Compare commits
1464 Commits
message-tr
...
workspace-
Author | SHA1 | Date | |
---|---|---|---|
87ce301faa | |||
1ab526dc64 | |||
c11162f794 | |||
8e5d613bfa | |||
8786da0044 | |||
3d5cb0f30a | |||
fb28f77c85 | |||
58c8006f1f | |||
277cff3013 | |||
cde3ce2e27 | |||
57a4ad2d00 | |||
8bc0caa21b | |||
ae478c2344 | |||
0dfdc9371e | |||
ac92d0678b | |||
bd9e6a3bdf | |||
2ce93ce39e | |||
5b95ccae5f | |||
0310f07eab | |||
e3acaa05be | |||
91d8a32f25 | |||
457c7adf59 | |||
8ebdb7f493 | |||
6c55ca59b0 | |||
0e4a47c0aa | |||
33100cd204 | |||
39a8243f27 | |||
5f50922d32 | |||
1137ca0fce | |||
36f3429ad2 | |||
703092f2c9 | |||
fd75c7c7b4 | |||
8abbd5dacb | |||
ed0fa7e1b7 | |||
15f1245927 | |||
25434e42d0 | |||
d6749589e8 | |||
8fea88879a | |||
d9e778f501 | |||
885b6ffaef | |||
4c64920f45 | |||
7369ea6125 | |||
20b7d34577 | |||
6cc4c33b13 | |||
5abf9a0425 | |||
17a0b27109 | |||
2bcdae4d5d | |||
0244c6d5b8 | |||
bc6be40fdd | |||
8c22b58611 | |||
074fe3ea70 | |||
5666fdefce | |||
e9613b0340 | |||
8a030e8fc4 | |||
9c23bf02bd | |||
8468fe87eb | |||
9617e83d88 | |||
912a30c566 | |||
2cd84da835 | |||
64fad9a394 | |||
3b797b32bc | |||
f6ae48ec70 | |||
61869d7db1 | |||
e2e90a550e | |||
80ceead18d | |||
ed8884e192 | |||
3f6165799f | |||
d2a40d6885 | |||
eb8fc738af | |||
b9e1d917da | |||
f721e80685 | |||
361a115729 | |||
d107b84be4 | |||
4cb967f3ac | |||
6f070319a0 | |||
13edecde6c | |||
75d1230dd1 | |||
779d5462f2 | |||
03757c1fcb | |||
1faee65a68 | |||
f7ab90b93b | |||
a7fa7d748d | |||
2b6d4ff279 | |||
06cea89dae | |||
260ca64e94 | |||
6dc4adfc13 | |||
e727c184ef | |||
412c50b939 | |||
8dcd70edbe | |||
1b383c7285 | |||
e96a90b161 | |||
d64d491f63 | |||
a6da22fa70 | |||
56cf7a6628 | |||
5b71788a84 | |||
39f7aa8457 | |||
c6baee2622 | |||
20f49e8c89 | |||
d6a6e6220a | |||
3b333c37fd | |||
289d577bc1 | |||
00ba937171 | |||
df3560143d | |||
bdeb20f7c9 | |||
1dd35b7d08 | |||
450291b856 | |||
125adb4d32 | |||
13cc58937e | |||
ee6a852996 | |||
2cfe978e1f | |||
0625655456 | |||
39a1f5cfe0 | |||
961fdd861f | |||
28adc03cce | |||
68c482ec32 | |||
a739f89dd1 | |||
5c7042cff3 | |||
255d4634a9 | |||
cb4c2ab824 | |||
4c449124ee | |||
c74536c9b3 | |||
df10ef532f | |||
6e18d18a81 | |||
4bc078b5fd | |||
cf49882e96 | |||
b0c6cf3fc5 | |||
883f51be93 | |||
ad52d783bd | |||
05f9be046f | |||
26aaecc33d | |||
f91138d0a2 | |||
89d89ae1cf | |||
e67fbcdc79 | |||
f4572eedd0 | |||
988b515ad2 | |||
2205a395e7 | |||
108b582f0d | |||
6ebd808e8b | |||
343c1133f4 | |||
fea2044eb8 | |||
6b353ece82 | |||
5b3f40102f | |||
b2ab3de80e | |||
56f6c9c5f6 | |||
39b0c88c76 | |||
1fca8a8b95 | |||
d442494f3a | |||
1f1fe36b89 | |||
d21f04b8b4 | |||
e73e4375b8 | |||
2905b0318d | |||
ab1ecb5ba2 | |||
47b8d16067 | |||
dc020628b5 | |||
bed063eea1 | |||
ed76e52918 | |||
e0f58c615b | |||
a681a2ba02 | |||
856207c154 | |||
00d897f282 | |||
49a09657c5 | |||
38fb51a99e | |||
99a865fb0f | |||
b919dd7271 | |||
9ddf19a1a4 | |||
23353fb77a | |||
8bdfb8df68 | |||
a65a0f03d4 | |||
7322a4e4ef | |||
870be026d8 | |||
e4fc899aca | |||
5412ce276c | |||
6200daa5bb | |||
4207536377 | |||
90e042e30c | |||
4e45e28ea0 | |||
d7a19fdd1b | |||
428d2fdb76 | |||
d77409876c | |||
81714ce1a3 | |||
6bae9ed20d | |||
44f70646d3 | |||
ed83b5494c | |||
84bac4414c | |||
4c4a703e63 | |||
c08021d91f | |||
df5ec16842 | |||
055a34877e | |||
a538027fe7 | |||
77289737c5 | |||
512798f9c6 | |||
5322e9a643 | |||
34f8568547 | |||
66e0e5b370 | |||
960af783f7 | |||
1a884535ad | |||
b2bb0bf10f | |||
4de15b2b57 | |||
493844d005 | |||
046308c582 | |||
cedcbc5fcf | |||
a46baeed0b | |||
cff503922c | |||
ce2efc6a67 | |||
778aa6be09 | |||
0052657a22 | |||
f60b995236 | |||
ceedc7e32c | |||
62507c9759 | |||
7beb7e0f65 | |||
ad67225bf8 | |||
dd0f721694 | |||
24f1e87813 | |||
68a0d7897f | |||
d9d56b7492 | |||
96a0b9d416 | |||
d48f064636 | |||
4e0a1c1152 | |||
272cbc1485 | |||
0ba7188625 | |||
6a2f038515 | |||
9f04009f80 | |||
2763d8dcc1 | |||
f8eb881728 | |||
0406b91bd5 | |||
e6678dadd8 | |||
a2fb8a8dce | |||
5f212b1ae2 | |||
23432e616d | |||
7d44c666ff | |||
ca09595350 | |||
b1dc2d967e | |||
c546ad9720 | |||
53f53c5a91 | |||
d2f675e41c | |||
d66370aecb | |||
1694148bdb | |||
7d0eeef90a | |||
23f3af832c | |||
52a87a22ec | |||
ea63f7562f | |||
d3de4e3fbd | |||
f326595202 | |||
4dd4c9f99f | |||
69bed929af | |||
db457db060 | |||
12c113a7a7 | |||
be96cb9291 | |||
bfc7b98e1d | |||
054f498201 | |||
6024b87d27 | |||
59b1aa26bb | |||
f96bc5c052 | |||
67c05e07b1 | |||
dafaab66b1 | |||
4fd24da4e4 | |||
bdfc516715 | |||
94da2531e7 | |||
9e99a8c25a | |||
bc83890c39 | |||
2d55eab62e | |||
d66207bc68 | |||
9f39ce5d27 | |||
eef194c3aa | |||
22f4aabadf | |||
2a19d5f143 | |||
7279cc1db8 | |||
e41b0bc16d | |||
e3aab2a90f | |||
7eec8a899a | |||
3262b63325 | |||
be59876c60 | |||
26f395bb84 | |||
7653cf6ad0 | |||
3203dc549c | |||
4ae9c978ce | |||
cb5c18c783 | |||
cb2babb1a0 | |||
c648a5f117 | |||
e562c863f7 | |||
cdaf8f3d19 | |||
a33aab07e6 | |||
26a5197d33 | |||
8be2800486 | |||
a2528a7a98 | |||
2332e2b0c5 | |||
a70f1f449b | |||
e425a183f7 | |||
0bbeb733a2 | |||
ff77994204 | |||
563951b789 | |||
ea65f5e795 | |||
670048e555 | |||
35b5cb9860 | |||
978cf9d3ab | |||
96fb6d8f16 | |||
c5c66ceb98 | |||
1efb0213c5 | |||
1a466cfce4 | |||
46ba718ab2 | |||
b40cc2c294 | |||
c86da9afbd | |||
d00933f044 | |||
172f78daad | |||
d7cc82909a | |||
7ce65e421b | |||
0c8941715d | |||
5ba9bb7a67 | |||
7b73df78c6 | |||
43d479c786 | |||
b9eca84d34 | |||
aef005f451 | |||
24669e3ba7 | |||
e99eefbb00 | |||
08b84b1449 | |||
7c980f42c9 | |||
42366e4be4 | |||
991a0c642b | |||
6aac51a2a7 | |||
6adc12368c | |||
eb2ee3f259 | |||
8b5cd4ef0f | |||
4517f60630 | |||
b25bad9995 | |||
c9b178b193 | |||
6b0fe1b0b0 | |||
c908a060b8 | |||
063fc8e29c | |||
d870fef122 | |||
6b723ed72a | |||
56fb7e2c58 | |||
41a5282b7e | |||
6f9ede569e | |||
e2e11b1a29 | |||
b59daac6f3 | |||
5fef9188c9 | |||
2c5d825c87 | |||
d5d7d8a391 | |||
7811632e6f | |||
6c5c3bedbe | |||
0942f50781 | |||
1eb6dfe1b8 | |||
1a77acfda6 | |||
ffd7eaede5 | |||
688a315cbf | |||
48fff0e96b | |||
e6bb06a7cc | |||
26225f0bfb | |||
3e4f744e56 | |||
2d2ac5b3f6 | |||
e06b608b10 | |||
f24e567dc4 | |||
1ea488bb3d | |||
8d47a150df | |||
dc24252e82 | |||
2167be053d | |||
926ddc2bdf | |||
4f7a28863c | |||
6e902f5fec | |||
01e7d6f30e | |||
d6f1c10b1b | |||
65f0b483f8 | |||
6a52deec7d | |||
3d468c26b0 | |||
a5e61e27c7 | |||
59ba112959 | |||
4800f63c3a | |||
1fce237538 | |||
71685a3b48 | |||
c0b9ce16a7 | |||
d1407d0026 | |||
d5bfc503fe | |||
de50cf80a8 | |||
5086bfedac | |||
f67ad23033 | |||
b7c1400eb3 | |||
b956c6f093 | |||
bfc850a94d | |||
be6e189b49 | |||
1c8955b34a | |||
e5a802bf99 | |||
55f290bc96 | |||
909f2e670a | |||
fa44289dd0 | |||
deb7e7317e | |||
5f1a2e3cec | |||
8b3f05b6e3 | |||
21ac225981 | |||
0c5d87d79b | |||
c224e019fe | |||
aa91491730 | |||
7bc77a23b4 | |||
b8a5d3fd9b | |||
8734a59cd7 | |||
e90504953f | |||
d67aa39bb1 | |||
1dc559740f | |||
a3e3d61d58 | |||
f6e9ae35fd | |||
3c33cf425e | |||
141b4ffe87 | |||
35d8780186 | |||
d11ea7d6cd | |||
3138b20b11 | |||
c3fb3a98b8 | |||
32cc136563 | |||
63e89482fe | |||
e8917e2d6f | |||
a2f4e196a1 | |||
dc1e23501c | |||
0e3431ac47 | |||
af7ba00e97 | |||
8d6ab6fe84 | |||
4917c79d09 | |||
04da2a61db | |||
3ca86b2197 | |||
35400238aa | |||
439d7f036f | |||
aed6375a2d | |||
a9a8b1ec6a | |||
b0e713b775 | |||
32fc25a3ac | |||
839492f15b | |||
c98103ffc8 | |||
86f3a637f1 | |||
e381f0f492 | |||
ce72aaf008 | |||
d79cae07a4 | |||
b2495a0e25 | |||
f0c6e96e82 | |||
ac1b814851 | |||
614bcd8016 | |||
c171ea12df | |||
b856e2990b | |||
e4cf3f144d | |||
f25e6916bd | |||
ad624d546f | |||
6df21fd5ff | |||
392999bc43 | |||
013f07278b | |||
35f806df4e | |||
4f1f226828 | |||
4c6dd64e87 | |||
a381017e83 | |||
34aa46a844 | |||
dc58a4f407 | |||
0428234c67 | |||
0155226c73 | |||
9585c823d5 | |||
31df386fed | |||
11794e3da9 | |||
c6f6ac926e | |||
79a33fa7fb | |||
909ec7a709 | |||
576376a21b | |||
0c2aa1437d | |||
bff553e978 | |||
ab206ff82a | |||
75e608f039 | |||
2fe7507689 | |||
cbed9f956a | |||
6d115dd23b | |||
8bbfa913cb | |||
65a7b042ed | |||
3640a4dd1e | |||
d5f1cd667b | |||
8f307c858c | |||
f21403fd9f | |||
a19e8d58f5 | |||
08f7ecad35 | |||
548a23a969 | |||
5a83ef8325 | |||
d2b968a7df | |||
58001563d9 | |||
8872913665 | |||
d66e7dd49e | |||
f365484a5c | |||
c3e557b162 | |||
b411425e1e | |||
e18d8b2fe8 | |||
81982beef4 | |||
0cdad5ac02 | |||
1c3bddda41 | |||
5d9b109f06 | |||
4d0480bfea | |||
580e3d2b74 | |||
ec018a000e | |||
e5dd86276d | |||
9ae1238038 | |||
f8dc32c366 | |||
2b3c31a503 | |||
9d94da83d7 | |||
5661946de1 | |||
fa752110f3 | |||
86efdc906a | |||
c217377114 | |||
6064661de9 | |||
ef544f6e06 | |||
c48c80e4e9 | |||
a915af4b30 | |||
bc22109130 | |||
17055023db | |||
95ac478f87 | |||
2fa6f7ba7e | |||
6b47b78d4d | |||
3dbee0b833 | |||
8886b7433c | |||
85d3336245 | |||
33e955770c | |||
be7b9729a2 | |||
10e59c0840 | |||
fe1a9c2b4b | |||
ac470b44ef | |||
0547a582d1 | |||
a1389a0730 | |||
ee7e46fbf6 | |||
13229a6d86 | |||
3efc7bf7f1 | |||
4d18d54d9d | |||
ccaa3f4ebf | |||
8064c6c827 | |||
a5d4abda00 | |||
6df48b68fa | |||
20b10b4d16 | |||
6bdfe638c5 | |||
67543775e9 | |||
25c6acf44b | |||
83aa23b274 | |||
4333b829eb | |||
42c736614c | |||
700911ca5f | |||
627f1cb59a | |||
ba0a038247 | |||
7ce5ea4142 | |||
9bfda11f0d | |||
1facd85981 | |||
630227f6b6 | |||
bbdf88cf94 | |||
de16108dff | |||
80f23841e4 | |||
df65d3d58c | |||
66f6fff54e | |||
ad557a3515 | |||
0215a449ad | |||
bd250e188b | |||
826e9c5dbf | |||
30f628d1eb | |||
bf978c61af | |||
5ebbd72d1c | |||
47c80d4d55 | |||
4e14f18a58 | |||
4456954d30 | |||
6925a82204 | |||
64efa61dc2 | |||
3ce25296e1 | |||
b017a2187b | |||
dd155e2f87 | |||
38a69f56b4 | |||
3d2d396c09 | |||
b33ebb450a | |||
560da76387 | |||
5b3c086ec3 | |||
8273de6bfa | |||
e15e202e11 | |||
c4edba042a | |||
8e25a1eb3b | |||
1babf10e58 | |||
8f9ef3f8f8 | |||
446e97bf12 | |||
4a316fbc66 | |||
8a7c1eebc0 | |||
f336564e19 | |||
aebdf79430 | |||
25fe730d16 | |||
61b029207d | |||
4c068c135b | |||
734d93087a | |||
e22fbda689 | |||
11db188fe9 | |||
61c906807b | |||
c6eb2761c7 | |||
109de6a356 | |||
fe5a289460 | |||
1b2a1a3a3d | |||
e967807acf | |||
4aee584850 | |||
ab8984e1ce | |||
b96cf2042a | |||
1413fa6e03 | |||
a6e4bab990 | |||
4b781ae7e6 | |||
702b8e78e4 | |||
4571a0ea0d | |||
437538644e | |||
dc5ceffe40 | |||
1c7592b4b1 | |||
fc8c93b2ed | |||
9708b68540 | |||
01f4dc68b8 | |||
8c1bf346a9 | |||
8f0f159960 | |||
cbf2cbac61 | |||
82e6f9c8d6 | |||
ae9360659d | |||
63f2066135 | |||
745e8f608f | |||
c9a7c9d491 | |||
ad8a97dfe0 | |||
8591a2fc09 | |||
d188a7b32a | |||
52d9cebcef | |||
f18e57dc38 | |||
828ad348ce | |||
516fe49bb0 | |||
555409aba2 | |||
7298831f54 | |||
fd5f30ab0d | |||
a2553f48e2 | |||
6f75c8c172 | |||
0cb8953232 | |||
6ea18136ac | |||
e43f10c8ed | |||
bbbce5f12f | |||
6cdc2c26df | |||
4e1de26a02 | |||
b5ee89459a | |||
2adfca0582 | |||
5ccc763385 | |||
652ce3ce8c | |||
1bd0ad11d8 | |||
27e71d9a6c | |||
7f2fce2684 | |||
25f907ffb1 | |||
1034798969 | |||
862f1ea18c | |||
7c65643eed | |||
e9da061558 | |||
45dcfa9a70 | |||
907dd4381b | |||
687bfbb081 | |||
246d9f1357 | |||
e37f17be2b | |||
3c2ec3c770 | |||
a969e82954 | |||
509ad5ed9a | |||
afd3b76970 | |||
399ebcc049 | |||
e88f08072c | |||
38c3422331 | |||
0e2ed0fb0b | |||
4f61f9a43d | |||
d6995194dd | |||
0a9ebf62b3 | |||
ab25b8ab69 | |||
d2290cc3e2 | |||
e5e8590024 | |||
178c8c50a0 | |||
b7212a4e8f | |||
177a136adc | |||
dddd97f6df | |||
86feb674ee | |||
0f8da7d24f | |||
8efaa798a9 | |||
a243ba6693 | |||
ba1da9deb9 | |||
29781b2e5c | |||
e942444630 | |||
c3b88d4113 | |||
6d2d4fcc77 | |||
9f9067e29b | |||
c779dcb608 | |||
1c4d53eec3 | |||
a25f7df626 | |||
f8dc3b88f8 | |||
539b8ae9f6 | |||
cc06916811 | |||
5e7c25e136 | |||
b9f9dd948a | |||
d1f0a1e33a | |||
93d3270566 | |||
6f57f07214 | |||
1951812a47 | |||
8f6a7f393d | |||
d3031b406a | |||
2915319602 | |||
0751d122d7 | |||
957b3b69dc | |||
083b1c9d8b | |||
09edf4ba59 | |||
1a50b94b19 | |||
5bd977dd3c | |||
c3cb0be011 | |||
63ee20dc17 | |||
9284e96a48 | |||
bdd4e68422 | |||
5c60d8982e | |||
50ed2f45af | |||
9debefd7ca | |||
c18ff91030 | |||
039229f340 | |||
d9169d27f5 | |||
7ff7ec0e7a | |||
a9c0dcbd6b | |||
8258c01d5c | |||
6c6527b66d | |||
cac0848316 | |||
26c9453b3a | |||
4e283cf419 | |||
e96a76edf4 | |||
766b5b801c | |||
3fb7cce80f | |||
a0d9c9b65a | |||
597cd9c7ab | |||
1f7f3715cc | |||
0905940ef8 | |||
0573cbfbac | |||
65e37b1d6b | |||
e7fb2c50a3 | |||
09374b08a3 | |||
7fa9e88294 | |||
b76fe12209 | |||
911e71542b | |||
43de15c3fc | |||
5cb2ea04f7 | |||
e287aa10e3 | |||
b237cf9a63 | |||
c0d4f11510 | |||
1850c93785 | |||
cd0e31cd30 | |||
02ebd44851 | |||
29c4742383 | |||
d059d1fedc | |||
7cac4ee1ae | |||
0c38f49dde | |||
2e77724341 | |||
13c10b1e27 | |||
229604690b | |||
4dfc869e9d | |||
7bde88a6e2 | |||
73ecdbd6da | |||
6eb90434f0 | |||
65991cc057 | |||
95d438b86d | |||
46906eef43 | |||
b2185eb2b1 | |||
539ad8196e | |||
adbf3b1c37 | |||
6259513b0f | |||
d8df46d4a1 | |||
4aa80105ca | |||
13a1175792 | |||
ed065fc4ea | |||
4632db177a | |||
cc0de39725 | |||
f8a5728615 | |||
5695c8b0aa | |||
11c73fcbc3 | |||
5a7201ef4d | |||
d2902cb70f | |||
1b03d5cb45 | |||
51caecc1b3 | |||
ef1d3c56d8 | |||
aa8f386b7d | |||
43b751e27a | |||
69fde6822c | |||
b28c4dcd81 | |||
5014d16647 | |||
a5f9039e12 | |||
d9eb4702b2 | |||
a19047fa3a | |||
edba3e843f | |||
e48d119801 | |||
643c7fdb3a | |||
174caf0016 | |||
ab0d57d6ca | |||
a45021bd60 | |||
7b79e2e35c | |||
fdbda8e731 | |||
4c6c6d8658 | |||
c405cce700 | |||
8ade2fc116 | |||
40c7d3763b | |||
71bbd4ed5c | |||
a2617fe06a | |||
d1e1afdaab | |||
a94978db36 | |||
97f883b10e | |||
3af4ca3fe9 | |||
c19c236dbf | |||
6873539846 | |||
d9275d3dda | |||
e8b72a2a59 | |||
a160e31c2a | |||
a84e2e3307 | |||
83689e494c | |||
7c4d4b8695 | |||
771648893f | |||
81aed78a1f | |||
9af979097d | |||
35764fa09e | |||
4b1fea2fa4 | |||
04ecde9f8e | |||
ee79579b2e | |||
0f52c924e2 | |||
9ef2a46e33 | |||
1f550dbc72 | |||
e63a0f3a13 | |||
62b07a5260 | |||
91bcca71c6 | |||
e8fc731fc1 | |||
91e2117708 | |||
4ccff179a5 | |||
bd4aa54e48 | |||
f8b142064b | |||
38f5e30162 | |||
2799327c84 | |||
da4e24555b | |||
7f8f0f2358 | |||
cae61e62fd | |||
ab61017041 | |||
792dc489ee | |||
9baf8e19ea | |||
362fc78dcf | |||
4800a80c3a | |||
d8f7629a4f | |||
7514dfaef9 | |||
c50e324366 | |||
6e09cc5fc8 | |||
eafd3848a9 | |||
942810e88e | |||
df13408973 | |||
bbfc980fe6 | |||
c30b1c9bea | |||
9e554097f3 | |||
302b892dbc | |||
c0134067d7 | |||
a691192ffa | |||
5616a0ae2a | |||
a4befeba53 | |||
e6a70e4676 | |||
016ab1afee | |||
748739ed9c | |||
7fbf8ae4c9 | |||
9914754458 | |||
5a94a1cf3f | |||
afadeef204 | |||
f866707101 | |||
49919acb7c | |||
f76dae5ec8 | |||
bf6d0dc808 | |||
e4a6bf994f | |||
3fe7b13959 | |||
79aeb44bf8 | |||
51478f16ec | |||
528930d0a9 | |||
88be8e87ac | |||
b83d5975b0 | |||
6ca2fd03bd | |||
87e5457095 | |||
cba49959c1 | |||
7c5343a9fb | |||
0e91a213f5 | |||
466f661436 | |||
dad80b3d20 | |||
d96d07af10 | |||
0e40782723 | |||
7cc1cf3b4a | |||
702f596c44 | |||
db45c0920b | |||
d4a8c64d22 | |||
0deffbaaf0 | |||
af3ca027a1 | |||
24a4ca0c6d | |||
e1a109b9be | |||
8aeadcdf9a | |||
68bc5baf12 | |||
8da5c5f50e | |||
084ef99d10 | |||
730681aabc | |||
efd30a0eef | |||
3d60245b18 | |||
ccbf247970 | |||
84efaac52b | |||
8f5aae6df6 | |||
35f27ae150 | |||
d387a2d531 | |||
63d84533c9 | |||
fe91f7fe9e | |||
cc163237cc | |||
4acb082b6e | |||
929bf7f989 | |||
e3eaa69948 | |||
155bee388d | |||
2ca70e9e76 | |||
d31e905978 | |||
a57a34c540 | |||
b3794f1c17 | |||
5b971a66ba | |||
77fcf68ea3 | |||
15dd1164b7 | |||
0eeb62794d | |||
07cfb8d524 | |||
3e2a9a57a1 | |||
a0be7fa455 | |||
a433a1c637 | |||
d05cf2445d | |||
764f5b62f0 | |||
03a0809e39 | |||
f0645d468c | |||
ea3b2a5707 | |||
25eaf2a294 | |||
7bf748c579 | |||
04f33e8dc7 | |||
01c493565f | |||
f1e3104128 | |||
2ace472100 | |||
fff04d51b7 | |||
a1bfaac5a2 | |||
d80c1af1a3 | |||
cbde065a01 | |||
0b1c7320ab | |||
ce3f003e46 | |||
9fca747ef4 | |||
9455554453 | |||
4859eb63c4 | |||
e7220591ba | |||
20d579e7d8 | |||
52a68eb24a | |||
bc57574094 | |||
e4e92a2b38 | |||
023a274e41 | |||
5d0536d732 | |||
08b8b39a5d | |||
c972ff3ab4 | |||
98c2247c1b | |||
a97b8c1947 | |||
1dd2f2c6bc | |||
a9a513c621 | |||
2ce746e7dd | |||
11cde53108 | |||
fe542f8732 | |||
91319d5da2 | |||
5bd7b0dc31 | |||
c8f4adde7f | |||
b6a47cdf76 | |||
8b242dd4bd | |||
9b3e16595b | |||
2179f5836e | |||
320adb316d | |||
db36a90c48 | |||
5cfd72ff8b | |||
1d721c9080 | |||
5de1a15d98 | |||
b02089c435 | |||
ccdc6264da | |||
b736f52037 | |||
1af392b5f0 | |||
84716bccd4 | |||
f438ccfc53 | |||
fdd819e9f6 | |||
a3a09e6b2e | |||
7160e8a137 | |||
016ad69550 | |||
42e9b21b24 | |||
b6e3bf198d | |||
5e3eb5e8c3 | |||
ed1dc4649b | |||
5b586fdf3e | |||
703b21cef0 | |||
c7ec84eb33 | |||
4ce2620b68 | |||
ec6bc8f216 | |||
47a92e7fc0 | |||
e835cd2c2d | |||
6098dca0f8 | |||
21ff050a40 | |||
508001bfde | |||
410dfb2da3 | |||
74ccdbf3a9 | |||
80fc55b8ea | |||
dc424280a1 | |||
7b7c34a399 | |||
865976cfe7 | |||
81a497b476 | |||
dc1a6c746d | |||
27bcce0888 | |||
fdcb73d93e | |||
47dae0832b | |||
78e3126f97 | |||
12e45f275b | |||
7b32888322 | |||
44c2027b40 | |||
a90c81b2c7 | |||
8a25d49319 | |||
014ac2d388 | |||
1e3bf0ea7e | |||
54d11b65a1 | |||
6318c8e95b | |||
8c5bb8655d | |||
75b52d36f2 | |||
5111edb80b | |||
c4406d4ace | |||
3bd2f75866 | |||
6360d8bba6 | |||
f104a329b0 | |||
0e9c47bd56 | |||
2ca1fe3254 | |||
88211ed4bb | |||
266a0fb7d6 | |||
e6b91414de | |||
62afd2ffa3 | |||
37692513cf | |||
35bf6b0d36 | |||
0d1ac8cb5b | |||
b09b30616d | |||
daf133dca8 | |||
ae7f30483d | |||
d6fc2cc36f | |||
082a15bbc5 | |||
2bd64b6cab | |||
b1486f54c8 | |||
39a24b9a6d | |||
6e2ec7291e | |||
b0ba40f812 | |||
50c453c54d | |||
b71afe55d8 | |||
dd0882aa8b | |||
31914ab23b | |||
1816a6339d | |||
e9e2786fa3 | |||
024ab39c6d | |||
1210a78193 | |||
c55dbd5fbd | |||
263261cc86 | |||
077f0c56f1 | |||
e83656969e | |||
7d7ed7ce7a | |||
929073882f | |||
0513297029 | |||
704354be2c | |||
3715109ebe | |||
9d21b2cb25 | |||
7f56d06546 | |||
cf0664fcc6 | |||
26c8227df5 | |||
df43352441 | |||
7d705d4de3 | |||
4a0fbf03a8 | |||
54168fcd31 | |||
5012d64580 | |||
10ac42d6ad | |||
a277dfa9ce | |||
3d6c12121f | |||
98a093a279 | |||
7467e9e3a5 | |||
a7cd294403 | |||
29d89467b9 | |||
dc5dcc6139 | |||
6aaf4b87d5 | |||
4392516713 | |||
a9aa89e858 | |||
58c0abe600 | |||
d42263c1bc | |||
1d2f0e0e15 | |||
752c25bb7d | |||
df09e199b0 | |||
ab75c8cacc | |||
e94d54bffb | |||
5bce103a40 | |||
5ab852bfa3 | |||
439203349d | |||
2320c393c9 | |||
887f0f554b | |||
46c210c314 | |||
b4c3ab6726 | |||
95a6353dee | |||
cfea0649d8 | |||
045faf3f12 | |||
44ede8c942 | |||
1ddb775d59 | |||
cbcbd11ba0 | |||
bae07700a1 | |||
ca13cec01c | |||
f2af295867 | |||
0dffec661a | |||
bab5a006d8 | |||
f89b95c2aa | |||
f262473a3f | |||
b571ad0760 | |||
750672061a | |||
2ca5cfd6f5 | |||
8dd572d1cc | |||
b8647cc00e | |||
a9bdffc9e6 | |||
68723f191c | |||
5eafb29332 | |||
c92ce5983d | |||
fe52a9e1a1 | |||
1a25cd98ea | |||
611ca3c161 | |||
1dd4c7140e | |||
b9e58947cc | |||
73ab59f1d6 | |||
df8b03398f | |||
feaaefd8ba | |||
04200a4281 | |||
c6f84cfa59 | |||
103d0cba20 | |||
095e15fc11 | |||
ca2a11c57d | |||
9423d2c9ac | |||
6461db9df6 | |||
5ff609ed49 | |||
4aa2473d18 | |||
3f5bb8f98d | |||
0487630e03 | |||
d173f9e19d | |||
f53bf17331 | |||
ac54fed8d4 | |||
6deee27d14 | |||
d9f43e27a0 | |||
d1a178301f | |||
8b3d4857aa | |||
8d3abea6ef | |||
5060081db5 | |||
a8fa8a498a | |||
5635797cb9 | |||
468f30e4ab | |||
68b943d8ea | |||
994b4c0007 | |||
f9e4385e02 | |||
909b5ec43c | |||
7c37e94eda | |||
874dd14ddc | |||
d39d7b45e0 | |||
23e11175f8 | |||
aefa8af60e | |||
14a53cc43c | |||
a6be7ad6bd | |||
af05a9e268 | |||
eab96c66fd | |||
2a740448e1 | |||
94472ba9fa | |||
dfb110cd0f | |||
57dd02f6ae | |||
0b1430058a | |||
f2172c088e | |||
b93cb4b976 | |||
6744433245 | |||
01cd42bd51 | |||
bc3b208f41 | |||
049fea477b | |||
0ec4c66f96 | |||
8ad1da0db2 | |||
bd0bd22c63 | |||
e3b839360a | |||
968679f68c | |||
5d0e33e267 | |||
52f1cc6d19 | |||
1b2377370b | |||
5a6c9f176e | |||
a9fea8248c | |||
47644cad6a | |||
b7c6ec4b46 | |||
908b0fb727 | |||
e21aea6e13 | |||
6c13ca817d | |||
38009309e9 | |||
d98db2bd59 | |||
1f274c04fb | |||
ddfe9442fa | |||
8b792f8942 | |||
db96437f18 | |||
a614d72b00 | |||
ec6a6727e0 | |||
cc8a95572e | |||
d1108e1e7d | |||
744bc996db | |||
22bf4c8a93 | |||
46bce04788 | |||
9a2d883cf5 | |||
690deeb502 | |||
367eaf9161 | |||
f0e531310d | |||
cdef8bf5aa | |||
e9a9a5243c | |||
ee57dab846 | |||
9f68786547 | |||
c02b57efc3 | |||
7183aac362 | |||
011db9f34d | |||
7d8c3f1ecd | |||
087ac8470e | |||
3ce89e3c80 | |||
543a41bfea | |||
cb80dc6834 | |||
b55fd735f4 | |||
32fd323153 | |||
3aea09b614 | |||
374fd35476 | |||
20d3b1f8b1 | |||
7de5e78977 | |||
2da6507a87 | |||
5ca665bdde | |||
46f8d913a1 | |||
858a6bf827 | |||
89173544d4 | |||
163b2d0403 | |||
c83883f1f7 | |||
f6cbb14393 | |||
ffd25fe9e4 | |||
524e2ca8e2 | |||
33dca51650 | |||
58bb0044b2 | |||
79865172d3 | |||
730e8ffdf9 | |||
d128cc5af3 | |||
b5c2e1c98c | |||
2016e08f7b | |||
e752193a54 | |||
88a4256ad5 | |||
62ca7fb268 | |||
765779272f | |||
3333f30c42 | |||
732ba8576d | |||
a4481b38d2 | |||
176487834a | |||
394e01850b | |||
5f8391314a | |||
d593877548 | |||
d2bf7ec66c | |||
fe0f4060c4 | |||
40b0459174 | |||
a0b5a44fe9 | |||
5ce1e3fe92 | |||
8b3258cb09 | |||
22948b3d39 | |||
aa7de264e6 | |||
046e0609ab | |||
1a0d507316 | |||
b5853fe7e5 | |||
0f81c7efe0 | |||
0f0e3d9644 | |||
f6b4fa6e7e | |||
fbb88da134 | |||
83f11870da | |||
c635cb7016 | |||
dd23a61dbb | |||
2e00bc4aed | |||
6423cbfc92 | |||
ed3e287d91 | |||
347196d35c | |||
2ab3d069fb | |||
21641d8925 | |||
499c5737da | |||
40a8e9c1a6 | |||
2e98aab2e7 | |||
760b6ad9ed | |||
14434601a3 | |||
ae3d1423e5 | |||
d56fbf6d6a | |||
b7c60b02d1 | |||
d7075d9913 | |||
72cb4bf346 | |||
bffadf1b6e | |||
8552721983 | |||
ec36a0070e | |||
216dafd5b1 | |||
a4feb91644 | |||
778fd72e22 | |||
ff01bf68fd | |||
edbfafc5bd | |||
c20a7d4b01 | |||
c4e0e4197d | |||
71f63962a8 | |||
ef4c9b6f1f | |||
4d43424efd | |||
1f1f4432f6 | |||
98e60eb7e9 | |||
16bdff4f48 | |||
565cdf502c | |||
2dc8d9b462 | |||
ce6dd21cd3 | |||
e7066d12cf | |||
09df6a3818 | |||
17d1e06c7c | |||
e6b80927ad | |||
f9c5202dd1 | |||
a21ba292eb | |||
5429104f04 | |||
bb0a977edc | |||
77ed621c74 | |||
fc39919856 | |||
d8800c095a | |||
e5ba414f2d | |||
b8a9eec14f | |||
059504ca70 | |||
b2db95380b | |||
b6bb26e9ae | |||
126d02ae90 | |||
8d76e362a0 | |||
004cf3da5c | |||
fb9fd6925a | |||
ec725cc6d4 | |||
79fe60e6fb | |||
c793d7d0a4 | |||
1b7c3580e6 | |||
3c3e0b6f20 | |||
fd6863dda4 | |||
690be611ee | |||
18c5405b79 | |||
cfd63c7d4c | |||
49a9335f68 | |||
5b1d52c5e7 | |||
9f43ed3f95 | |||
0770fd5be5 | |||
3c8ba348c2 | |||
b84a7042f1 | |||
8ef75524ea | |||
cec62a7ca5 | |||
77fe0db623 | |||
e86c821878 | |||
02f67af464 | |||
fd310bc7b9 | |||
94d3e27c53 | |||
6aa02c5edc | |||
97e19d7d4a | |||
94f32030e6 | |||
721e1ea863 | |||
5331d3e360 | |||
f52744cfbc | |||
4749393ab5 | |||
975603d608 | |||
99346522e9 | |||
090335ad8b | |||
d8af8f7305 | |||
2a2b597e09 | |||
da1d43fc61 | |||
03a750176b | |||
1d2bb5c51c | |||
60046aaa4f | |||
a6df234528 | |||
5ead0de7ca | |||
229cfd9f80 | |||
01c267b095 | |||
33a6c41810 | |||
7040e9a63b | |||
901eabb82d | |||
b11aa5f676 | |||
ccaa11bb6b | |||
dd0fba4270 | |||
3f6759061d | |||
abc7b1ff02 | |||
75a677701d | |||
11656ebd89 | |||
d62b4cf130 | |||
c4b3d18e26 | |||
38b7904f92 | |||
c89ee2f8f2 | |||
c21e692652 | |||
071efd826c | |||
d27f19a561 | |||
7642040fc0 | |||
67e70df0c9 | |||
fc0acc9e37 | |||
3a7dc32659 | |||
36e761b7a5 | |||
9a1cb9c3db | |||
32e2ff7573 | |||
ea4b1c6c29 | |||
2d574047e4 | |||
8ded91e975 | |||
0b2eeccd4b | |||
fa5b0efdb4 | |||
7555915441 | |||
3e1b1d5789 | |||
8e759d7f32 | |||
caaa543385 | |||
dce4b2f325 | |||
fb7ed1ee28 | |||
a74cef9d2f | |||
c88d21d487 | |||
6f83b39ee4 | |||
f5c4e23c9c | |||
f1fb0d32c6 | |||
ed129b40a3 | |||
fd1ce40ee7 | |||
2b15f38730 | |||
86515f3943 | |||
9afb09128b | |||
e40063fc34 | |||
d20d815b45 | |||
ed36517615 | |||
fa60165764 | |||
3c9d0fbca6 | |||
8d31c2f4dc | |||
eaa1f9a0f4 | |||
3e19f41cba | |||
e6902a1f1c | |||
b1007ed811 | |||
54d385a0a9 | |||
df48e9d1c0 | |||
e3be74a31a | |||
a8aa0c085f | |||
cd78f1158c | |||
d9668bd425 | |||
dc3ff10c6f | |||
31f9da7b8f | |||
05e3a747d0 | |||
8db212db99 | |||
2494cc1637 | |||
ee75355e71 | |||
863f9f285b | |||
d7e0051bc6 | |||
2aa305b51d | |||
4006d7d57f | |||
0a566f70b6 | |||
be2801d1fa | |||
648126e598 | |||
8184b2c57b | |||
337eab614e | |||
20abc4cb99 | |||
5aca0b7704 | |||
74446ce3e0 | |||
7bb14bd8da | |||
ed75f0da63 | |||
d9008054cf | |||
b10d9c9ad7 | |||
5f855045a2 | |||
88737b2083 | |||
a50d64e8e9 | |||
4deef2a9ef | |||
dd9a29633a | |||
4407f0bd65 | |||
73fd896f3c | |||
f8acd0f6f6 | |||
ccc6a23f68 | |||
c236c501e7 | |||
2a0e0ae66f | |||
11276a3505 | |||
c90371d9d5 | |||
038a32330f | |||
fa1fe4fdd1 | |||
59d6029f47 | |||
33b3d05039 | |||
7486c09fbb | |||
3f750a5637 | |||
e54fe59723 | |||
38b20ca0a4 | |||
8c178378ae | |||
341b9a80c5 | |||
45e89bf34a | |||
c17e1249d5 | |||
262f92e0be | |||
da797dff35 | |||
7043215f5e | |||
5202dd5157 | |||
7c4765d053 | |||
69c8aef6c2 | |||
71786e50cf | |||
e5647c4f83 | |||
98a8dd682d | |||
af3b965e00 | |||
2dfe113a42 |
12
.gitignore
vendored
@ -18,12 +18,20 @@ config
|
||||
configure
|
||||
data/gnome-shell.desktop
|
||||
data/gnome-shell.desktop.in
|
||||
data/gschemas.compiled
|
||||
data/org.gnome.shell.gschema.xml
|
||||
data/org.gnome.shell.gschema.valid
|
||||
data/org.gnome.accessibility.magnifier.gschema.xml
|
||||
data/org.gnome.accessibility.magnifier.gschema.valid
|
||||
js/misc/config.js
|
||||
intltool-extract.in
|
||||
intltool-merge.in
|
||||
intltool-update.in
|
||||
libtool
|
||||
m4/
|
||||
omf.make
|
||||
po/*.gmo
|
||||
po/gnome-shell.pot
|
||||
po/Makefile.in.in
|
||||
po/POTFILES
|
||||
po/stamp-it
|
||||
@ -36,9 +44,13 @@ src/Makefile
|
||||
src/Makefile.in
|
||||
src/gnomeshell-taskpanel
|
||||
src/gnome-shell
|
||||
src/run-js-test
|
||||
src/test-recorder
|
||||
src/test-recorder.ogg
|
||||
src/test-theme
|
||||
src/st.h
|
||||
src/stamp-st.h
|
||||
src/stamp-st.h.tmp
|
||||
stamp-h1
|
||||
tests/run-test.sh
|
||||
xmldocs.make
|
||||
|
20
Makefile.am
@ -1,9 +1,13 @@
|
||||
SUBDIRS = data js src tests po
|
||||
# Point to our macro directory and pick up user flags from the environment
|
||||
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
|
||||
|
||||
SUBDIRS = data js src tests po man
|
||||
|
||||
EXTRA_DIST = \
|
||||
.project \
|
||||
.settings \
|
||||
autogen.sh
|
||||
autogen.sh \
|
||||
tools/check-for-missing.py
|
||||
|
||||
# These are files checked into Git that we don't want to distribute
|
||||
DIST_EXCLUDE = \
|
||||
@ -14,14 +18,4 @@ DIST_EXCLUDE = \
|
||||
|
||||
distcheck-hook:
|
||||
@echo "Checking disted files against files in git"
|
||||
@failed=false; \
|
||||
exclude=`(for p in $(DIST_EXCLUDE) ; do echo --exclude=$$p ; done)`; \
|
||||
for f in `cd $(srcdir) && git ls-files $$exclude` ; do \
|
||||
if ! test -e $(distdir)/$$f ; then \
|
||||
echo File missing from distribution: $$f ; \
|
||||
failed=true ; \
|
||||
fi \
|
||||
done ; \
|
||||
if $$failed ; then \
|
||||
exit 1 ; \
|
||||
fi
|
||||
@$(srcdir)/tools/check-for-missing.py $(srcdir) $(distdir) $(DIST_EXCLUDE)
|
||||
|
@ -5,7 +5,6 @@ srcdir=`dirname $0`
|
||||
test -z "$srcdir" && srcdir=.
|
||||
|
||||
PKG_NAME="gnome-shell"
|
||||
REQUIRED_AUTOMAKE_VERSION=1.10
|
||||
|
||||
(test -f $srcdir/configure.ac \
|
||||
&& test -d $srcdir/src) || {
|
||||
@ -15,7 +14,7 @@ REQUIRED_AUTOMAKE_VERSION=1.10
|
||||
}
|
||||
|
||||
which gnome-autogen.sh || {
|
||||
echo "You need to install gnome-common from GNOME Subversion (or from"
|
||||
echo "You need to install gnome-common from GNOME Git (or from"
|
||||
echo "your OS vendor's package manager)."
|
||||
exit 1
|
||||
}
|
||||
|
125
configure.ac
@ -1,33 +1,43 @@
|
||||
AC_INIT(gnome-shell, 2.28.1)
|
||||
AC_PREREQ(2.63)
|
||||
AC_INIT([gnome-shell],[2.91.6],[https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-shell],[gnome-shell])
|
||||
|
||||
AC_CONFIG_AUX_DIR(config)
|
||||
AC_CONFIG_HEADERS([config.h])
|
||||
AC_CONFIG_SRCDIR([src/shell-global.c])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AC_CONFIG_AUX_DIR([config])
|
||||
|
||||
AM_INIT_AUTOMAKE([dist-bzip2 no-dist-gzip foreign])
|
||||
AC_SUBST([PACKAGE_NAME], ["$PACKAGE_NAME"])
|
||||
AC_SUBST([PACKAGE_VERSION], ["$PACKAGE_VERSION"])
|
||||
|
||||
AM_INIT_AUTOMAKE([1.10 dist-bzip2 no-dist-gzip foreign])
|
||||
AM_MAINTAINER_MODE
|
||||
|
||||
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])],)
|
||||
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
|
||||
|
||||
AC_CONFIG_HEADERS(config.h)
|
||||
|
||||
AC_DISABLE_STATIC
|
||||
# Checks for programs.
|
||||
AC_PROG_CC
|
||||
# Needed for per-target cflags, like in gnomeshell-taskpanel
|
||||
AM_PROG_CC_C_O
|
||||
AM_PROG_LIBTOOL
|
||||
|
||||
# Initialize libtool
|
||||
LT_PREREQ([2.2.6])
|
||||
LT_INIT([disable-static])
|
||||
|
||||
GETTEXT_PACKAGE=gnome-shell
|
||||
AC_SUBST(GETTEXT_PACKAGE)
|
||||
AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, "$GETTEXT_PACKAGE",
|
||||
[The prefix for our gettext translation domains.])
|
||||
|
||||
PKG_PROG_PKG_CONFIG(0.16)
|
||||
|
||||
IT_PROG_INTLTOOL(0.26)
|
||||
AM_GLIB_GNU_GETTEXT
|
||||
|
||||
PKG_PROG_PKG_CONFIG([0.22])
|
||||
|
||||
# GConf stuff
|
||||
AC_PATH_PROG(GCONFTOOL, gconftool-2, no)
|
||||
AM_GCONF_SOURCE_2
|
||||
|
||||
GLIB_GSETTINGS
|
||||
|
||||
# Get a value to substitute into gnome-shell.in
|
||||
AM_PATH_PYTHON([2.5])
|
||||
AC_SUBST(PYTHON)
|
||||
@ -50,17 +60,73 @@ fi
|
||||
|
||||
AM_CONDITIONAL(BUILD_RECORDER, $build_recorder)
|
||||
|
||||
CLUTTER_MIN_VERSION=1.5.15
|
||||
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
|
||||
GJS_MIN_VERSION=0.7.8
|
||||
MUTTER_MIN_VERSION=2.91.4
|
||||
GTK_MIN_VERSION=2.91.7
|
||||
GIO_MIN_VERSION=2.25.9
|
||||
LIBECAL_REQUIRED=1.6.0
|
||||
LIBEDATASERVER_REQUIRED=1.2.0
|
||||
LIBEDATASERVERUI2_REQUIRED=1.2.0
|
||||
LIBEDATASERVERUI3_REQUIRED=2.91.6
|
||||
|
||||
|
||||
# Collect more than 20 libraries for a prize!
|
||||
PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-unix-2.0 gtk+-2.0 dbus-glib-1 mutter-plugins
|
||||
gjs-gi-1.0 libgnome-menu $recorder_modules gconf-2.0
|
||||
gdk-x11-2.0 clutter-x11-1.0 clutter-glx-1.0
|
||||
gnome-desktop-2.0 >= 2.26 libstartup-notification-1.0
|
||||
gobject-introspection-1.0 >= 0.6.5)
|
||||
PKG_CHECK_MODULES(TIDY, clutter-1.0)
|
||||
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-2.0 libcroco-0.6)
|
||||
PKG_CHECK_MODULES(BIG, clutter-1.0 gtk+-2.0 librsvg-2.0)
|
||||
PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-2.0)
|
||||
PKG_CHECK_MODULES(TRAY, gtk+-2.0)
|
||||
PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION
|
||||
gio-unix-2.0 dbus-glib-1 libxml-2.0
|
||||
gtk+-3.0 >= $GTK_MIN_VERSION
|
||||
mutter-plugins >= $MUTTER_MIN_VERSION
|
||||
gjs-internals-1.0 >= $GJS_MIN_VERSION
|
||||
libgnome-menu $recorder_modules gconf-2.0
|
||||
gdk-x11-3.0
|
||||
clutter-x11-1.0 >= $CLUTTER_MIN_VERSION
|
||||
clutter-glx-1.0 >= $CLUTTER_MIN_VERSION
|
||||
libstartup-notification-1.0
|
||||
gobject-introspection-1.0 >= $GOBJECT_INTROSPECTION_MIN_VERSION
|
||||
libcanberra)
|
||||
|
||||
GJS_VERSION=`$PKG_CONFIG --modversion gjs-internals-1.0`
|
||||
AC_DEFINE_UNQUOTED([GJS_VERSION], ["$GJS_VERSION"], [The version of GJS we're linking to])
|
||||
AC_SUBST([GJS_VERSION], ["$GJS_VERSION"])
|
||||
|
||||
saved_CFLAGS=$CFLAGS
|
||||
saved_LIBS=$LIBS
|
||||
CFLAGS=$MUTTER_PLUGIN_CFLAGS
|
||||
LIBS=$MUTTER_PLUGIN_LIBS
|
||||
# sn_startup_sequence_get_application_id, we can replace with a version check later
|
||||
AC_CHECK_FUNCS(JS_NewGlobalObject sn_startup_sequence_get_application_id)
|
||||
CFLAGS=$saved_CFLAGS
|
||||
LIBS=$saved_LIBS
|
||||
|
||||
PKG_CHECK_MODULES(ST, clutter-1.0 gtk+-3.0 libcroco-0.6 gnome-desktop-3.0 >= 2.90.0)
|
||||
PKG_CHECK_MODULES(GDMUSER, dbus-glib-1 gtk+-3.0)
|
||||
PKG_CHECK_MODULES(TRAY, gtk+-3.0)
|
||||
PKG_CHECK_MODULES(GVC, libpulse libpulse-mainloop-glib gobject-2.0)
|
||||
PKG_CHECK_MODULES(JS_TEST, clutter-x11-1.0 gjs-1.0 gobject-introspection-1.0 gtk+-3.0)
|
||||
|
||||
AC_MSG_CHECKING([for bluetooth support])
|
||||
PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 2.90.0],
|
||||
[BLUETOOTH_DIR=`$PKG_CONFIG --variable=libdir gnome-bluetooth-1.0`/gnome-bluetooth
|
||||
BLUETOOTH_LIBS="-L'$BLUETOOTH_DIR' -lgnome-bluetooth-applet"
|
||||
AC_SUBST([BLUETOOTH_LIBS],["$BLUETOOTH_LIBS"])
|
||||
AC_DEFINE_UNQUOTED([BLUETOOTH_DIR],["$BLUETOOTH_DIR"],[Path to installed GnomeBluetooth typelib and library])
|
||||
AC_DEFINE([HAVE_BLUETOOTH],[1],[Define if you have libgnome-bluetooth-applet])
|
||||
AC_SUBST([HAVE_BLUETOOTH],[1])
|
||||
AC_MSG_RESULT([yes])],
|
||||
[AC_DEFINE([HAVE_BLUETOOTH],[0])
|
||||
AC_SUBST([HAVE_BLUETOOTH],[0])
|
||||
AC_MSG_RESULT([no])])
|
||||
|
||||
# Default to libedataserverui-3.0, but allow falling back to 1.2
|
||||
PKG_CHECK_EXISTS(libedataserverui-3.0,
|
||||
[EDS_API=3.0
|
||||
LIBEDATASERVERUI_REQUIRED=$LIBEDATASERVERUI3_REQUIRED],
|
||||
[EDS_API=1.2
|
||||
LIBEDATASERVERUI_REQUIRED=$LIBEDATASERVERUI2_REQUIRED])
|
||||
PKG_CHECK_MODULES(LIBECAL, libecal-1.2 >= $LIBECAL_REQUIRED libedataserver-1.2 >= $LIBEDATASERVER_REQUIRED libedataserverui-$EDS_API >= $LIBEDATASERVERUI_REQUIRED)
|
||||
AC_SUBST(LIBECAL_CFLAGS)
|
||||
AC_SUBST(LIBECAL_LIBS)
|
||||
|
||||
MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin
|
||||
# FIXME: metacity-plugins.pc should point directly to its .gir file
|
||||
@ -70,12 +136,11 @@ AC_SUBST(MUTTER_BIN_DIR)
|
||||
AC_SUBST(MUTTER_LIB_DIR)
|
||||
AC_SUBST(MUTTER_PLUGIN_DIR)
|
||||
|
||||
GJS_JS_DIR=`$PKG_CONFIG --variable=jsdir gjs-1.0`
|
||||
GJS_JS_NATIVE_DIR=`$PKG_CONFIG --variable=jsnativedir gjs-1.0`
|
||||
AC_SUBST(GJS_JS_DIR)
|
||||
AC_SUBST(GJS_JS_NATIVE_DIR)
|
||||
GJS_CONSOLE=`$PKG_CONFIG --variable=gjs_console gjs-1.0`
|
||||
AC_SUBST(GJS_CONSOLE)
|
||||
|
||||
AC_CHECK_FUNCS(fdwalk)
|
||||
AC_CHECK_FUNCS(mallinfo)
|
||||
AC_CHECK_HEADERS([sys/resource.h])
|
||||
|
||||
# Sets GLIB_GENMARSHAL and GLIB_MKENUMS
|
||||
@ -94,8 +159,7 @@ AC_SUBST(TYPELIBDIR)
|
||||
# Stay command-line compatible with the gnome-common configure option. Here
|
||||
# minimum/yes/maximum are the same, however.
|
||||
AC_ARG_ENABLE(compile_warnings,
|
||||
AC_HELP_STRING([--enable-compile-warnings=@<:@no/minimum/yes/maximum/error@:>@],
|
||||
[Turn on compiler warnings]),,
|
||||
AS_HELP_STRING([--enable-compile-warnings=@<:@no/minimum/yes/maximum/error@:>@],[Turn on compiler warnings]),,
|
||||
enable_compile_warnings=error)
|
||||
|
||||
changequote(,)dnl
|
||||
@ -122,13 +186,14 @@ changequote([,])dnl
|
||||
AC_PATH_PROG(mutter, [mutter])
|
||||
AC_SUBST(mutter)
|
||||
|
||||
AC_OUTPUT([
|
||||
AC_CONFIG_FILES([
|
||||
Makefile
|
||||
data/Makefile
|
||||
js/Makefile
|
||||
js/misc/Makefile
|
||||
js/ui/Makefile
|
||||
js/misc/config.js
|
||||
src/Makefile
|
||||
tests/Makefile
|
||||
po/Makefile.in
|
||||
man/Makefile
|
||||
])
|
||||
AC_OUTPUT
|
||||
|
@ -3,47 +3,102 @@ desktop_DATA = gnome-shell.desktop
|
||||
|
||||
# We substitute in bindir so it works as an autostart
|
||||
# file when built in a non-system prefix
|
||||
gnome-shell.desktop.in: gnome-shell.desktop.in.in
|
||||
%.desktop.in:%.desktop.in.in
|
||||
$(AM_V_GEN) sed -e "s|@bindir[@]|$(bindir)|" \
|
||||
-e "s|@VERSION[@]|$(VERSION)|" \
|
||||
$< > $@ || rm $@
|
||||
|
||||
# Placeholder until we add intltool
|
||||
gnome-shell.desktop: gnome-shell.desktop.in
|
||||
%.desktop:%.desktop.in
|
||||
$(AM_V_GEN) sed s/^_// < $< > $@ || rm $@
|
||||
|
||||
searchprovidersdir = $(pkgdatadir)/search_providers
|
||||
dist_searchproviders_DATA = \
|
||||
search_providers/google.xml \
|
||||
search_providers/wikipedia.xml
|
||||
|
||||
imagesdir = $(pkgdatadir)/images
|
||||
dist_images_DATA = \
|
||||
add-workspace.svg \
|
||||
app-well-glow.png \
|
||||
close-black.svg \
|
||||
magnifier.svg \
|
||||
remove-workspace.svg
|
||||
magnifier.svg
|
||||
|
||||
themedir = $(pkgdatadir)/theme
|
||||
dist_theme_DATA = \
|
||||
theme/gnome-shell.css \
|
||||
theme/close.svg \
|
||||
theme/calendar-arrow-left.svg \
|
||||
theme/calendar-arrow-right.svg \
|
||||
theme/close-window.svg \
|
||||
theme/scroll-button-down.png \
|
||||
theme/close.svg \
|
||||
theme/corner-ripple.png \
|
||||
theme/dash-placeholder.svg \
|
||||
theme/filter-selected.svg \
|
||||
theme/gnome-shell.css \
|
||||
theme/mosaic-view-active.svg \
|
||||
theme/mosaic-view.svg \
|
||||
theme/move-window-on-new.svg \
|
||||
theme/process-working.png \
|
||||
theme/running-indicator.svg \
|
||||
theme/scroll-button-down-hover.png \
|
||||
theme/scroll-button-up.png \
|
||||
theme/scroll-button-down.png \
|
||||
theme/scroll-button-up-hover.png \
|
||||
theme/scroll-vhandle.png \
|
||||
theme/section-back.svg \
|
||||
theme/section-more.svg
|
||||
theme/scroll-button-up.png \
|
||||
theme/scroll-hhandle.svg \
|
||||
theme/scroll-vhandle.svg \
|
||||
theme/section-more.svg \
|
||||
theme/section-more-open.svg \
|
||||
theme/separator-white.png \
|
||||
theme/single-view-active.svg \
|
||||
theme/single-view.svg \
|
||||
theme/toggle-off-us.svg \
|
||||
theme/toggle-off-intl.svg \
|
||||
theme/toggle-on-us.svg \
|
||||
theme/toggle-on-intl.svg \
|
||||
theme/ws-switch-arrow-left.svg \
|
||||
theme/ws-switch-arrow-right.svg
|
||||
|
||||
schemadir = @GCONF_SCHEMA_FILE_DIR@
|
||||
schema_DATA = gnome-shell.schemas
|
||||
gsettings_SCHEMAS = \
|
||||
org.gnome.accessibility.magnifier.gschema.xml \
|
||||
org.gnome.shell.gschema.xml
|
||||
|
||||
@INTLTOOL_XML_NOMERGE_RULE@
|
||||
@GSETTINGS_RULES@
|
||||
|
||||
# We need to compile schemas at make time
|
||||
# to run from source tree
|
||||
gschemas.compiled: $(gsettings_SCHEMAS:.xml=.valid)
|
||||
$(AM_V_GEN) $(GLIB_COMPILE_SCHEMAS) --targetdir=. .
|
||||
|
||||
all-local: gschemas.compiled
|
||||
|
||||
|
||||
# GConf schemas: provide defaults for keys from Metacity we are overriding
|
||||
gconfschemadir = @GCONF_SCHEMA_FILE_DIR@
|
||||
gconfschema_DATA = gnome-shell.schemas
|
||||
|
||||
menudir = $(sysconfdir)/xdg/menus
|
||||
|
||||
menu_DATA = \
|
||||
gs-applications.menu
|
||||
|
||||
shadersdir = $(pkgdatadir)/shaders
|
||||
shaders_DATA = \
|
||||
shaders/dim-window.glsl
|
||||
|
||||
install-data-local:
|
||||
GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(srcdir)/$(schema_DATA)
|
||||
GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(srcdir)/$(gconfschema_DATA)
|
||||
|
||||
EXTRA_DIST = \
|
||||
gnome-shell.desktop.in.in \
|
||||
$(schema_DATA)
|
||||
|
||||
CLEANFILES = \
|
||||
gnome-shell.desktop.in \
|
||||
$(desktop_DATA)
|
||||
|
||||
EXTRA_DIST = \
|
||||
gnome-shell.desktop.in.in \
|
||||
$(menu_DATA) \
|
||||
$(gconfschema_DATA) \
|
||||
$(shaders_DATA) \
|
||||
org.gnome.accessibility.magnifier.gschema.xml.in \
|
||||
org.gnome.shell.gschema.xml.in
|
||||
|
||||
CLEANFILES = \
|
||||
gnome-shell.desktop.in \
|
||||
$(desktop_DATA) \
|
||||
$(gsettings_SCHEMAS) \
|
||||
gschemas.compiled
|
||||
|
||||
|
@ -1,70 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="59.995201"
|
||||
height="59.995102"
|
||||
id="svg3113"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.46"
|
||||
version="1.0"
|
||||
sodipodi:docname="add-workspace.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
<defs
|
||||
id="defs3115">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective3121" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
gridtolerance="10000"
|
||||
guidetolerance="10"
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="375"
|
||||
inkscape:cy="520"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="641"
|
||||
inkscape:window-height="683"
|
||||
inkscape:window-x="4"
|
||||
inkscape:window-y="54" />
|
||||
<metadata
|
||||
id="metadata3118">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-498.57383,-439.50749)">
|
||||
<path
|
||||
id="path3269"
|
||||
d="M 528.57143,439.91129 C 512.23433,439.91129 498.97763,453.16795 498.97763,469.50504 C 498.97763,485.84214 512.23433,499.09881 528.57143,499.09879 C 544.90853,499.09879 558.16513,485.84215 558.16523,469.50504 C 558.16523,453.16794 544.90853,439.9113 528.57143,439.91129 z M 525.29023,451.16129 L 531.88393,451.16129 C 533.75363,451.16129 535.25893,452.66659 535.25893,454.53629 L 535.25893,462.84879 L 543.54023,462.84879 C 545.40973,462.84879 546.91523,464.35409 546.91523,466.22379 L 546.91523,472.81754 C 546.91523,474.68728 545.40993,476.19255 543.54023,476.19254 L 535.25893,476.19254 L 535.25893,484.47379 C 535.25893,486.34353 533.75363,487.8488 531.88393,487.84879 L 525.29023,487.84879 C 523.42053,487.84881 521.91523,486.34351 521.91523,484.47379 L 521.91523,476.19254 L 513.60263,476.19254 C 511.73313,476.19257 510.22773,474.68726 510.22763,472.81754 L 510.22763,466.22379 C 510.22763,464.35407 511.73303,462.8488 513.60263,462.84879 L 521.91523,462.84879 L 521.91523,454.53629 C 521.91523,452.66657 523.42043,451.1613 525.29023,451.16129 z"
|
||||
style="opacity:0.30701785;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.807603px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.6 KiB |
@ -62,5 +62,5 @@
|
||||
clip-rule="evenodd"
|
||||
d="M10.5,3.5l2,2L10,8l2.5,2.5l-2,2L8,10l-2.5,2.5l-2-2L6,8L3.5,5.5l2-2L8,6L10.5,3.5 z M0,8c0-4.418,3.582-8,8-8s8,3.582,8,8s-3.582,8-8,8S0,12.418,0,8z"
|
||||
id="path2394"
|
||||
style="fill-opacity:1;fill:#000000" />
|
||||
</svg>
|
||||
style="fill-opacity:1;fill:#545454" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@ -7,7 +7,7 @@ X-GNOME-Bugzilla-Bugzilla=GNOME
|
||||
X-GNOME-Bugzilla-Product=gnome-shell
|
||||
X-GNOME-Bugzilla-Component=general
|
||||
X-GNOME-Bugzilla-Version=@VERSION@
|
||||
Categories=GNOME;GTK;Utility;Core;
|
||||
Categories=GNOME;GTK;Core;
|
||||
OnlyShowIn=GNOME;
|
||||
NoDisplay=true
|
||||
X-GNOME-Autostart-Phase=WindowManager
|
||||
|
@ -1,108 +1,85 @@
|
||||
<gconfschemafile>
|
||||
<schemalist>
|
||||
|
||||
<!-- Metacity overrides -->
|
||||
<schema>
|
||||
<key>/schemas/desktop/gnome/shell/development_tools</key>
|
||||
<applyto>/desktop/gnome/shell/development_tools</applyto>
|
||||
<key>/schemas/desktop/gnome/shell/windows/attach_modal_dialogs</key>
|
||||
<applyto>/desktop/gnome/shell/windows/attach_modal_dialogs</applyto>
|
||||
<owner>gnome-shell</owner>
|
||||
<type>bool</type>
|
||||
<default>true</default>
|
||||
<locale name="C">
|
||||
<short>Enable internal tools useful for developers and testers from Alt-F2</short>
|
||||
<long>
|
||||
Allows access to internal debugging and monitoring tools using
|
||||
the Alt-F2 dialog.
|
||||
</long>
|
||||
<short>Attach modal dialog to the parent window</short>
|
||||
<long>
|
||||
This key overrides /apps/mutter/general/attach_modal_dialogs when
|
||||
running GNOME Shell.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<schema>
|
||||
<key>/schemas/desktop/gnome/shell/app_monitor/enable_monitoring</key>
|
||||
<applyto>/desktop/gnome/shell/app_monitor/enable_monitoring</applyto>
|
||||
<owner>gnome-shell</owner>
|
||||
<type>bool</type>
|
||||
<default>true</default>
|
||||
<locale name="C">
|
||||
<short>Whether to collect stats about applications usage</short>
|
||||
<long>
|
||||
The shell normally monitors active applications in order to present the most used ones (e.g. in launchers). While this data will be kept private, you may want to disable this for privacy reasons. Please note that doing so won't remove already saved data.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<schema>
|
||||
<key>/schemas/desktop/gnome/shell/favorite_apps</key>
|
||||
<applyto>/desktop/gnome/shell/favorite_apps</applyto>
|
||||
<owner>gnome-shell</owner>
|
||||
<type>list</type>
|
||||
<list_type>string</list_type>
|
||||
<default>[mozilla-firefox.desktop,evolution.desktop,openoffice.org-writer.desktop]</default>
|
||||
<locale name="C">
|
||||
<short>List of desktop file IDs for favorite applications</short>
|
||||
<long>
|
||||
The applications corresponding to these identifiers will be displayed in the favorites area.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<schema>
|
||||
<key>/schemas/desktop/gnome/shell/sidebar/visible</key>
|
||||
<applyto>/desktop/gnome/shell/sidebar/visible</applyto>
|
||||
<owner>gnome-shell</owner>
|
||||
<type>bool</type>
|
||||
<default>false</default>
|
||||
<locale name="C">
|
||||
<short>Whether or not to display the sidebar</short>
|
||||
<long>
|
||||
Determines whether or not the sidebar is visible.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<schema>
|
||||
<key>/schemas/desktop/gnome/shell/sidebar/expanded</key>
|
||||
<applyto>/desktop/gnome/shell/sidebar/expanded</applyto>
|
||||
<owner>gnome-shell</owner>
|
||||
<type>bool</type>
|
||||
<default>true</default>
|
||||
<locale name="C">
|
||||
<short>Whether the sidebar should be in the expanded (wide) mode</short>
|
||||
<long>
|
||||
Controls the expanded/collapsed state of the sidebar.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<schema>
|
||||
<key>/schemas/desktop/gnome/shell/sidebar/widgets</key>
|
||||
<applyto>/desktop/gnome/shell/sidebar/widgets</applyto>
|
||||
<owner>gnome-shell</owner>
|
||||
<type>list</type>
|
||||
<list_type>string</list_type>
|
||||
<default>[imports.ui.widget.ClockWidget,imports.ui.widget.AppsWidget,imports.ui.widget.RecentDocsWidget]</default>
|
||||
<locale name="C">
|
||||
<short>The widgets to display in the sidebar</short>
|
||||
<long>
|
||||
The widgets to display in the sidebar, in order from top to bottom. Each widget "name" is actually a JavaScript expression referring to a widget constructor object.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<schema>
|
||||
<key>/schemas/desktop/gnome/shell/disabled_extensions</key>
|
||||
<applyto>/desktop/gnome/shell/disabled_extensions</applyto>
|
||||
<key>/schemas/desktop/gnome/shell/windows/button_layout</key>
|
||||
<applyto>/desktop/gnome/shell/windows/button_layout</applyto>
|
||||
<owner>gnome-shell</owner>
|
||||
<type>list</type>
|
||||
<list_type>string</list_type>
|
||||
<default>[]</default>
|
||||
<type>string</type>
|
||||
<default>:minimize,maximize,close</default>
|
||||
<locale name="C">
|
||||
<short>Uuids of extensions to disable</short>
|
||||
<short>Arrangement of buttons on the titlebar</short>
|
||||
<long>
|
||||
Arrangement of buttons on the titlebar. The
|
||||
value should be a string, such as
|
||||
"menu:minimize,maximize,spacer,close"; the colon separates the
|
||||
left corner of the window from the right corner, and
|
||||
the button names are comma-separated. Duplicate buttons
|
||||
are not allowed. Unknown button names are silently ignored
|
||||
so that buttons can be added in future gnome-shell versions
|
||||
without breaking older versions.
|
||||
A special spacer tag can be used to insert some space between
|
||||
two adjacent buttons.
|
||||
|
||||
This key overrides /apps/metacity/general/button_layout when
|
||||
running GNOME Shell.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<schema>
|
||||
<key>/schemas/desktop/gnome/shell/windows/edge_tiling</key>
|
||||
<applyto>/desktop/gnome/shell/windows/edge_tiling</applyto>
|
||||
<owner>gnome-shell</owner>
|
||||
<type>bool</type>
|
||||
<default>true</default>
|
||||
<locale name="C">
|
||||
<short>enable edge tiling when dropping windows on screen edges</short>
|
||||
<long>
|
||||
GNOME Shell extensions have a uuid property; this key lists extensions which should not be loaded.
|
||||
</long>
|
||||
If enabled, dropping windows on vertical screen edges maximizes them
|
||||
vertically and resizes them horizontally to cover half of the
|
||||
available area. Dropping windows on the top screen edge maximizes them
|
||||
completely.
|
||||
|
||||
This key overrides /apps/metacity/general/edge_tiling when
|
||||
running GNOME Shell.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
<schema>
|
||||
<key>/schemas/desktop/gnome/shell/windows/theme</key>
|
||||
<applyto>/desktop/gnome/shell/windows/theme</applyto>
|
||||
<owner>gnome-shell</owner>
|
||||
<type>string</type>
|
||||
<default>Adwaita</default>
|
||||
<locale name="C">
|
||||
<short>Current theme</short>
|
||||
<long>
|
||||
The theme determines the appearance of window borders,
|
||||
titlebar, and so forth.
|
||||
|
||||
This key overrides /apps/metacity/general/theme when
|
||||
running GNOME Shell.
|
||||
</long>
|
||||
</locale>
|
||||
</schema>
|
||||
|
||||
</schemalist>
|
||||
|
||||
</gconfschemafile>
|
||||
|
85
data/gs-applications.menu
Normal file
@ -0,0 +1,85 @@
|
||||
<Menu>
|
||||
<DefaultLayout>
|
||||
<Menuname>Accessories</Menuname>
|
||||
<Menuname>Games</Menuname>
|
||||
<Menuname>Graphics</Menuname>
|
||||
<Menuname>Internet</Menuname>
|
||||
<Menuname>Multimedia</Menuname>
|
||||
<Menuname>Office</Menuname>
|
||||
<Menuname>Other</Menuname>
|
||||
</DefaultLayout>
|
||||
|
||||
<Name>Applications</Name>
|
||||
<AppDir>/usr/local/share/applications</AppDir>
|
||||
<DefaultAppDirs/>
|
||||
|
||||
<Menu>
|
||||
<Name>Accessories</Name>
|
||||
<Include>
|
||||
<And>
|
||||
<Category>Utility</Category>
|
||||
<Not>
|
||||
<Category>System</Category>
|
||||
</Not>
|
||||
</And>
|
||||
</Include>
|
||||
</Menu>
|
||||
|
||||
<Menu>
|
||||
<Name>Games</Name>
|
||||
<Include>
|
||||
<And>
|
||||
<Category>Game</Category>
|
||||
</And>
|
||||
</Include>
|
||||
</Menu>
|
||||
|
||||
<Menu>
|
||||
<Name>Graphics</Name>
|
||||
<Include>
|
||||
<And>
|
||||
<Category>Graphics</Category>
|
||||
</And>
|
||||
</Include>
|
||||
</Menu>
|
||||
|
||||
<Menu>
|
||||
<Name>Internet</Name>
|
||||
<Include>
|
||||
<And>
|
||||
<Category>Network</Category>
|
||||
<Not><Category>Settings</Category></Not>
|
||||
</And>
|
||||
</Include>
|
||||
</Menu>
|
||||
|
||||
<Menu>
|
||||
<Name>Multimedia</Name>
|
||||
<Include>
|
||||
<And>
|
||||
<Category>AudioVideo</Category>
|
||||
<Not><Category>Settings</Category></Not>
|
||||
</And>
|
||||
</Include>
|
||||
</Menu>
|
||||
|
||||
<Menu>
|
||||
<Name>Office</Name>
|
||||
<Include>
|
||||
<And>
|
||||
<Category>Office</Category>
|
||||
</And>
|
||||
</Include>
|
||||
</Menu>
|
||||
|
||||
<Menu>
|
||||
<Name>Other</Name>
|
||||
<OnlyUnallocated/>
|
||||
<Include>
|
||||
<And>
|
||||
<Not><Category>Settings</Category></Not>
|
||||
<Not><Category>Screensaver</Category></Not>
|
||||
</And>
|
||||
</Include>
|
||||
</Menu>
|
||||
</Menu>
|
133
data/org.gnome.accessibility.magnifier.gschema.xml.in
Normal file
@ -0,0 +1,133 @@
|
||||
<schemalist>
|
||||
|
||||
<enum id="MouseTrackingMode">
|
||||
<value nick="none" value="0"/>
|
||||
<value nick="centered" value="1"/>
|
||||
<value nick="proportional" value="2"/>
|
||||
<value nick="push" value="3"/>
|
||||
</enum>
|
||||
|
||||
<enum id="ScreenPosition">
|
||||
<value nick="none" value="0"/>
|
||||
<value nick="full-screen" value="1"/>
|
||||
<value nick="top-half" value="2"/>
|
||||
<value nick="bottom-half" value="3"/>
|
||||
<value nick="left-half" value="4"/>
|
||||
<value nick="right-half" value="5"/>
|
||||
</enum>
|
||||
|
||||
<schema id="org.gnome.accessibility.magnifier"
|
||||
path="/desktop/gnome/accessibility/magnifier/"
|
||||
gettext-domain="@GETTEXT_PACKAGE@">
|
||||
<key name="show-magnifier" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Show or hide the magnifier</_summary>
|
||||
<_description>
|
||||
Show or hide the magnifier and all of its zoom regions.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="mouse-tracking" enum="MouseTrackingMode">
|
||||
<default>'proportional'</default>
|
||||
<_summary>Mouse Tracking Mode</_summary>
|
||||
<_description>
|
||||
Determines the position of the magnified mouse image within the
|
||||
magnified view and how it reacts to system mouse movement. The values
|
||||
are
|
||||
- none: no mouse tracking;
|
||||
- centered: the mouse image is
|
||||
displayed at the center of the zoom region (which also represents
|
||||
the point under the system mouse) and the magnified contents are
|
||||
scrolled as the system mouse moves;
|
||||
- proportional: the position of the magnified mouse in the zoom region
|
||||
is proportionally the same as the position of the system mouse on screen;
|
||||
- push: when the magnified mouse intersects a boundary of the zoom
|
||||
region, the contents are scrolled into view.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="screen-position" enum="ScreenPosition">
|
||||
<default>'full-screen'</default>
|
||||
<_summary>Screen position</_summary>
|
||||
<_description>
|
||||
The magnified view either fills the entire screen, or occupies the
|
||||
top-half, bottom-half, left-half, or right-half of the screen.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="mag-factor" type="d">
|
||||
<default>2.0</default>
|
||||
<_summary>Magnification factor</_summary>
|
||||
<_description>
|
||||
The power of the magnification. A value of 1.0 means no magnification.
|
||||
A value of 2.0 doubles the size.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="lens-mode" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Enable lens mode</_summary>
|
||||
<_description>
|
||||
Whether the magnified view should be centered over the location of
|
||||
the system mouse and move with it.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="scroll-at-edges" type="b">
|
||||
<default>false</default>
|
||||
<_summary>
|
||||
Scroll magnified contents beyond the edges of the desktop
|
||||
</_summary>
|
||||
<_description>
|
||||
For centered mouse tracking, when the system pointer is at or near the
|
||||
edge of the screen, the magnified contents continue to scroll such that
|
||||
the screen edge moves into the magnified view.
|
||||
</_description>
|
||||
</key>
|
||||
|
||||
<!-- Cross-hairs -->
|
||||
<key name="show-cross-hairs" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Show or hide crosshairs</_summary>
|
||||
<_description>
|
||||
Enables/disables display of crosshairs centered on the magnified
|
||||
mouse sprite.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="cross-hairs-thickness" type="i">
|
||||
<default>8</default>
|
||||
<_summary>Thickness of the crosshairs</_summary>
|
||||
<_description>
|
||||
Width of the vertical and horizontal lines that make up the crosshairs.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="cross-hairs-color" type="s">
|
||||
<default>'#ff0000'</default>
|
||||
<_summary>Color of the crosshairs</_summary>
|
||||
<_description>
|
||||
The color of the the vertical and horizontal lines that make up
|
||||
the crosshairs.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="cross-hairs-opacity" type="i">
|
||||
<default>169</default>
|
||||
<_summary>Opacity of the crosshairs</_summary>
|
||||
<_description>
|
||||
Determines the transparency of the crosshairs, from fully opaque
|
||||
to fully transparent.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="cross-hairs-length" type="i">
|
||||
<default>4096</default>
|
||||
<_summary>Length of the crosshairs</_summary>
|
||||
<_description>
|
||||
Determines the length of the vertical and horizontal lines that
|
||||
make up the crosshairs.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="cross-hairs-clip" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Clip the crosshairs at the center</_summary>
|
||||
<_description>
|
||||
Determines whether the crosshairs intersect the magnified mouse sprite,
|
||||
or are clipped such that the ends of the horizontal and vertical lines
|
||||
surround the mouse image.
|
||||
</_description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
119
data/org.gnome.shell.gschema.xml.in
Normal file
@ -0,0 +1,119 @@
|
||||
<schemalist>
|
||||
<schema id="org.gnome.shell" path="/apps/gnome-shell/"
|
||||
gettext-domain="@GETTEXT_PACKAGE@">
|
||||
<key name="development-tools" type="b">
|
||||
<default>true</default>
|
||||
<_summary>
|
||||
Enable internal tools useful for developers and testers from Alt-F2
|
||||
</_summary>
|
||||
<_description>
|
||||
Allows access to internal debugging and monitoring tools
|
||||
using the Alt-F2 dialog.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="disabled-extensions" type="as">
|
||||
<default>[]</default>
|
||||
<_summary>Uuids of extensions to disable</_summary>
|
||||
<_description>
|
||||
GNOME Shell extensions have a uuid property;
|
||||
this key lists extensions which should not be loaded.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="enable-app-monitoring" type="b">
|
||||
<default>true</default>
|
||||
<_summary>Whether to collect stats about applications usage</_summary>
|
||||
<_description>
|
||||
The shell normally monitors active applications in order to present
|
||||
the most used ones (e.g. in launchers). While this data will be
|
||||
kept private, you may want to disable this for privacy reasons.
|
||||
Please note that doing so won't remove already saved data.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="favorite-apps" type="as">
|
||||
<default>[ 'mozilla-firefox.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'openoffice.org-writer.desktop', 'nautilus.desktop' ]</default>
|
||||
<_summary>List of desktop file IDs for favorite applications</_summary>
|
||||
<_description>
|
||||
The applications corresponding to these identifiers
|
||||
will be displayed in the favorites area.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="disabled-open-search-providers" type="as">
|
||||
<default>[]</default>
|
||||
<_summary>disabled OpenSearch providers</_summary>
|
||||
</key>
|
||||
<key name="command-history" type="as">
|
||||
<default>[]</default>
|
||||
<_summary>History for command (Alt-F2) dialog</_summary>
|
||||
</key>
|
||||
<child name="clock" schema="org.gnome.shell.clock"/>
|
||||
<child name="calendar" schema="org.gnome.shell.calendar"/>
|
||||
<child name="recorder" schema="org.gnome.shell.recorder"/>
|
||||
</schema>
|
||||
|
||||
<schema id="org.gnome.shell.calendar" path="/apps/gnome-shell/calendar/"
|
||||
gettext-domain="@GETTEXT_PACKAGE@">
|
||||
<key name="show-weekdate" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Show the week date in the calendar</_summary>
|
||||
<_description>
|
||||
If true, display the ISO week date in the calendar.
|
||||
</_description>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<schema id="org.gnome.shell.clock" path="/apps/gnome-shell/clock/"
|
||||
gettext-domain="@GETTEXT_PACKAGE@">
|
||||
<key name="show-seconds" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Show time with seconds</_summary>
|
||||
<_description>
|
||||
If true, display seconds in time.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="show-date" type="b">
|
||||
<default>false</default>
|
||||
<_summary>Show date in clock</_summary>
|
||||
<_description>
|
||||
If true, display date in the clock, in addition to time.
|
||||
</_description>
|
||||
</key>
|
||||
</schema>
|
||||
|
||||
<schema id="org.gnome.shell.recorder" path="/apps/gnome-shell/recorder/"
|
||||
gettext-domain="@GETTEXT_PACKAGE@">
|
||||
<key name="framerate" type="i">
|
||||
<default>15</default>
|
||||
<_summary>Framerate used for recording screencasts.</_summary>
|
||||
<_description>
|
||||
The framerate of the resulting screencast recordered
|
||||
by GNOME Shell's screencast recorder in frames-per-second.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="pipeline" type="s">
|
||||
<default>''</default>
|
||||
<_summary>The gstreamer pipeline used to encode the screencast</_summary>
|
||||
<_description>
|
||||
Sets the GStreamer pipeline used to encode recordings.
|
||||
It follows the syntax used for gst-launch. The pipeline should have
|
||||
an unconnected sink pad where the recorded video is recorded. It will
|
||||
normally have a unconnected source pad; output from that pad
|
||||
will be written into the output file. However the pipeline can also
|
||||
take care of its own output - this might be used to send the output
|
||||
to an icecast server via shout2send or similar. When unset or set
|
||||
to an empty value, the default pipeline will be used. This is currently
|
||||
'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux'
|
||||
and records to WEBM using the VP8 codec. %T is used as a placeholder
|
||||
for a guess at the optimal thread count on the system.
|
||||
</_description>
|
||||
</key>
|
||||
<key name="file-extension" type="s">
|
||||
<default>'webm'</default>
|
||||
<_summary>File extension used for storing the screencast</_summary>
|
||||
<_description>
|
||||
The filename for recorded screencasts will be a unique filename
|
||||
based on the current date, and use this extension. It should be
|
||||
changed when recording to a different container format.
|
||||
</_description>
|
||||
</key>
|
||||
</schema>
|
||||
</schemalist>
|
@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="59.995201"
|
||||
height="59.995102"
|
||||
id="svg3113"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.46"
|
||||
version="1.0"
|
||||
sodipodi:docname="remove-workspace.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
<defs
|
||||
id="defs3115">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective3121" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
gridtolerance="10000"
|
||||
guidetolerance="10"
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4.5"
|
||||
inkscape:cx="-8.1974244"
|
||||
inkscape:cy="38.948933"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1400"
|
||||
inkscape:window-height="971"
|
||||
inkscape:window-x="454"
|
||||
inkscape:window-y="105" />
|
||||
<metadata
|
||||
id="metadata3118">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-498.57383,-439.50749)">
|
||||
<path
|
||||
style="opacity:0.30701785;fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:0.807603px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;display:inline"
|
||||
d="M 30 0.40625 C 13.662899 0.40624999 0.40625 13.66291 0.40625 30 C 0.40624999 46.337101 13.6629 59.59377 30 59.59375 C 46.337099 59.593749 59.59365 46.33711 59.59375 30 C 59.59375 13.662901 46.3371 0.40626 30 0.40625 z M 15.03125 23.34375 L 44.96875 23.34375 C 46.83825 23.343751 48.34375 24.84905 48.34375 26.71875 L 48.34375 33.3125 C 48.34375 35.182239 46.83845 36.68751 44.96875 36.6875 L 15.03125 36.6875 C 13.16175 36.687529 11.65635 35.18222 11.65625 33.3125 L 11.65625 26.71875 C 11.65625 24.849031 13.16165 23.34376 15.03125 23.34375 z "
|
||||
transform="translate(498.57383,439.50749)"
|
||||
id="path2382" />
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 2.8 KiB |
7
data/search_providers/google.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
||||
<ShortName>Google</ShortName>
|
||||
<Description>Google Search</Description>
|
||||
<InputEncoding>UTF-8</InputEncoding>
|
||||
<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAGABoAwAAFgAAACgAAAAQAAAAIAAAAAEAGAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADs9Pt8xetPtu9FsfFNtu%2BTzvb2%2B%2Fne4dFJeBw0egA%2FfAJAfAA8ewBBegAAAAD%2B%2FPtft98Mp%2BwWsfAVsvEbs%2FQeqvF8xO7%2F%2F%2F63yqkxdgM7gwE%2FggM%2BfQA%2BegBDeQDe7PIbotgQufcMufEPtfIPsvAbs%2FQvq%2Bfz%2Bf%2F%2B%2B%2FZKhR05hgBBhQI8hgBAgAI9ewD0%2B%2Fg3pswAtO8Cxf4Kw%2FsJvvYAqupKsNv%2B%2Fv7%2F%2FP5VkSU0iQA7jQA9hgBDgQU%2BfQH%2F%2Ff%2FQ6fM4sM4KsN8AteMCruIqqdbZ7PH8%2Fv%2Fg6Nc%2Fhg05kAA8jAM9iQI%2BhQA%2BgQDQu6b97uv%2F%2F%2F7V8Pqw3eiWz97q8%2Ff%2F%2F%2F%2F7%2FPptpkkqjQE4kwA7kAA5iwI8iAA8hQCOSSKdXjiyflbAkG7u2s%2F%2B%2F%2F39%2F%2F7r8utrqEYtjQE8lgA7kwA7kwA9jwA9igA9hACiWSekVRyeSgiYSBHx6N%2F%2B%2Fv7k7OFRmiYtlAA5lwI7lwI4lAA7kgI9jwE9iwI4iQCoVhWcTxCmb0K%2BooT8%2Fv%2F7%2F%2F%2FJ2r8fdwI1mwA3mQA3mgA8lAE8lAE4jwA9iwE%2BhwGfXifWvqz%2B%2Ff%2F58u%2Fev6Dt4tr%2B%2F%2F2ZuIUsggA7mgM6mAM3lgA5lgA6kQE%2FkwBChwHt4dv%2F%2F%2F728ei1bCi7VAC5XQ7kz7n%2F%2F%2F6bsZkgcB03lQA9lgM7kwA2iQktZToPK4r9%2F%2F%2F9%2F%2F%2FSqYK5UwDKZAS9WALIkFn%2B%2F%2F3%2F%2BP8oKccGGcIRJrERILYFEMwAAuEAAdX%2F%2Ff7%2F%2FP%2B%2BfDvGXQLIZgLEWgLOjlf7%2F%2F%2F%2F%2F%2F9QU90EAPQAAf8DAP0AAfMAAOUDAtr%2F%2F%2F%2F7%2B%2Fu2bCTIYwDPZgDBWQDSr4P%2F%2Fv%2F%2F%2FP5GRuABAPkAA%2FwBAfkDAPAAAesAAN%2F%2F%2B%2Fz%2F%2F%2F64g1C5VwDMYwK8Yg7y5tz8%2Fv%2FV1PYKDOcAAP0DAf4AAf0AAfYEAOwAAuAAAAD%2F%2FPvi28ymXyChTATRrIb8%2F%2F3v8fk6P8MAAdUCAvoAAP0CAP0AAfYAAO4AAACAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAA</Image>
|
||||
<Url type="text/html" method="GET" template="http://www.google.com/search?q={searchTerms}"/>
|
||||
</OpenSearchDescription>
|
44
data/search_providers/wikipedia.xml
Normal file
@ -0,0 +1,44 @@
|
||||
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
|
||||
<ShortName>Wikipedia</ShortName>
|
||||
<Description>Wikipedia, the free encyclopedia</Description>
|
||||
<InputEncoding>UTF-8</InputEncoding>
|
||||
<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAAAEABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAEAgQAhIOEAMjHyABIR0gA6ejpAGlqaQCpqKkAKCgoAPz9%2FAAZGBkAmJiYANjZ2ABXWFcAent6ALm6uQA8OjwAiIiIiIiIiIiIiI4oiL6IiIiIgzuIV4iIiIhndo53KIiIiB%2FWvXoYiIiIfEZfWBSIiIEGi%2FfoqoiIgzuL84i9iIjpGIoMiEHoiMkos3FojmiLlUipYliEWIF%2BiDe0GoRa7D6GPbjcu1yIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
|
||||
<Url type="text/html" method="GET" template="http://{language}.wikipedia.org/wiki/Special:Search?search={searchTerms}"/>
|
||||
<!-- The criterion for being below is being listed with more than 100,000
|
||||
articles on http://meta.wikimedia.org/wiki/List_of_Wikipedias -->
|
||||
<Language>ar</Language>
|
||||
<Language>bg</Language>
|
||||
<Language>ca</Language>
|
||||
<Language>cs</Language>
|
||||
<Language>da</Language>
|
||||
<Language>de</Language>
|
||||
<Language>en</Language>
|
||||
<Language>eo</Language>
|
||||
<Language>es</Language>
|
||||
<Language>fa</Language>
|
||||
<Language>fi</Language>
|
||||
<Language>fr</Language>
|
||||
<Language>he</Language>
|
||||
<Language>hu</Language>
|
||||
<Language>id</Language>
|
||||
<Language>it</Language>
|
||||
<Language>ja</Language>
|
||||
<Language>ko</Language>
|
||||
<Language>lt</Language>
|
||||
<Language>nl</Language>
|
||||
<Language>no</Language>
|
||||
<Language>pl</Language>
|
||||
<Language>pt</Language>
|
||||
<Language>ro</Language>
|
||||
<Language>ru</Language>
|
||||
<Language>sk</Language>
|
||||
<Language>sl</Language>
|
||||
<Language>sr</Language>
|
||||
<Language>sv</Language>
|
||||
<Language>tr</Language>
|
||||
<Language>uk</Language>
|
||||
<Language>vi</Language>
|
||||
<Language>vo</Language>
|
||||
<Language>war</Language>
|
||||
<Language>zh</Language>
|
||||
</OpenSearchDescription>
|
26
data/shaders/dim-window.glsl
Normal file
@ -0,0 +1,26 @@
|
||||
#version 110
|
||||
uniform sampler2D sampler0;
|
||||
uniform float fraction;
|
||||
uniform float height;
|
||||
const float c = -0.2;
|
||||
const float border_max_height = 60.0;
|
||||
|
||||
mat4 contrast = mat4 (1.0 + c, 0.0, 0.0, 0.0,
|
||||
0.0, 1.0 + c, 0.0, 0.0,
|
||||
0.0, 0.0, 1.0 + c, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0);
|
||||
vec4 off = vec4(0.633, 0.633, 0.633, 0);
|
||||
void main()
|
||||
{
|
||||
vec4 color = texture2D(sampler0, gl_TexCoord[0].st);
|
||||
float y = height * gl_TexCoord[0][1];
|
||||
|
||||
// To reduce contrast, blend with a mid gray
|
||||
gl_FragColor = color * contrast - off * c;
|
||||
|
||||
// We only fully dim at a distance of BORDER_MAX_HEIGHT from the edge and
|
||||
// when the fraction is 1.0. For other locations and fractions we linearly
|
||||
// interpolate back to the original undimmed color.
|
||||
gl_FragColor = color + (gl_FragColor - color) * min(y / border_max_height, 1.0);
|
||||
gl_FragColor = color + (gl_FragColor - color) * fraction;
|
||||
}
|
82
data/theme/calendar-arrow-left.svg
Normal file
@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48+devel r9942 custom"
|
||||
sodipodi:docname="New document 4">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="8.984481"
|
||||
inkscape:cy="5.6224906"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
borderlayer="true"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="930"
|
||||
inkscape:window-height="681"
|
||||
inkscape:window-x="1892"
|
||||
inkscape:window-y="272"
|
||||
inkscape:window-maximized="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid17403"
|
||||
empspacing="5"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1036.3622)">
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#5f5f5f;fill-opacity:1;stroke:#5f5f5f;stroke-width:0.43015847;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
id="path18028"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="84.5"
|
||||
sodipodi:cy="337.5"
|
||||
sodipodi:r1="5"
|
||||
sodipodi:r2="2.5"
|
||||
sodipodi:arg1="0.52359878"
|
||||
sodipodi:arg2="1.5707963"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="M 88.830127,340 80.169873,340 84.5,332.5 z"
|
||||
transform="matrix(0,1.3621708,0.99186247,0,-325.48222,929.32667)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
82
data/theme/calendar-arrow-right.svg
Normal file
@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.48+devel r9942 custom"
|
||||
sodipodi:docname="arrow-left.svg">
|
||||
<defs
|
||||
id="defs4" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="7.7366092"
|
||||
inkscape:cy="6.4536271"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
borderlayer="true"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="930"
|
||||
inkscape:window-height="681"
|
||||
inkscape:window-x="1892"
|
||||
inkscape:window-y="272"
|
||||
inkscape:window-maximized="0">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid17403"
|
||||
empspacing="5"
|
||||
visible="true"
|
||||
enabled="true"
|
||||
snapvisiblegridlinesonly="true" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-1036.3622)">
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="fill:#5f5f5f;fill-opacity:1;stroke:#5f5f5f;stroke-width:0.43015847;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
id="path18028"
|
||||
sodipodi:sides="3"
|
||||
sodipodi:cx="84.5"
|
||||
sodipodi:cy="337.5"
|
||||
sodipodi:r1="5"
|
||||
sodipodi:r2="2.5"
|
||||
sodipodi:arg1="0.52359878"
|
||||
sodipodi:arg2="1.5707963"
|
||||
inkscape:flatsided="true"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="M 88.830127,340 80.169873,340 84.5,332.5 z"
|
||||
transform="matrix(0,1.3621708,-0.99186247,0,342.48324,929.32667)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
BIN
data/theme/corner-ripple.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
84
data/theme/dash-placeholder.svg
Normal file
@ -0,0 +1,84 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="76"
|
||||
height="27"
|
||||
id="svg11252"
|
||||
version="1.1">
|
||||
<defs
|
||||
id="defs11254">
|
||||
<radialGradient
|
||||
xlink:href="#linearGradient39563-4-2"
|
||||
id="radialGradient68155-2-3"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1,0,0,0.3486842,0,317.8421)"
|
||||
cx="49"
|
||||
cy="488"
|
||||
fx="49"
|
||||
fy="488"
|
||||
r="38" />
|
||||
<linearGradient
|
||||
id="linearGradient39563-4-2">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop39565-1-4" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop39567-7-9" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
xlink:href="#linearGradient39573-6-1"
|
||||
id="radialGradient68157-0-8"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
cx="50.5"
|
||||
cy="487.5"
|
||||
fx="50.5"
|
||||
fy="487.5"
|
||||
r="10.5" />
|
||||
<linearGradient
|
||||
id="linearGradient39573-6-1">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop39575-5-6" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop39577-1-2" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-337,-518.86218)">
|
||||
<g
|
||||
id="g99967"
|
||||
style="display:inline"
|
||||
transform="translate(326,44.862171)">
|
||||
<rect
|
||||
style="opacity:0.49375;color:#000000;fill:url(#radialGradient68155-2-3);fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect99969"
|
||||
width="76"
|
||||
height="2"
|
||||
x="11"
|
||||
y="487"
|
||||
rx="0"
|
||||
ry="0" />
|
||||
<path
|
||||
style="opacity:0.43125;color:#000000;fill:url(#radialGradient68157-0-8);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path99971"
|
||||
d="M 61,487.5 C 61,493.29899 56.29899,498 50.5,498 44.70101,498 40,493.29899 40,487.5 40,481.70101 44.70101,477 50.5,477 c 5.79899,0 10.5,4.70101 10.5,10.5 z"
|
||||
transform="matrix(1.2857143,0,0,1.2857143,-14.428572,-139.28571)" />
|
||||
<path
|
||||
transform="matrix(0.43589747,0,0,0.43589747,28.487179,275)"
|
||||
d="M 61,487.5 C 61,493.29899 56.29899,498 50.5,498 44.70101,498 40,493.29899 40,487.5 40,481.70101 44.70101,477 50.5,477 c 5.79899,0 10.5,4.70101 10.5,10.5 z"
|
||||
id="path99973"
|
||||
style="color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
81
data/theme/filter-selected.svg
Normal file
@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="10"
|
||||
height="20"
|
||||
id="svg10003"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="filter-selected.svg">
|
||||
<defs
|
||||
id="defs10005">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 32 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="64 : 32 : 1"
|
||||
inkscape:persp3d-origin="32 : 21.333333 : 1"
|
||||
id="perspective10011" />
|
||||
<inkscape:perspective
|
||||
id="perspective9998"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.5"
|
||||
inkscape:cx="32"
|
||||
inkscape:cy="10.181818"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="994"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata10008">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,-44)">
|
||||
<path
|
||||
inkscape:export-ydpi="90"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-filename="/home/jimmac/src/cvs/gnome/gnome-shell-design/mockups/app-picker.png"
|
||||
sodipodi:nodetypes="cccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="rect34320"
|
||||
d="m -0.18726572,54.181804 10.55634072,10.55636 10e-6,-21.11269 z"
|
||||
style="opacity:0.21000001;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.99999988;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
113
data/theme/mosaic-view-active.svg
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="16"
|
||||
id="svg6503"
|
||||
version="1.1"
|
||||
inkscape:version="0.47pre4 r22446"
|
||||
sodipodi:docname="mosaic-view-active.svg">
|
||||
<defs
|
||||
id="defs6505">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 16 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="32 : 16 : 1"
|
||||
inkscape:persp3d-origin="16 : 10.666667 : 1"
|
||||
id="perspective6511" />
|
||||
<inkscape:perspective
|
||||
id="perspective6494"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.197802"
|
||||
inkscape:cx="-15.97056"
|
||||
inkscape:cy="16"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="997"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata6508">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,-16)">
|
||||
<g
|
||||
style="display:inline;fill:#cbcbcb;fill-opacity:1"
|
||||
transform="translate(-449.85476,-685.85869)"
|
||||
id="g5306">
|
||||
<rect
|
||||
style="fill:#cbcbcb;fill-opacity:1;stroke:#000000;stroke-width:0.99999970000000005;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.44262299999999999;stroke-dasharray:none"
|
||||
id="rect5308"
|
||||
width="11"
|
||||
height="7"
|
||||
x="450.5"
|
||||
y="710.5"
|
||||
rx="0.99999958"
|
||||
ry="1" />
|
||||
<rect
|
||||
style="fill:#cbcbcb;fill-opacity:1;stroke:#000000;stroke-width:0.99999970000000005;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.44262299999999999;stroke-dasharray:none;display:inline"
|
||||
id="rect5310"
|
||||
width="11"
|
||||
height="7"
|
||||
x="462.5"
|
||||
y="702.5"
|
||||
rx="0.99999958"
|
||||
ry="1" />
|
||||
<rect
|
||||
style="fill:#cbcbcb;fill-opacity:1;stroke:#000000;stroke-width:0.99999976000000002;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.44262299999999999;stroke-dasharray:none;display:inline"
|
||||
id="rect5312"
|
||||
width="11"
|
||||
height="7"
|
||||
x="450.5"
|
||||
y="702.5"
|
||||
rx="0.99999958"
|
||||
ry="1" />
|
||||
<rect
|
||||
style="fill:#cbcbcb;fill-opacity:1;stroke:#000000;stroke-width:0.99999970000000005;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.44262299999999999;stroke-dasharray:none;display:inline"
|
||||
id="rect5314"
|
||||
width="11"
|
||||
height="7"
|
||||
x="462.5"
|
||||
y="710.5"
|
||||
rx="0.99999958"
|
||||
ry="1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.7 KiB |
113
data/theme/mosaic-view.svg
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="16"
|
||||
id="svg6503"
|
||||
version="1.1"
|
||||
inkscape:version="0.47pre4 r22446"
|
||||
sodipodi:docname="New document 19">
|
||||
<defs
|
||||
id="defs6505">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 16 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="32 : 16 : 1"
|
||||
inkscape:persp3d-origin="16 : 10.666667 : 1"
|
||||
id="perspective6511" />
|
||||
<inkscape:perspective
|
||||
id="perspective6494"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.197802"
|
||||
inkscape:cx="16"
|
||||
inkscape:cy="16"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="997"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata6508">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,-16)">
|
||||
<g
|
||||
style="display:inline"
|
||||
transform="translate(-449.85476,-685.85869)"
|
||||
id="g5306">
|
||||
<rect
|
||||
style="fill:#666666;fill-opacity:1;stroke:#000000;stroke-width:0.9999997;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.442623;stroke-dasharray:none"
|
||||
id="rect5308"
|
||||
width="11"
|
||||
height="7"
|
||||
x="450.5"
|
||||
y="710.5"
|
||||
rx="0.99999958"
|
||||
ry="1" />
|
||||
<rect
|
||||
style="fill:#666666;fill-opacity:1;stroke:#000000;stroke-width:0.9999997;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.442623;stroke-dasharray:none;display:inline"
|
||||
id="rect5310"
|
||||
width="11"
|
||||
height="7"
|
||||
x="462.5"
|
||||
y="702.5"
|
||||
rx="0.99999958"
|
||||
ry="1" />
|
||||
<rect
|
||||
style="fill:#666666;fill-opacity:1;stroke:#000000;stroke-width:0.99999976;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.442623;stroke-dasharray:none;display:inline"
|
||||
id="rect5312"
|
||||
width="11"
|
||||
height="7"
|
||||
x="450.5"
|
||||
y="702.5"
|
||||
rx="0.99999958"
|
||||
ry="1" />
|
||||
<rect
|
||||
style="fill:#666666;fill-opacity:1;stroke:#000000;stroke-width:0.9999997;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:0.442623;stroke-dasharray:none;display:inline"
|
||||
id="rect5314"
|
||||
width="11"
|
||||
height="7"
|
||||
x="462.5"
|
||||
y="710.5"
|
||||
rx="0.99999958"
|
||||
ry="1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.6 KiB |
89
data/theme/move-window-on-new.svg
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="98"
|
||||
height="98"
|
||||
id="svg6375"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="add-workspace.svg">
|
||||
<defs
|
||||
id="defs6377">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 16 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="32 : 16 : 1"
|
||||
inkscape:persp3d-origin="16 : 10.666667 : 1"
|
||||
id="perspective6383" />
|
||||
<inkscape:perspective
|
||||
id="perspective6366"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.9590209"
|
||||
inkscape:cx="56.650687"
|
||||
inkscape:cy="20.635343"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="997"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata6380">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,66)">
|
||||
<g
|
||||
id="g2824"
|
||||
transform="matrix(11.568551,0,0,11.698271,-78.828159,-304.81518)">
|
||||
<path
|
||||
style="fill:none;stroke:#666666;stroke-width:1.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
|
||||
d="m 11.07363,21.36834 0,6.43903"
|
||||
id="path5322" />
|
||||
<path
|
||||
style="fill:none;stroke:#666666;stroke-width:1.99999952;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
d="m 14.29314,24.58786 -6.43902,0"
|
||||
id="path5324" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:#000000;fill-opacity:0.98823529"
|
||||
d="m 48.239516,97.908047 c -0.41677,-0.05102 -1.269253,-0.222408 -1.894408,-0.380859 -4.088493,-1.036262 -7.520781,-4.753234 -8.330163,-9.021094 -0.154947,-0.817026 -0.257819,-6.68112 -0.257819,-14.696556 l 0,-13.337088 -13.829177,-0.08909 C 10.802042,60.298796 10.026884,60.268266 8.6851548,59.783022 3.6288503,57.954375 0.62673331,53.828648 0.62673331,48.708554 c 0,-5.625522 4.25936019,-10.425065 9.97721469,-11.242548 0.987903,-0.141242 7.368912,-0.254994 14.460646,-0.257791 l 12.692532,-0.005 0,-13.586668 c 0,-14.6441583 0.03287,-15.0698926 1.364686,-17.6753047 2.185477,-4.2754229 6.938193,-6.75739913 11.687647,-6.10355607 3.382776,0.46569661 6.737962,2.72496967 8.414081,5.66577137 1.480816,2.5981315 1.519067,3.0522448 1.519067,18.0333334 l 0,13.666424 12.692533,0.005 c 7.091733,0.0028 13.472742,0.116549 14.460646,0.257791 6.395303,0.914337 10.804785,6.623716 9.941157,12.871766 -0.698243,5.051565 -4.792685,9.104635 -9.941157,9.840713 -0.987904,0.141242 -7.368913,0.254995 -14.460646,0.257791 l -12.692533,0.005 0,13.801945 c 0,13.031417 -0.02798,13.895893 -0.501177,15.484801 -1.526902,5.127058 -6.919246,8.802262 -12.001914,8.18002 z"
|
||||
id="path2828"
|
||||
transform="translate(0,-66)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.0 KiB |
BIN
data/theme/process-working.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
89
data/theme/running-indicator.svg
Normal file
@ -0,0 +1,89 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
width="74.01342"
|
||||
height="74.006706"
|
||||
id="svg7355"
|
||||
version="1.1">
|
||||
<defs
|
||||
id="defs7357">
|
||||
<radialGradient
|
||||
xlink:href="#linearGradient36429"
|
||||
id="radialGradient7461"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0525552,0,0,1.0525552,-2.5162753,-9.0000838)"
|
||||
cx="47.878681"
|
||||
cy="171.25"
|
||||
fx="47.878681"
|
||||
fy="171.25"
|
||||
r="37" />
|
||||
<linearGradient
|
||||
id="linearGradient36429">
|
||||
<stop
|
||||
id="stop36431"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop36433"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
xlink:href="#linearGradient36471"
|
||||
id="radialGradient7463"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.1891549,0,0,0.55513246,-9.281289,36.12653)"
|
||||
cx="49.067139"
|
||||
cy="242.50381"
|
||||
fx="49.067139"
|
||||
fy="242.50381"
|
||||
r="37.00671" />
|
||||
<linearGradient
|
||||
id="linearGradient36471">
|
||||
<stop
|
||||
id="stop36473"
|
||||
offset="0"
|
||||
style="stop-color:#ffffff;stop-opacity:1;" />
|
||||
<stop
|
||||
id="stop36475"
|
||||
offset="1"
|
||||
style="stop-color:#ffffff;stop-opacity:0;" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
r="37.00671"
|
||||
fy="242.50381"
|
||||
fx="49.067139"
|
||||
cy="242.50381"
|
||||
cx="49.067139"
|
||||
gradientTransform="matrix(1.1891549,0,0,0.55513246,-9.281289,36.12653)"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
id="radialGradient7488"
|
||||
xlink:href="#linearGradient36471" />
|
||||
</defs>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-266.21629,-168.11809)">
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g30864"
|
||||
transform="translate(255.223,70.118091)">
|
||||
<rect
|
||||
ry="3.5996203"
|
||||
rx="3.5996203"
|
||||
y="98"
|
||||
x="11"
|
||||
height="74"
|
||||
width="74"
|
||||
id="rect14000"
|
||||
style="opacity:0.371875;fill:url(#radialGradient7461);fill-opacity:1;stroke:none" />
|
||||
<path
|
||||
id="rect34520"
|
||||
d="m 84.506708,167.95508 c 6e-6,1.96759 -1.584022,3.55162 -3.551629,3.55163 l -65.910146,0 c -1.967608,-1e-5 -3.551648,-1.58402 -3.551643,-3.55164"
|
||||
style="opacity:0.2;fill:none;stroke:url(#radialGradient7488);stroke-width:1;stroke-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
64
data/theme/scroll-hhandle.svg
Normal file
@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="10"
|
||||
height="4"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="scroll-hhandle.svg">
|
||||
<defs
|
||||
id="defs4">
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
id="rect3592"
|
||||
width="2"
|
||||
height="4"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="0"
|
||||
ry="0" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#rect3592"
|
||||
id="use2825"
|
||||
transform="translate(8,0)"
|
||||
width="10"
|
||||
height="4" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#use2825"
|
||||
id="use2827"
|
||||
transform="translate(-4,0)"
|
||||
width="10"
|
||||
height="4" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 323 B |
62
data/theme/scroll-vhandle.svg
Normal file
@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="4"
|
||||
height="10"
|
||||
id="svg2"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="scroll-hhandle.svg">
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#323232;fill-opacity:1;fill-rule:evenodd;stroke:none"
|
||||
id="rect3592"
|
||||
width="2"
|
||||
height="4"
|
||||
x="0"
|
||||
y="-4"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="matrix(0,1,-1,0,0,0)" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#rect3592"
|
||||
id="use3705"
|
||||
transform="translate(0,4)"
|
||||
width="4"
|
||||
height="10" />
|
||||
<use
|
||||
x="0"
|
||||
y="0"
|
||||
xlink:href="#use3705"
|
||||
id="use3707"
|
||||
transform="translate(0,4)"
|
||||
width="4"
|
||||
height="10" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 13.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.0" id="Foreground" x="0px" y="0px" width="12" height="16" viewBox="0 0 12 16" enable-background="new 0 0 29 18" xml:space="preserve" sodipodi:version="0.32" inkscape:version="0.46+devel" sodipodi:docname="back.svg" inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata id="metadata16"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><defs id="defs14"><inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:vp_x="0 : 9 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="29 : 9 : 1" inkscape:persp3d-origin="14.5 : 6 : 1" id="perspective18"/></defs><sodipodi:namedview inkscape:window-height="728" inkscape:window-width="1103" inkscape:pageshadow="2" inkscape:pageopacity="1" guidetolerance="10.0" gridtolerance="10.0" objecttolerance="10.0" borderopacity="1.0" bordercolor="#666666" pagecolor="#000000" id="base" showgrid="true" inkscape:zoom="27.260185" inkscape:cx="12.592456" inkscape:cy="8.2696842" inkscape:window-x="145" inkscape:window-y="38" inkscape:current-layer="Foreground" inkscape:snap-global="true" showguides="false"><inkscape:grid type="xygrid" id="grid2391" empspacing="5" visible="true" enabled="true" snapvisiblegridlinesonly="true"/></sodipodi:namedview>
|
||||
|
||||
|
||||
|
||||
<path style="fill: rgb(255, 255, 255); fill-opacity: 1; stroke: none;" d="M 10,2 10,14 2,8 10,2 z" id="path43"/></svg>
|
Before Width: | Height: | Size: 1.9 KiB |
87
data/theme/section-more-open.svg
Normal file
@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="5.8600588"
|
||||
height="9"
|
||||
id="svg3647"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="section-more.svg">
|
||||
<defs
|
||||
id="defs3649">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective3655" />
|
||||
<inkscape:perspective
|
||||
id="perspective3603"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="82.777778"
|
||||
inkscape:cx="2.9300294"
|
||||
inkscape:cy="5.466443"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="997"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata3652">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-262.78425,-490.71933)">
|
||||
<path
|
||||
transform="matrix(0,-0.98149546,0.71467449,0,25.404986,578.15569)"
|
||||
d="M 88.830127,340 80.169873,340 84.5,332.5 88.830127,340 z"
|
||||
inkscape:randomized="0"
|
||||
inkscape:rounded="0"
|
||||
inkscape:flatsided="true"
|
||||
sodipodi:arg2="1.5707963"
|
||||
sodipodi:arg1="0.52359878"
|
||||
sodipodi:r2="2.5"
|
||||
sodipodi:r1="5"
|
||||
sodipodi:cy="337.5"
|
||||
sodipodi:cx="84.5"
|
||||
sodipodi:sides="3"
|
||||
id="path5497-5"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.59699643;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
|
||||
sodipodi:type="star" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
BIN
data/theme/separator-white.png
Normal file
After Width: | Height: | Size: 531 B |
81
data/theme/single-view-active.svg
Normal file
@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="16"
|
||||
id="svg6446"
|
||||
version="1.1"
|
||||
inkscape:version="0.47pre4 r22446"
|
||||
sodipodi:docname="single-view-active.svg">
|
||||
<defs
|
||||
id="defs6448">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 16 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="32 : 16 : 1"
|
||||
inkscape:persp3d-origin="16 : 10.666667 : 1"
|
||||
id="perspective6454" />
|
||||
<inkscape:perspective
|
||||
id="perspective6441"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.197802"
|
||||
inkscape:cx="0.014720032"
|
||||
inkscape:cy="16"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="997"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata6451">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,-17)">
|
||||
<rect
|
||||
ry="0.5"
|
||||
rx="0.49999979"
|
||||
y="17.483809"
|
||||
x="0.53483802"
|
||||
height="15"
|
||||
width="23"
|
||||
id="rect5304"
|
||||
style="fill:#cccccc;fill-opacity:1;stroke:#cccccc;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
81
data/theme/single-view.svg
Normal file
@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="16"
|
||||
id="svg6446"
|
||||
version="1.1"
|
||||
inkscape:version="0.47pre4 r22446"
|
||||
sodipodi:docname="single-view.svg">
|
||||
<defs
|
||||
id="defs6448">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 16 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="32 : 16 : 1"
|
||||
inkscape:persp3d-origin="16 : 10.666667 : 1"
|
||||
id="perspective6454" />
|
||||
<inkscape:perspective
|
||||
id="perspective6441"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.197802"
|
||||
inkscape:cx="0.014720032"
|
||||
inkscape:cy="16"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
inkscape:window-width="1680"
|
||||
inkscape:window-height="997"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata6451">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
transform="translate(0,-17)">
|
||||
<rect
|
||||
ry="0.5"
|
||||
rx="0.49999979"
|
||||
y="17.483809"
|
||||
x="0.53483802"
|
||||
height="15"
|
||||
width="23"
|
||||
id="rect5304"
|
||||
style="fill:#626262;fill-opacity:1;stroke:#cccccc;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
126
data/theme/toggle-off-intl.svg
Normal file
@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="22"
|
||||
id="svg3273"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="New document 14">
|
||||
<defs
|
||||
id="defs3275">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective3281" />
|
||||
<inkscape:perspective
|
||||
id="perspective3261"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="32.000004"
|
||||
inkscape:cy="10.999997"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="609"
|
||||
inkscape:window-height="501"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata3278">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-343,-521.36218)">
|
||||
<g
|
||||
id="g17454"
|
||||
transform="translate(-453,448.36218)"
|
||||
style="display:inline">
|
||||
<rect
|
||||
transform="scale(-1,1)"
|
||||
ry="4"
|
||||
rx="4"
|
||||
y="74.5"
|
||||
x="-859.5"
|
||||
height="19"
|
||||
width="63.000004"
|
||||
id="rect17456"
|
||||
style="color:#000000;fill:none;stroke:#2e3436;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect
|
||||
transform="scale(-1,1)"
|
||||
ry="4"
|
||||
rx="4"
|
||||
y="74"
|
||||
x="-828"
|
||||
height="20"
|
||||
width="31"
|
||||
id="rect17458"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#5f5f5f;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline" />
|
||||
<g
|
||||
transform="matrix(-1,0,0,1,1619.1239,-33.986291)"
|
||||
id="g17460"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 803.6322,115.48629 0,4.29495"
|
||||
id="path17462"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;display:inline"
|
||||
d="m 806.62805,115.48629 0,4.29495"
|
||||
id="path17464"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;display:inline"
|
||||
d="m 809.6239,115.48629 0,4.29495"
|
||||
id="path17466"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="color:#000000;fill:none;stroke:#ffffff;stroke-width:1.96875012;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path18722"
|
||||
sodipodi:cx="47.6875"
|
||||
sodipodi:cy="11.5625"
|
||||
sodipodi:rx="3.9375"
|
||||
sodipodi:ry="3.9375"
|
||||
d="m 51.625,11.5625 c 0,2.174621 -1.762879,3.9375 -3.9375,3.9375 -2.174621,0 -3.9375,-1.762879 -3.9375,-3.9375 0,-2.1746212 1.762879,-3.9375 3.9375,-3.9375 2.174621,0 3.9375,1.7628788 3.9375,3.9375 z"
|
||||
transform="matrix(1.0158729,0,0,1.0158729,795.55556,72.25399)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.7 KiB |
138
data/theme/toggle-off-us.svg
Normal file
@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="64"
|
||||
height="22"
|
||||
id="svg3012"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="New document 6">
|
||||
<defs
|
||||
id="defs3014">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective3020" />
|
||||
<inkscape:perspective
|
||||
id="perspective2997"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="32.000004"
|
||||
inkscape:cy="10.999997"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="609"
|
||||
inkscape:window-height="501"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata3017">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-343,-521.36218)">
|
||||
<g
|
||||
id="g17454"
|
||||
transform="translate(-453,448.36218)"
|
||||
style="display:inline">
|
||||
<rect
|
||||
transform="scale(-1,1)"
|
||||
ry="4"
|
||||
rx="4"
|
||||
y="74.5"
|
||||
x="-859.5"
|
||||
height="19"
|
||||
width="63.000004"
|
||||
id="rect17456"
|
||||
style="color:#000000;fill:none;stroke:#2e3436;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect
|
||||
transform="scale(-1,1)"
|
||||
ry="4"
|
||||
rx="4"
|
||||
y="74"
|
||||
x="-828"
|
||||
height="20"
|
||||
width="31"
|
||||
id="rect17458"
|
||||
style="fill:#000000;fill-opacity:1;stroke:#5f5f5f;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline" />
|
||||
<g
|
||||
transform="matrix(-1,0,0,1,1619.1239,-33.986291)"
|
||||
id="g17460"
|
||||
style="display:inline">
|
||||
<path
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 803.6322,115.48629 0,4.29495"
|
||||
id="path17462"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;display:inline"
|
||||
d="m 806.62805,115.48629 0,4.29495"
|
||||
id="path17464"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;display:inline"
|
||||
d="m 809.6239,115.48629 0,4.29495"
|
||||
id="path17466"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
style="font-size:8.95877075px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Cantarell;-inkscape-font-specification:Cantarell Bold"
|
||||
id="text17468"
|
||||
transform="translate(0.34375,0)">
|
||||
<path
|
||||
d="m 837.28518,80.750726 c 0.63282,6e-6 1.19566,0.123947 1.68852,0.371824 0.49284,0.247888 0.8807,0.609505 1.16359,1.084851 0.28287,0.472439 0.42431,1.022155 0.42431,1.649149 0,0.635748 -0.13853,1.200045 -0.41556,1.692892 -0.27706,0.489934 -0.66638,0.870507 -1.16797,1.141719 -0.5016,0.271213 -1.07756,0.406819 -1.72789,0.406819 -0.42869,0 -0.83551,-0.06562 -1.22045,-0.196848 -0.38495,-0.134148 -0.73053,-0.32808 -1.03674,-0.581795 -0.30329,-0.256631 -0.54534,-0.589085 -0.72615,-0.997363 -0.17789,-0.408276 -0.26684,-0.869045 -0.26683,-1.382311 -10e-6,-0.638658 0.13997,-1.200039 0.41994,-1.684144 0.27996,-0.487011 0.66782,-0.858835 1.16359,-1.115472 0.49576,-0.259541 1.06297,-0.389315 1.70164,-0.389321 m 0.57305,1.089225 c -0.20123,-0.05249 -0.40683,-0.07873 -0.61679,-0.07874 -0.20998,5e-6 -0.41412,0.02625 -0.61242,0.07874 -0.19831,0.04958 -0.38933,0.129779 -0.57305,0.240592 -0.18081,0.107907 -0.33974,0.242055 -0.47681,0.402445 -0.13706,0.160399 -0.24642,0.358705 -0.32808,0.594918 -0.0816,0.233306 -0.12248,0.491395 -0.12248,0.774269 0,0.67366 0.20851,1.214627 0.62554,1.622903 0.41702,0.408278 0.93758,0.612416 1.56166,0.612416 0.25954,0 0.51034,-0.04229 0.7524,-0.126858 0.24496,-0.08457 0.47097,-0.20997 0.67803,-0.376198 0.20705,-0.166226 0.37328,-0.392236 0.49868,-0.678032 0.12539,-0.285792 0.18809,-0.610956 0.1881,-0.975492 -10e-6,-0.297455 -0.0437,-0.568668 -0.13123,-0.813638 -0.0875,-0.247878 -0.20415,-0.453475 -0.34995,-0.61679 -0.14291,-0.163307 -0.31059,-0.301829 -0.50306,-0.415568 -0.18956,-0.11373 -0.38641,-0.195385 -0.59054,-0.244967"
|
||||
style="line-height:125%;fill:#ffffff;fill-opacity:1;marker:none;font-family:Cantarell;-inkscape-font-specification:Cantarell Bold"
|
||||
id="path18599"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 843.5362,81.831203 0,1.17917 2.94834,0 0,1.014861 -2.94834,0 0,3.00713 -1.10673,0 0,-6.216022 4.31754,0 0,1.014861 -3.21081,0"
|
||||
style="line-height:125%;fill:#ffffff;fill-opacity:1;marker:none;font-family:Cantarell;-inkscape-font-specification:Cantarell Bold"
|
||||
id="path18601"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccc" />
|
||||
<path
|
||||
d="m 849.71285,81.831203 0,1.17917 2.94834,0 0,1.014861 -2.94834,0 0,3.00713 -1.10672,0 0,-6.216022 4.31753,0 0,1.014861 -3.21081,0"
|
||||
style="line-height:125%;fill:#ffffff;fill-opacity:1;marker:none;font-family:Cantarell;-inkscape-font-specification:Cantarell Bold"
|
||||
id="path18603"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccccccccccc" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 7.2 KiB |
122
data/theme/toggle-on-intl.svg
Normal file
@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="65"
|
||||
height="22"
|
||||
id="svg3199"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="New document 11">
|
||||
<defs
|
||||
id="defs3201">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective3207" />
|
||||
<inkscape:perspective
|
||||
id="perspective3187"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="32.500004"
|
||||
inkscape:cy="10.999997"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="609"
|
||||
inkscape:window-height="501"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata3204">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-342.5,-521.36218)">
|
||||
<g
|
||||
style="display:inline"
|
||||
transform="translate(-453.5,448.36218)"
|
||||
id="g16453">
|
||||
<rect
|
||||
style="color:#000000;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect16256-9-4"
|
||||
width="63.000004"
|
||||
height="19"
|
||||
x="-859.5"
|
||||
y="74.5"
|
||||
rx="4"
|
||||
ry="4"
|
||||
transform="scale(-1,1)" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:#5f5f5f;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline"
|
||||
id="rect16258-5-4"
|
||||
width="31"
|
||||
height="20"
|
||||
x="-860"
|
||||
y="74"
|
||||
rx="4"
|
||||
ry="4"
|
||||
transform="scale(-1,1)" />
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g16298-3-6"
|
||||
transform="matrix(-1,0,0,1,1651.1322,-33.986291)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16265-3-5"
|
||||
d="m 803.6322,115.48629 0,4.29495"
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16265-0-2-0"
|
||||
d="m 806.62805,115.48629 0,4.29495"
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16265-8-7-1"
|
||||
d="m 809.6239,115.48629 0,4.29495"
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
|
||||
</g>
|
||||
<path
|
||||
style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:2;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
d="m 16,27.9375 0,10.125"
|
||||
id="path19232"
|
||||
inkscape:connector-curvature="0"
|
||||
transform="translate(796,51.00002)" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.5 KiB |
128
data/theme/toggle-on-us.svg
Normal file
@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="65"
|
||||
height="22"
|
||||
id="svg2857"
|
||||
version="1.1"
|
||||
inkscape:version="0.47 r22583"
|
||||
sodipodi:docname="New document 2">
|
||||
<defs
|
||||
id="defs2859">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 526.18109 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="744.09448 : 526.18109 : 1"
|
||||
inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
|
||||
id="perspective2865" />
|
||||
<inkscape:perspective
|
||||
id="perspective2843"
|
||||
inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
|
||||
inkscape:vp_z="1 : 0.5 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_x="0 : 0.5 : 1"
|
||||
sodipodi:type="inkscape:persp3d" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="-69.642856"
|
||||
inkscape:cy="42.428569"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="609"
|
||||
inkscape:window-height="501"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="26"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata2862">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-444.64286,-781.36218)">
|
||||
<g
|
||||
style="display:inline"
|
||||
transform="translate(-351.35714,708.36218)"
|
||||
id="g16453">
|
||||
<rect
|
||||
style="color:#000000;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#3465a4;stroke-width:0.99999994;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect16256-9-4"
|
||||
width="63.000004"
|
||||
height="19"
|
||||
x="-859.5"
|
||||
y="74.5"
|
||||
rx="4"
|
||||
ry="4"
|
||||
transform="scale(-1,1)" />
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:#5f5f5f;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;display:inline"
|
||||
id="rect16258-5-4"
|
||||
width="31"
|
||||
height="20"
|
||||
x="-860"
|
||||
y="74"
|
||||
rx="4"
|
||||
ry="4"
|
||||
transform="scale(-1,1)" />
|
||||
<g
|
||||
style="display:inline"
|
||||
id="g16298-3-6"
|
||||
transform="matrix(-1,0,0,1,1651.1322,-33.986291)">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16265-3-5"
|
||||
d="m 803.6322,115.48629 0,4.29495"
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16265-0-2-0"
|
||||
d="m 806.62805,115.48629 0,4.29495"
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path16265-8-7-1"
|
||||
d="m 809.6239,115.48629 0,4.29495"
|
||||
style="fill:none;stroke:#5f5f5f;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1;display:inline" />
|
||||
</g>
|
||||
<g
|
||||
style="font-size:8.95877075px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:125%;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Cantarell;-inkscape-font-specification:Cantarell Bold"
|
||||
id="text42229-3-0">
|
||||
<path
|
||||
d="m 808.01473,80.573953 c 0.63283,6e-6 1.19567,0.123947 1.68852,0.371824 0.49284,0.247888 0.88071,0.609505 1.16359,1.084851 0.28287,0.472439 0.42431,1.022155 0.42432,1.649149 -10e-6,0.635748 -0.13853,1.200045 -0.41557,1.692892 -0.27705,0.489934 -0.66637,0.870506 -1.16796,1.141719 -0.50161,0.271212 -1.07757,0.406819 -1.72789,0.406819 -0.4287,0 -0.83552,-0.06562 -1.22046,-0.196848 -0.38495,-0.134148 -0.73053,-0.32808 -1.03673,-0.581795 -0.3033,-0.256631 -0.54535,-0.589085 -0.72615,-0.997363 -0.1779,-0.408276 -0.26684,-0.869045 -0.26684,-1.382311 0,-0.638658 0.13998,-1.200039 0.41994,-1.684144 0.27996,-0.487011 0.66782,-0.858835 1.16359,-1.115472 0.49576,-0.259541 1.06298,-0.389315 1.70164,-0.389321 m 0.57305,1.089225 c -0.20123,-0.05249 -0.40682,-0.07873 -0.61679,-0.07874 -0.20998,5e-6 -0.41411,0.02625 -0.61242,0.07874 -0.19831,0.04958 -0.38932,0.129779 -0.57304,0.240592 -0.18081,0.107907 -0.33975,0.242055 -0.47681,0.402445 -0.13707,0.160399 -0.24643,0.358705 -0.32808,0.594918 -0.0817,0.233305 -0.12249,0.491395 -0.12249,0.774269 0,0.67366 0.20851,1.214627 0.62554,1.622902 0.41703,0.408279 0.93758,0.612417 1.56166,0.612416 0.25955,10e-7 0.51035,-0.04228 0.7524,-0.126857 0.24496,-0.08457 0.47097,-0.20997 0.67803,-0.376199 0.20705,-0.166225 0.37328,-0.392236 0.49868,-0.678031 0.1254,-0.285792 0.1881,-0.610956 0.1881,-0.975492 0,-0.297455 -0.0437,-0.568668 -0.13123,-0.813638 -0.0875,-0.247878 -0.20414,-0.453475 -0.34995,-0.61679 -0.1429,-0.163307 -0.31059,-0.301829 -0.50306,-0.415568 -0.18956,-0.11373 -0.38641,-0.195385 -0.59054,-0.244967"
|
||||
style="line-height:125%;fill:#ffffff;fill-opacity:1;marker:none;font-family:Cantarell;-inkscape-font-specification:Cantarell Bold"
|
||||
id="path18606" />
|
||||
<path
|
||||
d="m 813.15903,80.639569 1.21608,0 3.4689,4.776844 0,-4.776844 1.10235,0 0,6.216022 -1.22921,0 -3.45577,-4.785594 0,4.785594 -1.10235,0 0,-6.216022"
|
||||
style="line-height:125%;fill:#ffffff;fill-opacity:1;marker:none;font-family:Cantarell;-inkscape-font-specification:Cantarell Bold"
|
||||
id="path18608" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.7 KiB |
96
data/theme/ws-switch-arrow-left.svg
Normal file
@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" width="96" height="96" id="svg25070" version="1.1" inkscape:version="0.47 r22583" sodipodi:docname="dark-arrow-larger.svg">
|
||||
<defs id="defs25072">
|
||||
<inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:vp_x="0 : 24 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="48 : 24 : 1" inkscape:persp3d-origin="24 : 16 : 1" id="perspective25078"/>
|
||||
<inkscape:perspective id="perspective24985" inkscape:persp3d-origin="0.5 : 0.33333333 : 1" inkscape:vp_z="1 : 0.5 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_x="0 : 0.5 : 1" sodipodi:type="inkscape:persp3d"/>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4034-0-4" id="linearGradient24957" gradientUnits="userSpaceOnUse" gradientTransform="translate(6)" x1="-86.552246" y1="185.439" x2="-83.37072" y2="197.31261"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4034-0-4">
|
||||
<stop style="stop-color: rgb(238, 238, 236); stop-opacity: 1;" offset="0" id="stop4036-5-7"/>
|
||||
<stop style="stop-color: rgb(186, 189, 182); stop-opacity: 1;" offset="1" id="stop4038-9-6"/>
|
||||
</linearGradient>
|
||||
<filter id="filter24765" inkscape:label="Invert" x="0" y="0" width="1" height="1" inkscape:menu="Color" inkscape:menu-tooltip="Invert colors" color-interpolation-filters="sRGB">
|
||||
<feColorMatrix id="feColorMatrix24767" type="saturate" values="1" result="fbSourceGraphic"/>
|
||||
<feColorMatrix id="feColorMatrix24769" in="fbSourceGraphic" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4632-1-3-9-3-2" id="linearGradient24955" gradientUnits="userSpaceOnUse" gradientTransform="translate(-5)" x1="-74.520325" y1="169.06032" x2="-74.520325" y2="205.94189"/>
|
||||
<linearGradient id="linearGradient4632-1-3-9-3-2">
|
||||
<stop style="stop-color: rgb(238, 238, 236); stop-opacity: 1;" offset="0" id="stop4634-1-8-3-9-0"/>
|
||||
<stop id="stop4636-1-9-9-8-8" offset="0.0274937" style="stop-color: rgb(255, 255, 255); stop-opacity: 1;"/>
|
||||
<stop id="stop4638-8-3-9-6-6" offset="0.274937" style="stop-color: rgb(242, 242, 242); stop-opacity: 1;"/>
|
||||
<stop id="stop4640-8-5-7-8-9" offset="0.38707438" style="stop-color: rgb(238, 238, 236); stop-opacity: 1;"/>
|
||||
<stop id="stop4642-5-41-9-6-9" offset="0.66528589" style="stop-color: rgb(217, 218, 216); stop-opacity: 1;"/>
|
||||
<stop id="stop4644-5-2-7-9-2" offset="0.76745707" style="stop-color: rgb(223, 224, 221); stop-opacity: 1;"/>
|
||||
<stop style="stop-color: rgb(240, 240, 240); stop-opacity: 1;" offset="1" id="stop4646-3-2-3-7-3"/>
|
||||
</linearGradient>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient4869-4-1" id="radialGradient24959" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.0075, 0, 0, 1.0075, -5.4544, -1.25141)" cx="-33.412369" cy="185.74171" fx="-33.412369" fy="185.74171" r="2.3554697"/>
|
||||
<linearGradient id="linearGradient4869-4-1">
|
||||
<stop style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0" id="stop4871-6-2"/>
|
||||
<stop id="stop4879-7-4" offset="0.31807542" style="stop-color: rgb(238, 238, 236); stop-opacity: 1;"/>
|
||||
<stop id="stop4877-6-1" offset="0.74691135" style="stop-color: rgb(200, 201, 198); stop-opacity: 1;"/>
|
||||
<stop style="stop-color: rgb(211, 215, 207); stop-opacity: 1;" offset="1" id="stop4873-1-0"/>
|
||||
</linearGradient>
|
||||
<filter id="filter25011" inkscape:label="Invert" x="0" y="0" width="1" height="1" inkscape:menu="Color" inkscape:menu-tooltip="Invert colors" color-interpolation-filters="sRGB">
|
||||
<feColorMatrix id="feColorMatrix25013" type="saturate" values="1" result="fbSourceGraphic"/>
|
||||
<feColorMatrix id="feColorMatrix25015" in="fbSourceGraphic" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<radialGradient inkscape:collect="always" xlink:href="#linearGradient4869-4-0" id="radialGradient24961" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.0075, 0, 0, 1.0075, -5.4544, -1.25141)" cx="-33.412369" cy="185.74171" fx="-33.412369" fy="185.74171" r="2.3554697"/>
|
||||
<linearGradient id="linearGradient4869-4-0">
|
||||
<stop style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0" id="stop4871-6-8"/>
|
||||
<stop id="stop4879-7-5" offset="0.31807542" style="stop-color: rgb(238, 238, 236); stop-opacity: 1;"/>
|
||||
<stop id="stop4877-6-5" offset="0.74691135" style="stop-color: rgb(200, 201, 198); stop-opacity: 1;"/>
|
||||
<stop style="stop-color: rgb(211, 215, 207); stop-opacity: 1;" offset="1" id="stop4873-1-4"/>
|
||||
</linearGradient>
|
||||
<filter id="filter25023" inkscape:label="Invert" x="0" y="0" width="1" height="1" inkscape:menu="Color" inkscape:menu-tooltip="Invert colors" color-interpolation-filters="sRGB">
|
||||
<feColorMatrix id="feColorMatrix25025" type="saturate" values="1" result="fbSourceGraphic"/>
|
||||
<feColorMatrix id="feColorMatrix25027" in="fbSourceGraphic" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4941" id="linearGradient24963" gradientUnits="userSpaceOnUse" x1="-39.858727" y1="184.61784" x2="-38.244785" y2="188.84898"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4941">
|
||||
<stop style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0" id="stop4943"/>
|
||||
<stop style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1" id="stop4945"/>
|
||||
</linearGradient>
|
||||
<filter id="filter25033" inkscape:label="Invert" x="0" y="0" width="1" height="1" inkscape:menu="Color" inkscape:menu-tooltip="Invert colors" color-interpolation-filters="sRGB">
|
||||
<feColorMatrix id="feColorMatrix25035" type="saturate" values="1" result="fbSourceGraphic"/>
|
||||
<feColorMatrix id="feColorMatrix25037" in="fbSourceGraphic" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<linearGradient inkscape:collect="always" xlink:href="#linearGradient4941-7" id="linearGradient24965" gradientUnits="userSpaceOnUse" x1="-39.858727" y1="184.61784" x2="-38.244785" y2="188.84898"/>
|
||||
<linearGradient inkscape:collect="always" id="linearGradient4941-7">
|
||||
<stop style="stop-color: rgb(255, 255, 255); stop-opacity: 1;" offset="0" id="stop4943-2"/>
|
||||
<stop style="stop-color: rgb(255, 255, 255); stop-opacity: 0;" offset="1" id="stop4945-5"/>
|
||||
</linearGradient>
|
||||
<filter id="filter25043" inkscape:label="Invert" x="0" y="0" width="1" height="1" inkscape:menu="Color" inkscape:menu-tooltip="Invert colors" color-interpolation-filters="sRGB">
|
||||
<feColorMatrix id="feColorMatrix25045" type="saturate" values="1" result="fbSourceGraphic"/>
|
||||
<feColorMatrix id="feColorMatrix25047" in="fbSourceGraphic" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<filter id="filter25049" inkscape:label="Invert" x="0" y="0" width="1" height="1" inkscape:menu="Color" inkscape:menu-tooltip="Invert colors" color-interpolation-filters="sRGB">
|
||||
<feColorMatrix id="feColorMatrix25051" type="saturate" values="1" result="fbSourceGraphic"/>
|
||||
<feColorMatrix id="feColorMatrix25053" in="fbSourceGraphic" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"/>
|
||||
</filter>
|
||||
<filter id="filter25055" inkscape:label="Invert" x="0" y="0" width="1" height="1" inkscape:menu="Color" inkscape:menu-tooltip="Invert colors" color-interpolation-filters="sRGB">
|
||||
<feColorMatrix id="feColorMatrix25057" type="saturate" values="1" result="fbSourceGraphic"/>
|
||||
<feColorMatrix id="feColorMatrix25059" in="fbSourceGraphic" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"/>
|
||||
</filter>
|
||||
</defs>
|
||||
<sodipodi:namedview id="base" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="2.8284271" inkscape:cx="48.631638" inkscape:cy="57.536221" inkscape:current-layer="layer1" showgrid="true" inkscape:grid-bbox="true" inkscape:document-units="px" inkscape:window-width="1200" inkscape:window-height="851" inkscape:window-x="0" inkscape:window-y="52" inkscape:window-maximized="0"/>
|
||||
<metadata id="metadata25075">
|
||||
<rdf:RDF>
|
||||
<cc:Work rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
|
||||
<dc:title/>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer" transform="translate(0, 48)">
|
||||
<g id="g4030-1-8" transform="matrix(2, 0, 0, 2, 193.25, -374.967)" style="stroke: rgb(0, 0, 0); display: inline; stroke-opacity: 1;">
|
||||
<path sodipodi:nodetypes="ccc" id="path3165-7-3" d="m -72.5,173.5 -14,14 14,14" style="overflow: visible; marker: none; color: rgb(0, 0, 0); fill: none; stroke: rgb(0, 0, 0); stroke-width: 7; stroke-linecap: round; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dasharray: none; stroke-dashoffset: 0pt; visibility: visible; display: inline;"/>
|
||||
</g>
|
||||
<path sodipodi:type="arc" style="overflow: visible; marker: none; color: rgb(0, 0, 0); fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 0.523439; visibility: visible; display: inline;" id="path4050-2-7-9-4" sodipodi:cx="-38.59375" sodipodi:cy="186.40625" sodipodi:rx="2.09375" sodipodi:ry="2.09375" d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z" transform="matrix(3.34328, 0, 0, 3.34328, 185.28, -623.176)"/>
|
||||
<path sodipodi:type="arc" style="overflow: visible; marker: none; color: rgb(0, 0, 0); fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; stroke: none; stroke-width: 0.523439; visibility: visible; display: inline;" id="path4050-2-7-9-4-8" sodipodi:cx="-38.59375" sodipodi:cy="186.40625" sodipodi:rx="2.09375" sodipodi:ry="2.09375" d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z" transform="matrix(3.34328, 0, 0, 3.34328, 207.28, -623.176)"/>
|
||||
<path sodipodi:type="arc" style="overflow: visible; marker: none; color: rgb(0, 0, 0); fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.697921; stroke-linecap: round; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dasharray: none; stroke-dashoffset: 0pt; visibility: visible; display: inline;" id="path4050-2-7-9-4-0" sodipodi:cx="-38.59375" sodipodi:cy="186.40625" sodipodi:rx="2.09375" sodipodi:ry="2.09375" d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z" transform="matrix(2.86565, 0, 0, 2.86565, 166.846, -534.143)"/>
|
||||
<path sodipodi:type="arc" style="overflow: visible; marker: none; color: rgb(0, 0, 0); fill: none; stroke: rgb(0, 0, 0); stroke-width: 0.697921; stroke-linecap: round; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-dasharray: none; stroke-dashoffset: 0pt; visibility: visible; display: inline;" id="path4050-2-7-9-4-0-9" sodipodi:cx="-38.59375" sodipodi:cy="186.40625" sodipodi:rx="2.09375" sodipodi:ry="2.09375" d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z" transform="matrix(2.86565, 0, 0, 2.86565, 188.846, -534.143)"/>
|
||||
<path style="overflow: visible; marker: none; font-size: medium; font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; text-indent: 0pt; text-align: start; text-decoration: none; line-height: normal; letter-spacing: normal; word-spacing: normal; text-transform: none; direction: ltr; text-anchor: start; opacity: 0.35; color: rgb(0, 0, 0); fill: none; stroke: rgb(0, 0, 0); stroke-width: 1; stroke-miterlimit: 4; stroke-dasharray: none; visibility: visible; display: inline; font-family: Bitstream Vera Sans; stroke-opacity: 1;" d="m 317.06251,365.96875 c -0.76948,0.0224 -1.52555,0.35464 -2.0625,0.90625 l -16.125,16.125 16.125,16.125 c 1.11265,1.11265 3.13735,1.11265 4.25,0 1.11265,-1.11264 1.11265,-3.13735 0,-4.25 l -11.875,-11.875 11.875,-11.875 c 0.86584,-0.83655 1.1475,-2.22114 0.6773,-3.32947 -0.47021,-1.10834 -1.66156,-1.86802 -2.8648,-1.82678 z" id="path3165-7-3-1" sodipodi:nodetypes="ccccscccsc" transform="matrix(2, 0, 0, 2, -586, -765.967)"/>
|
||||
<path style="overflow: visible; marker: none; font-size: medium; font-style: normal; font-variant: normal; font-weight: normal; font-stretch: normal; text-indent: 0pt; text-align: start; text-decoration: none; line-height: normal; letter-spacing: normal; word-spacing: normal; text-transform: none; direction: ltr; text-anchor: start; color: rgb(0, 0, 0); fill: none; stroke: rgb(0, 0, 0); stroke-width: 1; stroke-linecap: round; stroke-miterlimit: 4; stroke-dasharray: none; visibility: visible; display: inline; font-family: Bitstream Vera Sans; stroke-opacity: 1;" d="m 320.08435,397.03059 c 0.007,-0.79449 -0.27079,-1.59203 -0.83434,-2.15559 L 307.37501,383 m 12.5523,-15.20447 c -0.47021,-1.10834 -1.66156,-1.86802 -2.8648,-1.82678 -0.76948,0.0224 -1.52555,0.35464 -2.0625,0.90625 L 298.87501,383" id="path3165-7-3-1-9" sodipodi:nodetypes="ccccccc" transform="matrix(2, 0, 0, 2, -586, -765.967)"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 13 KiB |
331
data/theme/ws-switch-arrow-right.svg
Normal file
@ -0,0 +1,331 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
width="96"
|
||||
height="96"
|
||||
id="svg25070">
|
||||
<defs
|
||||
id="defs25072">
|
||||
<linearGradient
|
||||
x1="-86.552246"
|
||||
y1="185.439"
|
||||
x2="-83.37072"
|
||||
y2="197.31261"
|
||||
id="linearGradient24957"
|
||||
xlink:href="#linearGradient4034-0-4"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(6,0)" />
|
||||
<linearGradient
|
||||
id="linearGradient4034-0-4">
|
||||
<stop
|
||||
id="stop4036-5-7"
|
||||
style="stop-color:#eeeeec;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop4038-9-6"
|
||||
style="stop-color:#babdb6;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter24765">
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphic"
|
||||
values="1"
|
||||
type="saturate"
|
||||
id="feColorMatrix24767" />
|
||||
<feColorMatrix
|
||||
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
|
||||
in="fbSourceGraphic"
|
||||
id="feColorMatrix24769" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
x1="-74.520325"
|
||||
y1="169.06032"
|
||||
x2="-74.520325"
|
||||
y2="205.94189"
|
||||
id="linearGradient24955"
|
||||
xlink:href="#linearGradient4632-1-3-9-3-2"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-5,0)" />
|
||||
<linearGradient
|
||||
id="linearGradient4632-1-3-9-3-2">
|
||||
<stop
|
||||
id="stop4634-1-8-3-9-0"
|
||||
style="stop-color:#eeeeec;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop4636-1-9-9-8-8"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0.0274937" />
|
||||
<stop
|
||||
id="stop4638-8-3-9-6-6"
|
||||
style="stop-color:#f2f2f2;stop-opacity:1"
|
||||
offset="0.274937" />
|
||||
<stop
|
||||
id="stop4640-8-5-7-8-9"
|
||||
style="stop-color:#eeeeec;stop-opacity:1"
|
||||
offset="0.38707438" />
|
||||
<stop
|
||||
id="stop4642-5-41-9-6-9"
|
||||
style="stop-color:#d9dad8;stop-opacity:1"
|
||||
offset="0.66528589" />
|
||||
<stop
|
||||
id="stop4644-5-2-7-9-2"
|
||||
style="stop-color:#dfe0dd;stop-opacity:1"
|
||||
offset="0.76745707" />
|
||||
<stop
|
||||
id="stop4646-3-2-3-7-3"
|
||||
style="stop-color:#f0f0f0;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
cx="-33.412369"
|
||||
cy="185.74171"
|
||||
r="2.3554697"
|
||||
fx="-33.412369"
|
||||
fy="185.74171"
|
||||
id="radialGradient24959"
|
||||
xlink:href="#linearGradient4869-4-1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0075,0,0,1.0075,-5.4544,-1.25141)" />
|
||||
<linearGradient
|
||||
id="linearGradient4869-4-1">
|
||||
<stop
|
||||
id="stop4871-6-2"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop4879-7-4"
|
||||
style="stop-color:#eeeeec;stop-opacity:1"
|
||||
offset="0.31807542" />
|
||||
<stop
|
||||
id="stop4877-6-1"
|
||||
style="stop-color:#c8c9c6;stop-opacity:1"
|
||||
offset="0.74691135" />
|
||||
<stop
|
||||
id="stop4873-1-0"
|
||||
style="stop-color:#d3d7cf;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter25011">
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphic"
|
||||
values="1"
|
||||
type="saturate"
|
||||
id="feColorMatrix25013" />
|
||||
<feColorMatrix
|
||||
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
|
||||
in="fbSourceGraphic"
|
||||
id="feColorMatrix25015" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
cx="-33.412369"
|
||||
cy="185.74171"
|
||||
r="2.3554697"
|
||||
fx="-33.412369"
|
||||
fy="185.74171"
|
||||
id="radialGradient24961"
|
||||
xlink:href="#linearGradient4869-4-0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.0075,0,0,1.0075,-5.4544,-1.25141)" />
|
||||
<linearGradient
|
||||
id="linearGradient4869-4-0">
|
||||
<stop
|
||||
id="stop4871-6-8"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop4879-7-5"
|
||||
style="stop-color:#eeeeec;stop-opacity:1"
|
||||
offset="0.31807542" />
|
||||
<stop
|
||||
id="stop4877-6-5"
|
||||
style="stop-color:#c8c9c6;stop-opacity:1"
|
||||
offset="0.74691135" />
|
||||
<stop
|
||||
id="stop4873-1-4"
|
||||
style="stop-color:#d3d7cf;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter25023">
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphic"
|
||||
values="1"
|
||||
type="saturate"
|
||||
id="feColorMatrix25025" />
|
||||
<feColorMatrix
|
||||
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
|
||||
in="fbSourceGraphic"
|
||||
id="feColorMatrix25027" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
x1="-39.858727"
|
||||
y1="184.61784"
|
||||
x2="-38.244785"
|
||||
y2="188.84898"
|
||||
id="linearGradient24963"
|
||||
xlink:href="#linearGradient4941"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
id="linearGradient4941">
|
||||
<stop
|
||||
id="stop4943"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop4945"
|
||||
style="stop-color:#ffffff;stop-opacity:0"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter25033">
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphic"
|
||||
values="1"
|
||||
type="saturate"
|
||||
id="feColorMatrix25035" />
|
||||
<feColorMatrix
|
||||
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
|
||||
in="fbSourceGraphic"
|
||||
id="feColorMatrix25037" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
x1="-39.858727"
|
||||
y1="184.61784"
|
||||
x2="-38.244785"
|
||||
y2="188.84898"
|
||||
id="linearGradient24965"
|
||||
xlink:href="#linearGradient4941-7"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
id="linearGradient4941-7">
|
||||
<stop
|
||||
id="stop4943-2"
|
||||
style="stop-color:#ffffff;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop4945-5"
|
||||
style="stop-color:#ffffff;stop-opacity:0"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<filter
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter25043">
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphic"
|
||||
values="1"
|
||||
type="saturate"
|
||||
id="feColorMatrix25045" />
|
||||
<feColorMatrix
|
||||
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
|
||||
in="fbSourceGraphic"
|
||||
id="feColorMatrix25047" />
|
||||
</filter>
|
||||
<filter
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter25049">
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphic"
|
||||
values="1"
|
||||
type="saturate"
|
||||
id="feColorMatrix25051" />
|
||||
<feColorMatrix
|
||||
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
|
||||
in="fbSourceGraphic"
|
||||
id="feColorMatrix25053" />
|
||||
</filter>
|
||||
<filter
|
||||
x="0"
|
||||
y="0"
|
||||
width="1"
|
||||
height="1"
|
||||
color-interpolation-filters="sRGB"
|
||||
id="filter25055">
|
||||
<feColorMatrix
|
||||
result="fbSourceGraphic"
|
||||
values="1"
|
||||
type="saturate"
|
||||
id="feColorMatrix25057" />
|
||||
<feColorMatrix
|
||||
values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"
|
||||
in="fbSourceGraphic"
|
||||
id="feColorMatrix25059" />
|
||||
</filter>
|
||||
</defs>
|
||||
<g
|
||||
transform="translate(0,48)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="matrix(-2,0,0,2,-97.2497,-374.967)"
|
||||
id="g4030-1-8"
|
||||
style="stroke:#000000;stroke-opacity:1;display:inline">
|
||||
<path
|
||||
d="m -72.5,173.5 -14,14 14,14"
|
||||
id="path3165-7-3"
|
||||
style="color:#000000;fill:none;stroke:#000000;stroke-width:7;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
|
||||
</g>
|
||||
<path
|
||||
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
|
||||
transform="matrix(-3.34328,0,0,3.34328,-89.2797,-623.176)"
|
||||
id="path4050-2-7-9-4"
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible" />
|
||||
<path
|
||||
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
|
||||
transform="matrix(-3.34328,0,0,3.34328,-111.2797,-623.176)"
|
||||
id="path4050-2-7-9-4-8"
|
||||
style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.52343899;marker:none;visibility:visible;display:inline;overflow:visible" />
|
||||
<path
|
||||
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
|
||||
transform="matrix(-2.86565,0,0,2.86565,-70.8457,-534.143)"
|
||||
id="path4050-2-7-9-4-0"
|
||||
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
|
||||
<path
|
||||
d="m -36.5,186.40625 a 2.09375,2.09375 0 1 1 -4.1875,0 2.09375,2.09375 0 1 1 4.1875,0 z"
|
||||
transform="matrix(-2.86565,0,0,2.86565,-92.8457,-534.143)"
|
||||
id="path4050-2-7-9-4-0-9"
|
||||
style="color:#000000;fill:none;stroke:#000000;stroke-width:0.69792098;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
|
||||
<path
|
||||
d="m 47.87528,-34.0295 c 1.53896,0.0448 3.0511,0.70928 4.125,1.8125 l 32.25,32.25 -32.25,32.25 c -2.2253,2.2253 -6.2747,2.2253 -8.5,0 -2.2253,-2.22528 -2.2253,-6.2747 0,-8.5 l 23.75,-23.75 -23.75,-23.75 c -1.73168,-1.6731 -2.295,-4.44228 -1.3546,-6.65894 0.94042,-2.21668 3.32312,-3.73604 5.7296,-3.65356 z"
|
||||
id="path3165-7-3-1"
|
||||
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;opacity:0.35;color:#000000;fill:none;stroke:#000000;stroke-width:2;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans" />
|
||||
<path
|
||||
d="m 41.8316,28.09418 c -0.014,-1.58898 0.54158,-3.18406 1.66868,-4.31118 l 23.75,-23.75 m -25.1046,-30.40894 c 0.94042,-2.21668 3.32312,-3.73604 5.7296,-3.65356 1.53896,0.0448 3.0511,0.70928 4.125,1.8125 l 32.25,32.25"
|
||||
id="path3165-7-3-1-9"
|
||||
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0pt;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;text-anchor:start;color:#000000;fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;font-family:Bitstream Vera Sans" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
@ -33,7 +33,7 @@
|
||||
<foaf:Person>
|
||||
<foaf:name>Colin Walters</foaf:name>
|
||||
<foaf:mbox rdf:resource="mailto:walters@verbum.org" />
|
||||
<gnome:userid>cwalters</gnome:userid>
|
||||
<gnome:userid>walters</gnome:userid>
|
||||
</foaf:Person>
|
||||
</maintainer>
|
||||
<maintainer>
|
||||
|
@ -1 +1,64 @@
|
||||
SUBDIRS = misc ui
|
||||
|
||||
jsdir = $(pkgdatadir)/js
|
||||
|
||||
nobase_dist_js_DATA = \
|
||||
misc/config.js \
|
||||
misc/docInfo.js \
|
||||
misc/fileUtils.js \
|
||||
misc/format.js \
|
||||
misc/gnomeSession.js \
|
||||
misc/params.js \
|
||||
misc/telepathy.js \
|
||||
misc/util.js \
|
||||
perf/core.js \
|
||||
ui/altTab.js \
|
||||
ui/appDisplay.js \
|
||||
ui/appFavorites.js \
|
||||
ui/boxpointer.js \
|
||||
ui/calendar.js \
|
||||
ui/chrome.js \
|
||||
ui/ctrlAltTab.js \
|
||||
ui/dash.js \
|
||||
ui/dateMenu.js \
|
||||
ui/dnd.js \
|
||||
ui/docDisplay.js \
|
||||
ui/endSessionDialog.js \
|
||||
ui/environment.js \
|
||||
ui/extensionSystem.js \
|
||||
ui/iconGrid.js \
|
||||
ui/lightbox.js \
|
||||
ui/link.js \
|
||||
ui/lookingGlass.js \
|
||||
ui/magnifier.js \
|
||||
ui/magnifierDBus.js \
|
||||
ui/main.js \
|
||||
ui/messageTray.js \
|
||||
ui/modalDialog.js \
|
||||
ui/notificationDaemon.js \
|
||||
ui/overview.js \
|
||||
ui/panel.js \
|
||||
ui/panelMenu.js \
|
||||
ui/placeDisplay.js \
|
||||
ui/popupMenu.js \
|
||||
ui/runDialog.js \
|
||||
ui/scripting.js \
|
||||
ui/search.js \
|
||||
ui/searchDisplay.js \
|
||||
ui/shellDBus.js \
|
||||
ui/statusIconDispatcher.js \
|
||||
ui/statusMenu.js \
|
||||
ui/status/accessibility.js \
|
||||
ui/status/keyboard.js \
|
||||
ui/status/power.js \
|
||||
ui/status/volume.js \
|
||||
ui/status/bluetooth.js \
|
||||
ui/telepathyClient.js \
|
||||
ui/tweener.js \
|
||||
ui/viewSelector.js \
|
||||
ui/windowAttentionHandler.js \
|
||||
ui/windowManager.js \
|
||||
ui/workspace.js \
|
||||
ui/workspaceThumbnail.js \
|
||||
ui/workspacesView.js \
|
||||
ui/workspaceSwitcherPopup.js \
|
||||
ui/xdndHandler.js
|
||||
|
@ -1,6 +0,0 @@
|
||||
jsmiscdir = $(pkgdatadir)/js/misc
|
||||
|
||||
dist_jsmisc_DATA = \
|
||||
docInfo.js \
|
||||
format.js \
|
||||
params.js
|
10
js/misc/config.js.in
Normal file
@ -0,0 +1,10 @@
|
||||
/* mode: js2; indent-tabs-mode: nil; tab-size: 4 */
|
||||
/* The name of this package (not localized) */
|
||||
const PACKAGE_NAME = '@PACKAGE_NAME@';
|
||||
/* The version of this package */
|
||||
const PACKAGE_VERSION = '@PACKAGE_VERSION@';
|
||||
/* The version of GJS we're linking to */
|
||||
const GJS_VERSION = '@GJS_VERSION@';
|
||||
/* 1 if gnome-bluetooth is available, 0 otherwise */
|
||||
const HAVE_BLUETOOTH = @HAVE_BLUETOOTH@;
|
||||
|
@ -1,14 +1,10 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const Search = imports.ui.search;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const THUMBNAIL_ICON_MARGIN = 2;
|
||||
|
||||
@ -22,7 +18,7 @@ DocInfo.prototype = {
|
||||
// We actually used get_modified() instead of get_visited()
|
||||
// here, as GtkRecentInfo doesn't updated get_visited()
|
||||
// correctly. See http://bugzilla.gnome.org/show_bug.cgi?id=567094
|
||||
this.timestamp = recentInfo.get_modified().getTime() / 1000;
|
||||
this.timestamp = recentInfo.get_modified();
|
||||
this.name = recentInfo.get_display_name();
|
||||
this._lowerName = this.name.toLowerCase();
|
||||
this.uri = recentInfo.get_uri();
|
||||
@ -30,11 +26,11 @@ DocInfo.prototype = {
|
||||
},
|
||||
|
||||
createIcon : function(size) {
|
||||
return Shell.TextureCache.get_default().load_recent_thumbnail(size, this.recentInfo);
|
||||
return St.TextureCache.get_default().load_recent_thumbnail(size, this.recentInfo);
|
||||
},
|
||||
|
||||
launch : function() {
|
||||
Shell.DocSystem.get_default().open(this.recentInfo);
|
||||
launch : function(workspaceIndex) {
|
||||
Shell.DocSystem.get_default().open(this.recentInfo, workspaceIndex);
|
||||
},
|
||||
|
||||
matchTerms: function(terms) {
|
||||
@ -43,15 +39,12 @@ DocInfo.prototype = {
|
||||
let term = terms[i];
|
||||
let idx = this._lowerName.indexOf(term);
|
||||
if (idx == 0) {
|
||||
if (mtype != Search.MatchType.NONE)
|
||||
return Search.MatchType.MULTIPLE;
|
||||
mtype = Search.MatchType.PREFIX;
|
||||
} else if (idx > 0) {
|
||||
if (mtype != Search.MatchType.NONE)
|
||||
return Search.MatchType.MULTIPLE;
|
||||
mtype = Search.MatchType.SUBSTRING;
|
||||
if (mtype == Search.MatchType.NONE)
|
||||
mtype = Search.MatchType.SUBSTRING;
|
||||
} else {
|
||||
continue;
|
||||
return Search.MatchType.NONE;
|
||||
}
|
||||
}
|
||||
return mtype;
|
||||
@ -67,8 +60,7 @@ function getDocManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* DocManager wraps the DocSystem, primarily to expose DocInfo objects
|
||||
* which conform to the GenericDisplay item API.
|
||||
* DocManager wraps the DocSystem, primarily to expose DocInfo objects.
|
||||
*/
|
||||
function DocManager() {
|
||||
this._init();
|
||||
@ -113,40 +105,36 @@ DocManager.prototype = {
|
||||
return this._docSystem.queue_existence_check(count);
|
||||
},
|
||||
|
||||
initialSearch: function(terms) {
|
||||
let multipleMatches = [];
|
||||
_searchDocs: function(items, terms) {
|
||||
let multiplePrefixMatches = [];
|
||||
let prefixMatches = [];
|
||||
let multipleSubtringMatches = [];
|
||||
let substringMatches = [];
|
||||
for (let i = 0; i < this._infosByTimestamp.length; i++) {
|
||||
let item = this._infosByTimestamp[i];
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
let item = items[i];
|
||||
let mtype = item.matchTerms(terms);
|
||||
if (mtype == Search.MatchType.MULTIPLE)
|
||||
multipleMatches.push(item.uri);
|
||||
if (mtype == Search.MatchType.MULTIPLE_PREFIX)
|
||||
multiplePrefixMatches.push(item.uri);
|
||||
else if (mtype == Search.MatchType.PREFIX)
|
||||
prefixMatches.push(item.uri);
|
||||
else if (mtype == Search.MatchType.MULTIPLE_SUBSTRING)
|
||||
multipleSubtringMatches.push(item.uri);
|
||||
else if (mtype == Search.MatchType.SUBSTRING)
|
||||
substringMatches.push(item.uri);
|
||||
}
|
||||
return multipleMatches.concat(prefixMatches.concat(substringMatches));
|
||||
return multiplePrefixMatches.concat(prefixMatches.concat(multipleSubtringMatches.concat(substringMatches)));
|
||||
},
|
||||
|
||||
initialSearch: function(terms) {
|
||||
return this._searchDocs(this._infosByTimestamp, terms);
|
||||
},
|
||||
|
||||
subsearch: function(previousResults, terms) {
|
||||
let multipleMatches = [];
|
||||
let prefixMatches = [];
|
||||
let substringMatches = [];
|
||||
for (let i = 0; i < previousResults.length; i++) {
|
||||
let uri = previousResults[i];
|
||||
let item = this._infosByUri[uri];
|
||||
let mtype = item.matchTerms(terms);
|
||||
if (mtype == Search.MatchType.MULTIPLE)
|
||||
multipleMatches.push(uri);
|
||||
else if (mtype == Search.MatchType.PREFIX)
|
||||
prefixMatches.push(uri);
|
||||
else if (mtype == Search.MatchType.SUBSTRING)
|
||||
substringMatches.push(uri);
|
||||
}
|
||||
return multipleMatches.concat(prefixMatches.concat(substringMatches));
|
||||
return this._searchDocs(previousResults.map(Lang.bind(this,
|
||||
function(url) {
|
||||
return this._infosByUri[url];
|
||||
})), terms);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(DocManager.prototype);
|
||||
|
22
js/misc/fileUtils.js
Normal file
@ -0,0 +1,22 @@
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
|
||||
function listDirAsync(file, callback) {
|
||||
let allFiles = [];
|
||||
file.enumerate_children_async(Gio.FILE_ATTRIBUTE_STANDARD_NAME,
|
||||
Gio.FileQueryInfoFlags.NONE,
|
||||
GLib.PRIORITY_LOW, null, function (obj, res) {
|
||||
let enumerator = obj.enumerate_children_finish(res);
|
||||
function onNextFileComplete(obj, res) {
|
||||
let files = obj.next_files_finish(res);
|
||||
if (files.length) {
|
||||
allFiles = allFiles.concat(files);
|
||||
enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, onNextFileComplete);
|
||||
} else {
|
||||
enumerator.close(null);
|
||||
callback(allFiles);
|
||||
}
|
||||
}
|
||||
enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, onNextFileComplete);
|
||||
});
|
||||
}
|
@ -6,8 +6,10 @@
|
||||
* It has to be set up using String.prototype.format = Format.format;
|
||||
* Usage:
|
||||
* "somestring %s %d".format('hello', 5);
|
||||
* It supports %s, %d and %f, for %f it also support precisions like
|
||||
* "%.2f".format(1.526)
|
||||
* It supports %s, %d, %x and %f, for %f it also support precisions like
|
||||
* "%.2f".format(1.526). All specifiers can be prefixed with a minimum
|
||||
* field width, e.g. "%5s".format("foo"). Unless the width is prefixed
|
||||
* with '0', the formatted string will be padded with spaces.
|
||||
*/
|
||||
|
||||
function format() {
|
||||
@ -15,30 +17,44 @@ function format() {
|
||||
let i = 0;
|
||||
let args = arguments;
|
||||
|
||||
return str.replace(/%(?:\.([0-9]+))?(.)/g, function (str, precisionGroup, genericGroup) {
|
||||
return str.replace(/%([0-9]+)?(?:\.([0-9]+))?(.)/g, function (str, widthGroup, precisionGroup, genericGroup) {
|
||||
|
||||
if (precisionGroup != '' && genericGroup != 'f')
|
||||
throw new Error("Precision can only be specified for 'f'");
|
||||
|
||||
let fillChar = (widthGroup[0] == '0') ? '0' : ' ';
|
||||
let width = parseInt(widthGroup, 10) || 0;
|
||||
|
||||
function fillWidth(s, c, w) {
|
||||
let fill = '';
|
||||
for (let i = 0; i < w; i++)
|
||||
fill += c;
|
||||
return fill.substr(s.length) + s;
|
||||
}
|
||||
|
||||
let s = '';
|
||||
switch (genericGroup) {
|
||||
case '%':
|
||||
return '%';
|
||||
break;
|
||||
case 's':
|
||||
return args[i++].toString();
|
||||
s = args[i++].toString();
|
||||
break;
|
||||
case 'd':
|
||||
return parseInt(args[i++]);
|
||||
s = parseInt(args[i++]).toString();
|
||||
break;
|
||||
case 'x':
|
||||
s = parseInt(args[i++]).toString(16);
|
||||
break;
|
||||
case 'f':
|
||||
if (precisionGroup == '')
|
||||
return parseFloat(args[i++]);
|
||||
s = parseFloat(args[i++]).toString();
|
||||
else
|
||||
return parseFloat(args[i++]).toFixed(parseInt(precisionGroup));
|
||||
s = parseFloat(args[i++]).toFixed(parseInt(precisionGroup));
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported conversion character %' + genericGroup);
|
||||
}
|
||||
return ""; // Suppress warning
|
||||
return fillWidth(s, fillChar, width);
|
||||
});
|
||||
}
|
||||
|
103
js/misc/gnomeSession.js
Normal file
@ -0,0 +1,103 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const PresenceIface = {
|
||||
name: 'org.gnome.SessionManager.Presence',
|
||||
methods: [{ name: 'SetStatus',
|
||||
inSignature: 'u' }],
|
||||
properties: [{ name: 'status',
|
||||
signature: 'u',
|
||||
access: 'readwrite' }],
|
||||
signals: [{ name: 'StatusChanged',
|
||||
inSignature: 'u' }]
|
||||
};
|
||||
|
||||
const PresenceStatus = {
|
||||
AVAILABLE: 0,
|
||||
INVISIBLE: 1,
|
||||
BUSY: 2,
|
||||
IDLE: 3
|
||||
};
|
||||
|
||||
function Presence() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
Presence.prototype = {
|
||||
_init: function() {
|
||||
DBus.session.proxifyObject(this, 'org.gnome.SessionManager', '/org/gnome/SessionManager/Presence', this);
|
||||
},
|
||||
|
||||
getStatus: function(callback) {
|
||||
this.GetRemote('status', Lang.bind(this,
|
||||
function(status, ex) {
|
||||
if (!ex)
|
||||
callback(this, status);
|
||||
}));
|
||||
},
|
||||
|
||||
setStatus: function(status) {
|
||||
this.SetStatusRemote(status);
|
||||
}
|
||||
};
|
||||
DBus.proxifyPrototype(Presence.prototype, PresenceIface);
|
||||
|
||||
// Note inhibitors are immutable objects, so they don't
|
||||
// change at runtime (changes always come in the form
|
||||
// of new inhibitors)
|
||||
const InhibitorIface = {
|
||||
name: 'org.gnome.SessionManager.Inhibitor',
|
||||
properties: [{ name: 'app_id',
|
||||
signature: 's',
|
||||
access: 'readonly' },
|
||||
{ name: 'client_id',
|
||||
signature: 's',
|
||||
access: 'readonly' },
|
||||
{ name: 'reason',
|
||||
signature: 's',
|
||||
access: 'readonly' },
|
||||
{ name: 'flags',
|
||||
signature: 'u',
|
||||
access: 'readonly' },
|
||||
{ name: 'toplevel_xid',
|
||||
signature: 'u',
|
||||
access: 'readonly' },
|
||||
{ name: 'cookie',
|
||||
signature: 'u',
|
||||
access: 'readonly' }],
|
||||
};
|
||||
|
||||
function Inhibitor(objectPath) {
|
||||
this._init(objectPath);
|
||||
}
|
||||
|
||||
Inhibitor.prototype = {
|
||||
_init: function(objectPath) {
|
||||
DBus.session.proxifyObject(this,
|
||||
"org.gnome.SessionManager",
|
||||
objectPath);
|
||||
this.isLoaded = false;
|
||||
this._loadingPropertiesCount = InhibitorIface.properties.length;
|
||||
for (let i = 0; i < InhibitorIface.properties.length; i++) {
|
||||
let propertyName = InhibitorIface.properties[i].name;
|
||||
this.GetRemote(propertyName, Lang.bind(this,
|
||||
function(value, exception) {
|
||||
if (exception)
|
||||
return;
|
||||
|
||||
this[propertyName] = value;
|
||||
this._loadingPropertiesCount--;
|
||||
|
||||
if (this._loadingPropertiesCount == 0) {
|
||||
this.isLoaded = true;
|
||||
this.emit("is-loaded");
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
};
|
||||
DBus.proxifyPrototype(Inhibitor.prototype, InhibitorIface);
|
||||
Signals.addSignalMethods(Inhibitor.prototype);
|
@ -2,7 +2,7 @@
|
||||
|
||||
// parse:
|
||||
// @params: caller-provided parameter object, or %null
|
||||
// @default: function-provided defaults object
|
||||
// @defaults: function-provided defaults object
|
||||
// @allowExtras: whether or not to allow properties not in @default
|
||||
//
|
||||
// Examines @params and fills in default values from @defaults for
|
||||
@ -10,24 +10,26 @@
|
||||
// @allowExtras is not %true, it will throw an error if @params
|
||||
// contains any properties that aren't in @defaults.
|
||||
//
|
||||
// If @params is %null, this returns @defaults.
|
||||
// If @params is %null, this returns the values from @defaults.
|
||||
//
|
||||
// Return value: the updated params
|
||||
// Return value: a new object, containing the merged parameters from
|
||||
// @params and @defaults
|
||||
function parse(params, defaults, allowExtras) {
|
||||
let ret = {}, prop;
|
||||
|
||||
if (!params)
|
||||
return defaults;
|
||||
params = {};
|
||||
|
||||
if (!allowExtras) {
|
||||
for (let prop in params) {
|
||||
if (!(prop in defaults))
|
||||
throw new Error('Unrecognized parameter "' + prop + '"');
|
||||
}
|
||||
for (prop in params) {
|
||||
if (!(prop in defaults) && !allowExtras)
|
||||
throw new Error('Unrecognized parameter "' + prop + '"');
|
||||
ret[prop] = params[prop];
|
||||
}
|
||||
|
||||
for (let prop in defaults) {
|
||||
if (!(prop in params))
|
||||
params[prop] = defaults[prop];
|
||||
for (prop in defaults) {
|
||||
if (!(prop in params))
|
||||
ret[prop] = defaults[prop];
|
||||
}
|
||||
|
||||
return params;
|
||||
return ret;
|
||||
}
|
361
js/misc/telepathy.js
Normal file
@ -0,0 +1,361 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
|
||||
// D-Bus utils
|
||||
function nameToPath(name) {
|
||||
return '/' + name.replace(/\./g, '/');
|
||||
};
|
||||
|
||||
function pathToName(path) {
|
||||
if (path[0] != '/')
|
||||
throw new Error('not a D-Bus path: ' + path);
|
||||
return path.substr(1).replace(/\//g, '.');
|
||||
};
|
||||
|
||||
// This is tp_escape_as_identifier() from telepathy-glib
|
||||
function escapeAsIdentifier(name) {
|
||||
if (!name)
|
||||
return '_';
|
||||
|
||||
// first char is replaced with _XX if it's non-alpha,
|
||||
// later chars are replaced with _XX if they're non-alphanumeric
|
||||
if (name.length == 1) {
|
||||
return name.replace(/[^a-zA-Z]/, _hexEscape);
|
||||
} else {
|
||||
return (name[0].replace(/[^a-zA-Z]/, _hexEscape) +
|
||||
name.substring(1).replace(/[^a-zA-Z0-9]/g, _hexEscape));
|
||||
}
|
||||
}
|
||||
|
||||
function _hexEscape(ch) {
|
||||
return '_' + ch.charCodeAt(0).toString(16);
|
||||
}
|
||||
|
||||
// Telepathy D-Bus interface definitions. Note that most of these are
|
||||
// incomplete, and only cover the methods/properties/signals that
|
||||
// we're currently using.
|
||||
|
||||
const TELEPATHY = 'org.freedesktop.Telepathy';
|
||||
|
||||
const CLIENT_NAME = TELEPATHY + '.Client';
|
||||
const ClientIface = {
|
||||
name: CLIENT_NAME,
|
||||
properties: [
|
||||
{ name: 'Interfaces',
|
||||
signature: 'as',
|
||||
access: 'read' }
|
||||
]
|
||||
};
|
||||
|
||||
const CLIENT_APPROVER_NAME = TELEPATHY + '.Client.Approver';
|
||||
const ClientApproverIface = {
|
||||
name: CLIENT_APPROVER_NAME,
|
||||
methods: [
|
||||
{ name: 'AddDispatchOperation',
|
||||
inSignature: 'a(oa{sv})oa{sv}',
|
||||
outSignature: '' }
|
||||
],
|
||||
properties: [
|
||||
{ name: 'ApproverChannelFilter',
|
||||
signature: 'aa{sv}',
|
||||
access: 'read' }
|
||||
]
|
||||
};
|
||||
|
||||
const CLIENT_HANDLER_NAME = TELEPATHY + '.Client.Handler';
|
||||
const ClientHandlerIface = {
|
||||
name: CLIENT_HANDLER_NAME,
|
||||
methods: [
|
||||
{ name: 'HandleChannels',
|
||||
inSignature: 'ooa(oa{sv})aota{sv}',
|
||||
outSignature: '' }
|
||||
],
|
||||
properties: [
|
||||
{ name: 'HandlerChannelFilter',
|
||||
signature: 'aa{sv}',
|
||||
access: 'read' }
|
||||
]
|
||||
};
|
||||
|
||||
const CLIENT_OBSERVER_NAME = TELEPATHY + '.Client.Observer';
|
||||
const ClientObserverIface = {
|
||||
name: CLIENT_OBSERVER_NAME,
|
||||
methods: [
|
||||
{ name: 'ObserveChannels',
|
||||
inSignature: 'ooa(oa{sv})oaoa{sv}',
|
||||
outSignature: '' }
|
||||
],
|
||||
properties: [
|
||||
{ name: 'ObserverChannelFilter',
|
||||
signature: 'aa{sv}',
|
||||
access: 'read' }
|
||||
]
|
||||
};
|
||||
|
||||
const CHANNEL_DISPATCH_OPERATION_NAME = TELEPATHY + '.ChannelDispatchOperation';
|
||||
const ChannelDispatchOperationIface = {
|
||||
name: CHANNEL_DISPATCH_OPERATION_NAME,
|
||||
methods: [
|
||||
{ name: 'HandleWith',
|
||||
inSignature: 's',
|
||||
outSignature: '' },
|
||||
{ name: 'Claim',
|
||||
inSignature: '',
|
||||
outSignature: '' }
|
||||
]
|
||||
};
|
||||
let ChannelDispatchOperation = DBus.makeProxyClass(ChannelDispatchOperationIface);
|
||||
|
||||
const CONNECTION_NAME = TELEPATHY + '.Connection';
|
||||
const ConnectionIface = {
|
||||
name: CONNECTION_NAME,
|
||||
signals: [
|
||||
{ name: 'StatusChanged',
|
||||
inSignature: 'uu' }
|
||||
]
|
||||
};
|
||||
let Connection = DBus.makeProxyClass(ConnectionIface);
|
||||
|
||||
const ConnectionStatus = {
|
||||
CONNECTED: 0,
|
||||
CONNECTING: 1,
|
||||
DISCONNECTED: 2
|
||||
};
|
||||
|
||||
const CONNECTION_ALIASING_NAME = CONNECTION_NAME + '.Interface.Aliasing';
|
||||
const ConnectionAliasingIface = {
|
||||
name: CONNECTION_ALIASING_NAME,
|
||||
methods: [
|
||||
{ name: 'RequestAliases',
|
||||
inSignature: 'au',
|
||||
outSignature: 'as'
|
||||
}
|
||||
],
|
||||
signals: [
|
||||
{ name: 'AliasesChanged',
|
||||
inSignature: 'a(us)' }
|
||||
]
|
||||
};
|
||||
let ConnectionAliasing = DBus.makeProxyClass(ConnectionAliasingIface);
|
||||
|
||||
const CONNECTION_AVATARS_NAME = CONNECTION_NAME + '.Interface.Avatars';
|
||||
const ConnectionAvatarsIface = {
|
||||
name: CONNECTION_AVATARS_NAME,
|
||||
methods: [
|
||||
{ name: 'GetKnownAvatarTokens',
|
||||
inSignature: 'au',
|
||||
outSignature: 'a{us}'
|
||||
},
|
||||
{ name: 'RequestAvatars',
|
||||
inSignature: 'au',
|
||||
outSignature: ''
|
||||
}
|
||||
],
|
||||
signals: [
|
||||
{ name: 'AvatarRetrieved',
|
||||
inSignature: 'usays'
|
||||
},
|
||||
{ name: 'AvatarUpdated',
|
||||
inSignature: 'us'
|
||||
}
|
||||
]
|
||||
};
|
||||
let ConnectionAvatars = DBus.makeProxyClass(ConnectionAvatarsIface);
|
||||
|
||||
const CONNECTION_CONTACTS_NAME = CONNECTION_NAME + '.Interface.Contacts';
|
||||
const ConnectionContactsIface = {
|
||||
name: CONNECTION_CONTACTS_NAME,
|
||||
methods: [
|
||||
{ name: 'GetContactAttributes',
|
||||
inSignature: 'auasb',
|
||||
outSignature: 'a{ua{sv}}'
|
||||
}
|
||||
]
|
||||
};
|
||||
let ConnectionContacts = DBus.makeProxyClass(ConnectionContactsIface);
|
||||
|
||||
const CONNECTION_REQUESTS_NAME = CONNECTION_NAME + '.Interface.Requests';
|
||||
const ConnectionRequestsIface = {
|
||||
name: CONNECTION_REQUESTS_NAME,
|
||||
methods: [
|
||||
{ name: 'CreateChannel',
|
||||
inSignature: 'a{sv}',
|
||||
outSignature: 'oa{sv}'
|
||||
},
|
||||
{ name: 'EnsureChannel',
|
||||
inSignature: 'a{sv}',
|
||||
outSignature: 'boa{sv}'
|
||||
}
|
||||
],
|
||||
properties: [
|
||||
{ name: 'Channels',
|
||||
signature: 'a(oa{sv})',
|
||||
access: 'read' }
|
||||
],
|
||||
signals: [
|
||||
{ name: 'NewChannels',
|
||||
inSignature: 'a(oa{sv})'
|
||||
},
|
||||
{ name: 'ChannelClosed',
|
||||
inSignature: 'o'
|
||||
}
|
||||
]
|
||||
};
|
||||
let ConnectionRequests = DBus.makeProxyClass(ConnectionRequestsIface);
|
||||
|
||||
const CONNECTION_SIMPLE_PRESENCE_NAME = CONNECTION_NAME + '.Interface.SimplePresence';
|
||||
const ConnectionSimplePresenceIface = {
|
||||
name: CONNECTION_SIMPLE_PRESENCE_NAME,
|
||||
methods: [
|
||||
{ name: 'SetPresence',
|
||||
inSignature: 'ss'
|
||||
},
|
||||
{ name: 'GetPresences',
|
||||
inSignature: 'au',
|
||||
outSignature: 'a{u(uss)}'
|
||||
}
|
||||
],
|
||||
signals: [
|
||||
{ name: 'PresencesChanged',
|
||||
inSignature: 'a{u(uss)}' }
|
||||
]
|
||||
};
|
||||
let ConnectionSimplePresence = DBus.makeProxyClass(ConnectionSimplePresenceIface);
|
||||
|
||||
const ConnectionPresenceType = {
|
||||
UNSET: 0,
|
||||
OFFLINE: 1,
|
||||
AVAILABLE: 2,
|
||||
AWAY: 3,
|
||||
EXTENDED_AWAY: 4,
|
||||
HIDDEN: 5,
|
||||
BUSY: 6,
|
||||
UNKNOWN: 7,
|
||||
ERROR: 8
|
||||
};
|
||||
|
||||
const HandleType = {
|
||||
NONE: 0,
|
||||
CONTACT: 1,
|
||||
ROOM: 2,
|
||||
LIST: 3,
|
||||
GROUP: 4
|
||||
};
|
||||
|
||||
const CHANNEL_NAME = TELEPATHY + '.Channel';
|
||||
const ChannelIface = {
|
||||
name: CHANNEL_NAME,
|
||||
signals: [
|
||||
{ name: 'Closed',
|
||||
inSignature: '' }
|
||||
]
|
||||
};
|
||||
let Channel = DBus.makeProxyClass(ChannelIface);
|
||||
|
||||
const CHANNEL_TEXT_NAME = CHANNEL_NAME + '.Type.Text';
|
||||
const ChannelTextIface = {
|
||||
name: CHANNEL_TEXT_NAME,
|
||||
methods: [
|
||||
{ name: 'ListPendingMessages',
|
||||
inSignature: 'b',
|
||||
outSignature: 'a(uuuuus)'
|
||||
},
|
||||
{ name: 'AcknowledgePendingMessages',
|
||||
inSignature: 'au',
|
||||
outSignature: ''
|
||||
},
|
||||
{ name: 'Send',
|
||||
inSignature: 'us',
|
||||
outSignature: ''
|
||||
}
|
||||
],
|
||||
signals: [
|
||||
{ name: 'Received',
|
||||
inSignature: 'uuuuus' },
|
||||
{ name: 'Sent',
|
||||
inSignature: 'uus' }
|
||||
]
|
||||
};
|
||||
let ChannelText = DBus.makeProxyClass(ChannelTextIface);
|
||||
|
||||
const ChannelTextMessageType = {
|
||||
NORMAL: 0,
|
||||
ACTION: 1,
|
||||
NOTICE: 2,
|
||||
AUTO_REPLY: 3,
|
||||
DELIVERY_REPORT: 4
|
||||
};
|
||||
|
||||
const CHANNEL_CONTACT_LIST_NAME = CHANNEL_NAME + '.Type.ContactList';
|
||||
// There is no interface associated with ContactList; it's just a
|
||||
// special kind of Channel.Interface.Group
|
||||
|
||||
const CHANNEL_GROUP_NAME = CHANNEL_NAME + '.Interface.Group';
|
||||
const ChannelGroupIface = {
|
||||
name: CHANNEL_GROUP_NAME,
|
||||
properties: [
|
||||
{ name: 'Members',
|
||||
signature: 'au',
|
||||
access: 'read' }
|
||||
],
|
||||
signals: [
|
||||
{ name: 'MembersChanged',
|
||||
inSignature: 'sauauauauuu' }
|
||||
]
|
||||
};
|
||||
let ChannelGroup = DBus.makeProxyClass(ChannelGroupIface);
|
||||
|
||||
const ACCOUNT_MANAGER_NAME = TELEPATHY + '.AccountManager';
|
||||
const AccountManagerIface = {
|
||||
name: ACCOUNT_MANAGER_NAME,
|
||||
properties: [
|
||||
{ name: 'ValidAccounts',
|
||||
signature: 'ao',
|
||||
access: 'read' }
|
||||
],
|
||||
signals: [
|
||||
{ name: 'AccountValidityChanged',
|
||||
inSignature: 'ob' }
|
||||
]
|
||||
};
|
||||
let AccountManager = DBus.makeProxyClass(AccountManagerIface);
|
||||
|
||||
const ACCOUNT_NAME = TELEPATHY + '.Account';
|
||||
const AccountIface = {
|
||||
name: ACCOUNT_NAME,
|
||||
properties: [
|
||||
{ name: 'Connection',
|
||||
signature: 'o',
|
||||
access: 'read' }
|
||||
]
|
||||
};
|
||||
let Account = DBus.makeProxyClass(AccountIface);
|
||||
|
||||
const CHANNEL_DISPATCHER_NAME = TELEPATHY + '.ChannelDispatcher';
|
||||
const ChannelDispatcherIface = {
|
||||
name: CHANNEL_DISPATCHER_NAME,
|
||||
methods: [
|
||||
{ name: 'EnsureChannel',
|
||||
inSignature: 'oa{sv}xs',
|
||||
outSignature: 'o' }
|
||||
]
|
||||
};
|
||||
let ChannelDispatcher = DBus.makeProxyClass(ChannelDispatcherIface);
|
||||
|
||||
const CHANNEL_REQUEST_NAME = TELEPATHY + '.ChannelRequest';
|
||||
const ChannelRequestIface = {
|
||||
name: CHANNEL_REQUEST_NAME,
|
||||
methods: [
|
||||
{ name: 'Proceed',
|
||||
inSignature: '',
|
||||
outSignature: '' }
|
||||
],
|
||||
signals: [
|
||||
{ name: 'Failed',
|
||||
signature: 'ss' },
|
||||
{ name: 'Succeeded',
|
||||
signature: '' }
|
||||
]
|
||||
};
|
||||
let ChannelRequest = DBus.makeProxyClass(ChannelRequestIface);
|
180
js/misc/util.js
Normal file
@ -0,0 +1,180 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
/* http://daringfireball.net/2010/07/improved_regex_for_matching_urls */
|
||||
const _urlRegexp = new RegExp('\\b(([a-z][\\w-]+:(/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)([^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:\'\\".,<>?«»“”‘’]))', 'gi');
|
||||
|
||||
// findUrls:
|
||||
// @str: string to find URLs in
|
||||
//
|
||||
// Searches @str for URLs and returns an array of objects with %url
|
||||
// properties showing the matched URL string, and %pos properties indicating
|
||||
// the position within @str where the URL was found.
|
||||
//
|
||||
// Return value: the list of match objects, as described above
|
||||
function findUrls(str) {
|
||||
let res = [], match;
|
||||
while ((match = _urlRegexp.exec(str)))
|
||||
res.push({ url: match[0], pos: match.index });
|
||||
return res;
|
||||
}
|
||||
|
||||
// spawn:
|
||||
// @argv: an argv array
|
||||
//
|
||||
// Runs @argv in the background, handling any errors that occur
|
||||
// when trying to start the program.
|
||||
function spawn(argv) {
|
||||
try {
|
||||
trySpawn(argv);
|
||||
} catch (err) {
|
||||
_handleSpawnError(argv[0], err);
|
||||
}
|
||||
}
|
||||
|
||||
// spawnCommandLine:
|
||||
// @command_line: a command line
|
||||
//
|
||||
// Runs @command_line in the background, handling any errors that
|
||||
// occur when trying to parse or start the program.
|
||||
function spawnCommandLine(command_line) {
|
||||
try {
|
||||
let [success, argc, argv] = GLib.shell_parse_argv(command_line);
|
||||
trySpawn(argv);
|
||||
} catch (err) {
|
||||
_handleSpawnError(command_line, err);
|
||||
}
|
||||
}
|
||||
|
||||
// spawnDesktop:
|
||||
// @id: a desktop file ID
|
||||
//
|
||||
// Spawns the desktop file identified by @id using startup notification,
|
||||
// etc, handling any errors that occur when trying to find or start
|
||||
// the program.
|
||||
function spawnDesktop(id) {
|
||||
try {
|
||||
trySpawnDesktop(id);
|
||||
} catch (err) {
|
||||
_handleSpawnError(id, err);
|
||||
}
|
||||
}
|
||||
|
||||
// trySpawn:
|
||||
// @argv: an argv array
|
||||
//
|
||||
// Runs @argv in the background. If launching @argv fails,
|
||||
// this will throw an error.
|
||||
function trySpawn(argv)
|
||||
{
|
||||
try {
|
||||
GLib.spawn_async(null, argv, null,
|
||||
GLib.SpawnFlags.SEARCH_PATH,
|
||||
null, null);
|
||||
} catch (err) {
|
||||
if (err.code == GLib.SpawnError.G_SPAWN_ERROR_NOENT) {
|
||||
err.message = _("Command not found");
|
||||
} else {
|
||||
// The exception from gjs contains an error string like:
|
||||
// Error invoking GLib.spawn_command_line_async: Failed to
|
||||
// execute child process "foo" (No such file or directory)
|
||||
// We are only interested in the part in the parentheses. (And
|
||||
// we can't pattern match the text, since it gets localized.)
|
||||
err.message = err.message.replace(/.*\((.+)\)/, '$1');
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// trySpawnCommandLine:
|
||||
// @command_line: a command line
|
||||
//
|
||||
// Runs @command_line in the background. If launching @command_line
|
||||
// fails, this will throw an error.
|
||||
function trySpawnCommandLine(command_line) {
|
||||
let success, argc, argv;
|
||||
|
||||
try {
|
||||
[success, argc, argv] = GLib.shell_parse_argv(command_line);
|
||||
} catch (err) {
|
||||
// Replace "Error invoking GLib.shell_parse_argv: " with
|
||||
// something nicer
|
||||
err.message = err.message.replace(/[^:]*: /, _("Could not parse command:") + "\n");
|
||||
throw err;
|
||||
}
|
||||
|
||||
trySpawn(argv);
|
||||
}
|
||||
|
||||
// trySpawnDesktop:
|
||||
// @id: a desktop file ID
|
||||
//
|
||||
// Spawns the desktop file identified by @id using startup notification.
|
||||
// On error, throws an exception.
|
||||
function trySpawnDesktop(id) {
|
||||
let app;
|
||||
|
||||
// shell_app_system_load_from_desktop_file() will end up returning
|
||||
// a stupid error message if the desktop file doesn't exist, but
|
||||
// that's the only case it returns an error for, so we just
|
||||
// substitute our own error in instead
|
||||
try {
|
||||
app = Shell.AppSystem.get_default().load_from_desktop_file(id + '.desktop');
|
||||
} catch (err) {
|
||||
throw new Error(_("No such application"));
|
||||
}
|
||||
|
||||
try {
|
||||
app.launch();
|
||||
} catch(err) {
|
||||
// see trySpawn
|
||||
err.message = err.message.replace(/.*\((.+)\)/, '$1');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function _handleSpawnError(command, err) {
|
||||
let title = _("Execution of '%s' failed:").format(command);
|
||||
|
||||
let source = new MessageTray.SystemNotificationSource();
|
||||
Main.messageTray.add(source);
|
||||
let notification = new MessageTray.Notification(source, title, err.message);
|
||||
notification.setTransient(true);
|
||||
source.notify(notification);
|
||||
}
|
||||
|
||||
// killall:
|
||||
// @processName: a process name
|
||||
//
|
||||
// Kills @processName. If no process with the given name is found,
|
||||
// this will fail silently.
|
||||
function killall(processName) {
|
||||
try {
|
||||
// pkill is more portable than killall, but on Linux at least
|
||||
// it won't match if you pass more than 15 characters of the
|
||||
// process name... However, if you use the '-f' flag to match
|
||||
// the entire command line, it will work, but we have to be
|
||||
// careful in that case that we can match
|
||||
// '/usr/bin/processName' but not 'gedit processName.c' or
|
||||
// whatever...
|
||||
|
||||
let argv = ['pkill', '-f', '^([^ ]*/)?' + processName + '($| )'];
|
||||
GLib.spawn_sync(null, argv, null, GLib.SpawnFlags.SEARCH_PATH, null, null);
|
||||
// It might be useful to return success/failure, but we'd need
|
||||
// a wrapper around WIFEXITED and WEXITSTATUS. Since none of
|
||||
// the current callers care, we don't bother.
|
||||
} catch (e) {
|
||||
logError(e, 'Failed to kill ' + processName);
|
||||
}
|
||||
}
|
140
js/perf/core.js
Normal file
@ -0,0 +1,140 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Scripting = imports.ui.scripting;
|
||||
|
||||
// This performance script measure the most important (core) performance
|
||||
// metrics for the shell. By looking at the output metrics of this script
|
||||
// someone should be able to get an idea of how well the shell is performing
|
||||
// on a particular system.
|
||||
|
||||
let METRICS = {
|
||||
overviewLatencyFirst:
|
||||
{ description: "Time to first frame after triggering overview, first time",
|
||||
units: "us" },
|
||||
overviewFpsFirst:
|
||||
{ description: "Frame rate when going to the overview, first time",
|
||||
units: "frames / s" },
|
||||
overviewLatencySubsequent:
|
||||
{ description: "Time to first frame after triggering overview, second time",
|
||||
units: "us"},
|
||||
overviewFpsSubsequent:
|
||||
{ description: "Frames rate when going to the overview, second time",
|
||||
units: "frames / s" },
|
||||
usedAfterOverview:
|
||||
{ description: "Malloc'ed bytes after the overview is shown once",
|
||||
units: "B" },
|
||||
leakedAfterOverview:
|
||||
{ description: "Additional malloc'ed bytes the second time the overview is shown",
|
||||
units: "B" }
|
||||
};
|
||||
|
||||
function run() {
|
||||
Scripting.defineScriptEvent("overviewShowStart", "Starting to show the overview");
|
||||
Scripting.defineScriptEvent("overviewShowDone", "Overview finished showing");
|
||||
Scripting.defineScriptEvent("afterShowHide", "After a show/hide cycle for the overview");
|
||||
|
||||
Main.overview.connect('shown', function() {
|
||||
Scripting.scriptEvent('overviewShowDone');
|
||||
});
|
||||
|
||||
yield Scripting.sleep(1000);
|
||||
yield Scripting.waitLeisure();
|
||||
for (let i = 0; i < 2; i++) {
|
||||
Scripting.scriptEvent('overviewShowStart');
|
||||
Main.overview.show();
|
||||
|
||||
yield Scripting.waitLeisure();
|
||||
Main.overview.hide();
|
||||
yield Scripting.waitLeisure();
|
||||
|
||||
global.gc();
|
||||
yield Scripting.sleep(1000);
|
||||
Scripting.collectStatistics();
|
||||
Scripting.scriptEvent('afterShowHide');
|
||||
}
|
||||
}
|
||||
|
||||
let showingOverview = false;
|
||||
let finishedShowingOverview = false;
|
||||
let overviewShowStart;
|
||||
let overviewFrames;
|
||||
let overviewLatency;
|
||||
let mallocUsedSize = 0;
|
||||
let overviewShowCount = 0;
|
||||
let firstOverviewUsedSize;
|
||||
let haveSwapComplete = false;
|
||||
|
||||
function script_overviewShowStart(time) {
|
||||
showingOverview = true;
|
||||
finishedShowingOverview = false;
|
||||
overviewShowStart = time;
|
||||
overviewFrames = 0;
|
||||
}
|
||||
|
||||
function script_overviewShowDone(time) {
|
||||
// We've set up the state at the end of the zoom out, but we
|
||||
// need to wait for one more frame to paint before we count
|
||||
// ourselves as done.
|
||||
finishedShowingOverview = true;
|
||||
}
|
||||
|
||||
function script_afterShowHide(time) {
|
||||
if (overviewShowCount == 1) {
|
||||
METRICS.usedAfterOverview.value = mallocUsedSize;
|
||||
} else {
|
||||
METRICS.leakedAfterOverview.value = mallocUsedSize - METRICS.usedAfterOverview.value;
|
||||
}
|
||||
}
|
||||
|
||||
function malloc_usedSize(time, bytes) {
|
||||
mallocUsedSize = bytes;
|
||||
}
|
||||
|
||||
function _frameDone(time) {
|
||||
if (showingOverview) {
|
||||
if (overviewFrames == 0)
|
||||
overviewLatency = time - overviewShowStart;
|
||||
|
||||
overviewFrames++;
|
||||
}
|
||||
|
||||
if (finishedShowingOverview) {
|
||||
showingOverview = false;
|
||||
finishedShowingOverview = false;
|
||||
overviewShowCount++;
|
||||
|
||||
let dt = (time - (overviewShowStart + overviewLatency)) / 1000000;
|
||||
|
||||
// If we see a start frame and an end frame, that would
|
||||
// be 1 frame for a FPS computation, hence the '- 1'
|
||||
let fps = (overviewFrames - 1) / dt;
|
||||
|
||||
if (overviewShowCount == 1) {
|
||||
METRICS.overviewLatencyFirst.value = overviewLatency;
|
||||
METRICS.overviewFpsFirst.value = fps;
|
||||
} else {
|
||||
METRICS.overviewLatencySubsequent.value = overviewLatency;
|
||||
METRICS.overviewFpsSubsequent.value = fps;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function glx_swapComplete(time, swapTime) {
|
||||
haveSwapComplete = true;
|
||||
|
||||
_frameDone(swapTime);
|
||||
}
|
||||
|
||||
function clutter_stagePaintDone(time) {
|
||||
// If we aren't receiving GLXBufferSwapComplete events, then we approximate
|
||||
// the time the user sees a frame with the time we finished doing drawing
|
||||
// commands for the frame. This doesn't take into account the time for
|
||||
// the GPU to finish painting, and the time for waiting for the buffer
|
||||
// swap, but if this are uniform - every frame takes the same time to draw -
|
||||
// then it won't upset our FPS calculation, though the latency value
|
||||
// will be slightly too low.
|
||||
|
||||
if (!haveSwapComplete)
|
||||
_frameDone(time);
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
jsuidir = $(pkgdatadir)/js/ui
|
||||
|
||||
dist_jsui_DATA = \
|
||||
altTab.js \
|
||||
appDisplay.js \
|
||||
appFavorites.js \
|
||||
calendar.js \
|
||||
chrome.js \
|
||||
dash.js \
|
||||
dnd.js \
|
||||
docDisplay.js \
|
||||
environment.js \
|
||||
extensionSystem.js \
|
||||
genericDisplay.js \
|
||||
lightbox.js \
|
||||
link.js \
|
||||
lookingGlass.js \
|
||||
main.js \
|
||||
messageTray.js \
|
||||
notificationDaemon.js \
|
||||
overview.js \
|
||||
panel.js \
|
||||
placeDisplay.js \
|
||||
runDialog.js \
|
||||
search.js \
|
||||
shellDBus.js \
|
||||
sidebar.js \
|
||||
statusMenu.js \
|
||||
tweener.js \
|
||||
widget.js \
|
||||
widgetBox.js \
|
||||
windowManager.js \
|
||||
workspaces.js
|
628
js/ui/altTab.js
@ -1,12 +1,9 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
@ -14,21 +11,19 @@ const St = imports.gi.St;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const POPUP_ARROW_COLOR = new Clutter.Color();
|
||||
POPUP_ARROW_COLOR.from_pixel(0xffffffff);
|
||||
const POPUP_UNFOCUSED_ARROW_COLOR = new Clutter.Color();
|
||||
POPUP_UNFOCUSED_ARROW_COLOR.from_pixel(0x808080ff);
|
||||
const TRANSPARENT_COLOR = new Clutter.Color();
|
||||
TRANSPARENT_COLOR.from_pixel(0x00000000);
|
||||
|
||||
const POPUP_APPICON_SIZE = 96;
|
||||
const POPUP_LIST_SPACING = 8;
|
||||
const POPUP_SCROLL_TIME = 0.10; // seconds
|
||||
const POPUP_FADE_TIME = 0.1; // seconds
|
||||
|
||||
const APP_ICON_HOVER_TIMEOUT = 200; // milliseconds
|
||||
|
||||
const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
|
||||
|
||||
const THUMBNAIL_SIZE = 256;
|
||||
const THUMBNAIL_DEFAULT_SIZE = 256;
|
||||
const THUMBNAIL_POPUP_TIME = 500; // milliseconds
|
||||
const THUMBNAIL_FADE_TIME = 0.2; // seconds
|
||||
const THUMBNAIL_FADE_TIME = 0.1; // seconds
|
||||
|
||||
const iconSizes = [96, 64, 48, 32, 22];
|
||||
|
||||
function mod(a, b) {
|
||||
return (a + b) % b;
|
||||
@ -40,31 +35,94 @@ function AltTabPopup() {
|
||||
|
||||
AltTabPopup.prototype = {
|
||||
_init : function() {
|
||||
this.actor = new Clutter.Group({ reactive: true,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: global.screen_width,
|
||||
height: global.screen_height });
|
||||
this.actor = new Shell.GenericContainer({ name: 'altTabPopup',
|
||||
reactive: true,
|
||||
visible: false });
|
||||
|
||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
|
||||
this._haveModal = false;
|
||||
|
||||
this._currentApp = 0;
|
||||
this._currentWindow = 0;
|
||||
this._currentWindow = -1;
|
||||
this._thumbnailTimeoutId = 0;
|
||||
this._motionTimeoutId = 0;
|
||||
|
||||
this.thumbnailsVisible = false;
|
||||
|
||||
// Initially disable hover so we ignore the enter-event if
|
||||
// the switcher appears underneath the current pointer location
|
||||
this._disableHover();
|
||||
|
||||
global.stage.add_actor(this.actor);
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||
alloc.min_size = global.screen_width;
|
||||
alloc.natural_size = global.screen_width;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||
alloc.min_size = global.screen_height;
|
||||
alloc.natural_size = global.screen_height;
|
||||
},
|
||||
|
||||
_allocate: function (actor, box, flags) {
|
||||
let childBox = new Clutter.ActorBox();
|
||||
let primary = global.get_primary_monitor();
|
||||
|
||||
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
||||
let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
|
||||
let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
|
||||
let vPadding = this.actor.get_theme_node().get_vertical_padding();
|
||||
let hPadding = leftPadding + rightPadding;
|
||||
|
||||
// Allocate the appSwitcher
|
||||
// We select a size based on an icon size that does not overflow the screen
|
||||
let [childMinHeight, childNaturalHeight] = this._appSwitcher.actor.get_preferred_height(primary.width - hPadding);
|
||||
let [childMinWidth, childNaturalWidth] = this._appSwitcher.actor.get_preferred_width(childNaturalHeight);
|
||||
childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
|
||||
childBox.x2 = Math.min(childBox.x1 + primary.width - hPadding, childBox.x1 + childNaturalWidth);
|
||||
childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
|
||||
childBox.y2 = childBox.y1 + childNaturalHeight;
|
||||
this._appSwitcher.actor.allocate(childBox, flags);
|
||||
|
||||
// Allocate the thumbnails
|
||||
// We try to avoid overflowing the screen so we base the resulting size on
|
||||
// those calculations
|
||||
if (this._thumbnails) {
|
||||
let icon = this._appIcons[this._currentApp].actor;
|
||||
// Force a stage relayout to make sure we get the correct position
|
||||
global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, 0, 0);
|
||||
let [posX, posY] = icon.get_transformed_position();
|
||||
let thumbnailCenter = posX + icon.width / 2;
|
||||
let [childMinWidth, childNaturalWidth] = this._thumbnails.actor.get_preferred_width(-1);
|
||||
childBox.x1 = Math.max(primary.x + leftPadding, Math.floor(thumbnailCenter - childNaturalWidth / 2));
|
||||
if (childBox.x1 + childNaturalWidth > primary.x + primary.width - hPadding) {
|
||||
let offset = childBox.x1 + childNaturalWidth - primary.width + hPadding;
|
||||
childBox.x1 = Math.max(primary.x + leftPadding, childBox.x1 - offset - hPadding);
|
||||
}
|
||||
|
||||
let spacing = this.actor.get_theme_node().get_length('spacing');
|
||||
|
||||
childBox.x2 = childBox.x1 + childNaturalWidth;
|
||||
if (childBox.x2 > primary.x + primary.width - rightPadding)
|
||||
childBox.x2 = primary.x + primary.width - rightPadding;
|
||||
childBox.y1 = this._appSwitcher.actor.allocation.y2 + spacing;
|
||||
this._thumbnails.addClones(primary.height - bottomPadding - childBox.y1);
|
||||
let [childMinHeight, childNaturalHeight] = this._thumbnails.actor.get_preferred_height(-1);
|
||||
childBox.y2 = childBox.y1 + childNaturalHeight;
|
||||
this._thumbnails.actor.allocate(childBox, flags);
|
||||
}
|
||||
},
|
||||
|
||||
show : function(backward) {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
let apps = tracker.get_running_apps ("");
|
||||
let apps = tracker.get_running_apps ('');
|
||||
|
||||
if (!apps.length)
|
||||
return false;
|
||||
@ -73,21 +131,17 @@ AltTabPopup.prototype = {
|
||||
return false;
|
||||
this._haveModal = true;
|
||||
|
||||
this._keyPressEventId = global.stage.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
||||
this._keyReleaseEventId = global.stage.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
||||
this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
||||
this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
||||
|
||||
this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside));
|
||||
this.actor.connect('scroll-event', Lang.bind(this, this._onScroll));
|
||||
|
||||
this._appSwitcher = new AppSwitcher(apps);
|
||||
this._appSwitcher = new AppSwitcher(apps, this);
|
||||
this.actor.add_actor(this._appSwitcher.actor);
|
||||
this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated));
|
||||
this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered));
|
||||
|
||||
let primary = global.get_primary_monitor();
|
||||
this._appSwitcher.actor.x = primary.x + Math.floor((primary.width - this._appSwitcher.actor.width) / 2);
|
||||
this._appSwitcher.actor.y = primary.y + Math.floor((primary.height - this._appSwitcher.actor.height) / 2);
|
||||
|
||||
this._appIcons = this._appSwitcher.icons;
|
||||
|
||||
// Make the initial selection
|
||||
@ -112,8 +166,9 @@ AltTabPopup.prototype = {
|
||||
this._select(0, 1, true);
|
||||
else
|
||||
this._select(1);
|
||||
} else
|
||||
} else {
|
||||
this._select(1);
|
||||
}
|
||||
}
|
||||
|
||||
// There's a race condition; if the user released Alt before
|
||||
@ -121,12 +176,20 @@ AltTabPopup.prototype = {
|
||||
// https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
|
||||
// details.) So we check now. (Have to do this after updating
|
||||
// selection.)
|
||||
let mods = global.get_modifier_keys();
|
||||
let [x, y, mods] = global.get_pointer();
|
||||
if (!(mods & Gdk.ModifierType.MOD1_MASK)) {
|
||||
this._finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.actor.opacity = 0;
|
||||
this.actor.show();
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
time: POPUP_FADE_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
@ -138,10 +201,16 @@ AltTabPopup.prototype = {
|
||||
},
|
||||
|
||||
_nextWindow : function() {
|
||||
// We actually want the second window if we're in the unset state
|
||||
if (this._currentWindow == -1)
|
||||
this._currentWindow = 0;
|
||||
return mod(this._currentWindow + 1,
|
||||
this._appIcons[this._currentApp].cachedWindows.length);
|
||||
},
|
||||
_previousWindow : function() {
|
||||
// Also assume second window here
|
||||
if (this._currentWindow == -1)
|
||||
this._currentWindow = 1;
|
||||
return mod(this._currentWindow - 1,
|
||||
this._appIcons[this._currentApp].cachedWindows.length);
|
||||
},
|
||||
@ -149,48 +218,56 @@ AltTabPopup.prototype = {
|
||||
_keyPressEvent : function(actor, event) {
|
||||
let keysym = event.get_key_symbol();
|
||||
let shift = (Shell.get_event_state(event) & Clutter.ModifierType.SHIFT_MASK);
|
||||
// X allows servers to represent Shift+Tab in two different ways
|
||||
if (shift && keysym == Clutter.Tab)
|
||||
keysym = Clutter.ISO_Left_Tab;
|
||||
|
||||
this._disableHover();
|
||||
|
||||
// The WASD stuff is for debugging in Xephyr, where the arrow
|
||||
// keys aren't mapped correctly
|
||||
|
||||
if (keysym == Clutter.grave)
|
||||
this._select(this._currentApp, shift ? this._previousWindow() : this._nextWindow());
|
||||
this._select(this._currentApp, this._nextWindow());
|
||||
else if (keysym == Clutter.asciitilde)
|
||||
this._select(this._currentApp, this._previousWindow());
|
||||
else if (keysym == Clutter.Escape)
|
||||
this.destroy();
|
||||
else if (this._thumbnailsFocused) {
|
||||
if (keysym == Clutter.Tab) {
|
||||
if (shift && this._currentWindow == 0)
|
||||
this._select(this._previousApp());
|
||||
else if (!shift && this._currentWindow == this._appIcons[this._currentApp].cachedWindows.length - 1)
|
||||
if (this._currentWindow == this._appIcons[this._currentApp].cachedWindows.length - 1)
|
||||
this._select(this._nextApp());
|
||||
else
|
||||
this._select(this._currentApp, shift ? this._previousWindow() : this._nextWindow());
|
||||
} else if (keysym == Clutter.Left || keysym == Clutter.a)
|
||||
this._select(this._currentApp, this._nextWindow());
|
||||
} else if (keysym == Clutter.ISO_Left_Tab) {
|
||||
if (this._currentWindow == 0 || this._currentWindow == -1)
|
||||
this._select(this._previousApp());
|
||||
else
|
||||
this._select(this._currentApp, this._previousWindow());
|
||||
} else if (keysym == Clutter.Left)
|
||||
this._select(this._currentApp, this._previousWindow());
|
||||
else if (keysym == Clutter.Right || keysym == Clutter.d)
|
||||
else if (keysym == Clutter.Right)
|
||||
this._select(this._currentApp, this._nextWindow());
|
||||
else if (keysym == Clutter.Up || keysym == Clutter.w)
|
||||
else if (keysym == Clutter.Up)
|
||||
this._select(this._currentApp, null, true);
|
||||
} else {
|
||||
if (keysym == Clutter.Tab)
|
||||
this._select(shift ? this._previousApp() : this._nextApp());
|
||||
else if (keysym == Clutter.Left || keysym == Clutter.a)
|
||||
this._select(this._previousApp());
|
||||
else if (keysym == Clutter.Right || keysym == Clutter.d)
|
||||
this._select(this._nextApp());
|
||||
else if (keysym == Clutter.Down || keysym == Clutter.s)
|
||||
this._select(this._currentApp, this._currentWindow);
|
||||
else if (keysym == Clutter.ISO_Left_Tab)
|
||||
this._select(this._previousApp());
|
||||
else if (keysym == Clutter.Left)
|
||||
this._select(this._previousApp());
|
||||
else if (keysym == Clutter.Right)
|
||||
this._select(this._nextApp());
|
||||
else if (keysym == Clutter.Down)
|
||||
this._select(this._currentApp, 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_keyReleaseEvent : function(actor, event) {
|
||||
let keysym = event.get_key_symbol();
|
||||
let [x, y, mods] = global.get_pointer();
|
||||
let state = mods & Clutter.ModifierType.MOD1_MASK;
|
||||
|
||||
if (keysym == Clutter.Alt_L || keysym == Clutter.Alt_R)
|
||||
if (state == 0)
|
||||
this._finish();
|
||||
|
||||
return true;
|
||||
@ -200,7 +277,7 @@ AltTabPopup.prototype = {
|
||||
let direction = event.get_scroll_direction();
|
||||
if (direction == Clutter.ScrollDirection.UP) {
|
||||
if (this._thumbnailsFocused) {
|
||||
if (this._currentWindow == 0)
|
||||
if (this._currentWindow == 0 || this._currentWindow == -1)
|
||||
this._select(this._previousApp());
|
||||
else
|
||||
this._select(this._currentApp, this._previousWindow());
|
||||
@ -234,10 +311,17 @@ AltTabPopup.prototype = {
|
||||
_appActivated : function(appSwitcher, n) {
|
||||
// If the user clicks on the selected app, activate the
|
||||
// selected window; otherwise (eg, they click on an app while
|
||||
// !mouseActive) activate the first window of the clicked-on
|
||||
// app.
|
||||
let window = (n == this._currentApp) ? this._currentWindow : 0;
|
||||
Main.activateWindow(this._appIcons[n].cachedWindows[window]);
|
||||
// !mouseActive) activate the the clicked-on app.
|
||||
if (n == this._currentApp) {
|
||||
let window;
|
||||
if (this._currentWindow >= 0)
|
||||
window = this._appIcons[this._currentApp].cachedWindows[this._currentWindow];
|
||||
else
|
||||
window = null;
|
||||
this._appIcons[this._currentApp].app.activate_window(window, global.get_current_time());
|
||||
} else {
|
||||
this._appIcons[n].app.activate_window(null, global.get_current_time());
|
||||
}
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
@ -249,7 +333,8 @@ AltTabPopup.prototype = {
|
||||
},
|
||||
|
||||
_windowActivated : function(thumbnailList, n) {
|
||||
Main.activateWindow(this._appIcons[this._currentApp].cachedWindows[n]);
|
||||
let appIcon = this._appIcons[this._currentApp];
|
||||
Main.activateWindow(appIcon.cachedWindows[n]);
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
@ -276,23 +361,35 @@ AltTabPopup.prototype = {
|
||||
|
||||
_finish : function() {
|
||||
let app = this._appIcons[this._currentApp];
|
||||
let window = app.cachedWindows[this._currentWindow];
|
||||
Main.activateWindow(window);
|
||||
if (this._currentWindow >= 0) {
|
||||
Main.activateWindow(app.cachedWindows[this._currentWindow]);
|
||||
} else {
|
||||
app.app.activate_window(null, global.get_current_time());
|
||||
}
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
destroy : function() {
|
||||
this.actor.destroy();
|
||||
if (this.actor.visible) {
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: POPUP_FADE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this.actor.destroy();
|
||||
})
|
||||
});
|
||||
} else
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
_onDestroy : function() {
|
||||
if (this._haveModal)
|
||||
Main.popModal(this.actor);
|
||||
|
||||
if (this._keyPressEventId)
|
||||
global.stage.disconnect(this._keyPressEventId);
|
||||
if (this._keyReleaseEventId)
|
||||
global.stage.disconnect(this._keyReleaseEventId);
|
||||
if (this._thumbnails)
|
||||
this._destroyThumbnails();
|
||||
|
||||
if (this._motionTimeoutId != 0)
|
||||
Mainloop.source_remove(this._motionTimeoutId);
|
||||
@ -339,7 +436,7 @@ AltTabPopup.prototype = {
|
||||
this._thumbnailsFocused = (window != null) && !forceAppFocus;
|
||||
|
||||
this._currentApp = app;
|
||||
this._currentWindow = window ? window : 0;
|
||||
this._currentWindow = window ? window : -1;
|
||||
this._appSwitcher.highlight(app, this._thumbnailsFocused);
|
||||
|
||||
if (window != null) {
|
||||
@ -351,19 +448,28 @@ AltTabPopup.prototype = {
|
||||
!forceAppFocus) {
|
||||
this._thumbnailTimeoutId = Mainloop.timeout_add (
|
||||
THUMBNAIL_POPUP_TIME,
|
||||
Lang.bind(this, function () {
|
||||
this._select(this._currentApp, 0, true);
|
||||
return false;
|
||||
}));
|
||||
Lang.bind(this, this._timeoutPopupThumbnails));
|
||||
}
|
||||
},
|
||||
|
||||
_timeoutPopupThumbnails: function() {
|
||||
if (!this._thumbnails)
|
||||
this._createThumbnails();
|
||||
this._thumbnailTimeoutId = 0;
|
||||
this._thumbnailsFocused = false;
|
||||
return false;
|
||||
},
|
||||
|
||||
_destroyThumbnails : function() {
|
||||
Tweener.addTween(this._thumbnails.actor,
|
||||
let thumbnailsActor = this._thumbnails.actor;
|
||||
Tweener.addTween(thumbnailsActor,
|
||||
{ opacity: 0,
|
||||
time: THUMBNAIL_FADE_TIME,
|
||||
transition: "easeOutQuad",
|
||||
onComplete: function() { this.destroy(); }
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
thumbnailsActor.destroy();
|
||||
this.thumbnailsVisible = false;
|
||||
})
|
||||
});
|
||||
this._thumbnails = null;
|
||||
},
|
||||
@ -375,38 +481,12 @@ AltTabPopup.prototype = {
|
||||
|
||||
this.actor.add_actor(this._thumbnails.actor);
|
||||
|
||||
let thumbnailCenter;
|
||||
if (this._thumbnails.actor.width < this._appSwitcher.actor.width) {
|
||||
// Center the thumbnails under the corresponding AppIcon.
|
||||
// If this is being called when the switcher is first
|
||||
// being brought up, then nothing will have been assigned
|
||||
// an allocation yet, and the get_transformed_position()
|
||||
// call will return 0,0.
|
||||
// (http://bugzilla.openedhand.com/show_bug.cgi?id=1115).
|
||||
// Calling clutter_actor_get_allocation_box() would force
|
||||
// it to properly allocate itself, but we can't call that
|
||||
// because it has an out-caller-allocates arg. So we use
|
||||
// clutter_stage_get_actor_at_pos(), which will force a
|
||||
// reallocation as a side effect.
|
||||
global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, 0, 0);
|
||||
|
||||
let icon = this._appIcons[this._currentApp].actor;
|
||||
let [stageX, stageY] = icon.get_transformed_position();
|
||||
thumbnailCenter = stageX + icon.width / 2;
|
||||
} else {
|
||||
// Center the thumbnails on the monitor
|
||||
let primary = global.get_primary_monitor();
|
||||
thumbnailCenter = primary.x + primary.width / 2;
|
||||
}
|
||||
|
||||
this._thumbnails.actor.x = Math.floor(thumbnailCenter - this._thumbnails.actor.width / 2);
|
||||
this._thumbnails.actor.y = this._appSwitcher.actor.y + this._appSwitcher.actor.height + POPUP_LIST_SPACING;
|
||||
|
||||
this._thumbnails.actor.opacity = 0;
|
||||
Tweener.addTween(this._thumbnails.actor,
|
||||
{ opacity: 255,
|
||||
time: THUMBNAIL_FADE_TIME,
|
||||
transition: "easeOutQuad"
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function () { this.thumbnailsVisible = true; })
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -417,23 +497,99 @@ function SwitcherList(squareItems) {
|
||||
|
||||
SwitcherList.prototype = {
|
||||
_init : function(squareItems) {
|
||||
this.actor = new St.Bin({ style_class: 'switcher-list' });
|
||||
this.actor = new Shell.GenericContainer({ style_class: 'switcher-list' });
|
||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocateTop));
|
||||
|
||||
// Here we use a GenericContainer so that we can force all the
|
||||
// children except the separator to have the same width.
|
||||
this._list = new Shell.GenericContainer();
|
||||
this._list.spacing = POPUP_LIST_SPACING;
|
||||
this._list = new Shell.GenericContainer({ style_class: 'switcher-list-item-container' });
|
||||
this._list.spacing = 0;
|
||||
this._list.connect('style-changed', Lang.bind(this, function() {
|
||||
this._list.spacing = this._list.get_theme_node().get_length('spacing');
|
||||
}));
|
||||
|
||||
this._list.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this._list.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this._list.connect('allocate', Lang.bind(this, this._allocate));
|
||||
|
||||
this.actor.add_actor(this._list);
|
||||
this._clipBin = new St.Bin({style_class: 'cbin'});
|
||||
this._clipBin.child = this._list;
|
||||
this.actor.add_actor(this._clipBin);
|
||||
|
||||
this._leftGradient = new St.BoxLayout({style_class: 'thumbnail-scroll-gradient-left', vertical: true});
|
||||
this._rightGradient = new St.BoxLayout({style_class: 'thumbnail-scroll-gradient-right', vertical: true});
|
||||
this.actor.add_actor(this._leftGradient);
|
||||
this.actor.add_actor(this._rightGradient);
|
||||
|
||||
// Those arrows indicate whether scrolling in one direction is possible
|
||||
this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
||||
pseudo_class: 'highlighted' });
|
||||
this._leftArrow.connect('repaint', Lang.bind(this,
|
||||
function (area) {
|
||||
Shell.draw_box_pointer(area, Shell.PointerDirection.LEFT);
|
||||
}));
|
||||
|
||||
this._rightArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
|
||||
pseudo_class: 'highlighted' });
|
||||
this._rightArrow.connect('repaint', Lang.bind(this,
|
||||
function (area) {
|
||||
Shell.draw_box_pointer(area, Shell.PointerDirection.RIGHT);
|
||||
}));
|
||||
|
||||
this.actor.add_actor(this._leftArrow);
|
||||
this.actor.add_actor(this._rightArrow);
|
||||
|
||||
this._items = [];
|
||||
this._highlighted = -1;
|
||||
this._separator = null;
|
||||
this._squareItems = squareItems;
|
||||
this._minSize = 0;
|
||||
this._scrollableRight = true;
|
||||
this._scrollableLeft = false;
|
||||
},
|
||||
|
||||
_allocateTop: function(actor, box, flags) {
|
||||
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
||||
let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
let scrollable = this._minSize > box.x2 - box.x1;
|
||||
|
||||
this._clipBin.allocate(box, flags);
|
||||
|
||||
childBox.x1 = 0;
|
||||
childBox.y1 = 0;
|
||||
childBox.x2 = this._leftGradient.width;
|
||||
childBox.y2 = this.actor.height;
|
||||
this._leftGradient.allocate(childBox, flags);
|
||||
this._leftGradient.opacity = (this._scrollableLeft && scrollable) ? 255 : 0;
|
||||
|
||||
childBox.x1 = (this.actor.allocation.x2 - this.actor.allocation.x1) - this._rightGradient.width;
|
||||
childBox.y1 = 0;
|
||||
childBox.x2 = childBox.x1 + this._rightGradient.width;
|
||||
childBox.y2 = this.actor.height;
|
||||
this._rightGradient.allocate(childBox, flags);
|
||||
this._rightGradient.opacity = (this._scrollableRight && scrollable) ? 255 : 0;
|
||||
|
||||
let arrowWidth = Math.floor(leftPadding / 3);
|
||||
let arrowHeight = arrowWidth * 2;
|
||||
childBox.x1 = leftPadding / 2;
|
||||
childBox.y1 = this.actor.height / 2 - arrowWidth;
|
||||
childBox.x2 = childBox.x1 + arrowWidth;
|
||||
childBox.y2 = childBox.y1 + arrowHeight;
|
||||
this._leftArrow.allocate(childBox, flags);
|
||||
this._leftArrow.opacity = this._leftGradient.opacity;
|
||||
|
||||
arrowWidth = Math.floor(rightPadding / 3);
|
||||
arrowHeight = arrowWidth * 2;
|
||||
childBox.x1 = this.actor.width - arrowWidth - rightPadding / 2;
|
||||
childBox.y1 = this.actor.height / 2 - arrowWidth;
|
||||
childBox.x2 = childBox.x1 + arrowWidth;
|
||||
childBox.y2 = childBox.y1 + arrowHeight;
|
||||
this._rightArrow.allocate(childBox, flags);
|
||||
this._rightArrow.opacity = this._rightGradient.opacity;
|
||||
},
|
||||
|
||||
addItem : function(item) {
|
||||
@ -444,34 +600,83 @@ SwitcherList.prototype = {
|
||||
this._list.add_actor(bbox);
|
||||
|
||||
let n = this._items.length;
|
||||
bbox.connect('clicked', Lang.bind(this, function () {
|
||||
this._itemActivated(n);
|
||||
}));
|
||||
bbox.connect('enter-event', Lang.bind(this, function () {
|
||||
this._itemEntered(n);
|
||||
}));
|
||||
bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); }));
|
||||
bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); }));
|
||||
|
||||
this._items.push(bbox);
|
||||
},
|
||||
|
||||
_onItemClicked: function (index) {
|
||||
this._itemActivated(index);
|
||||
},
|
||||
|
||||
_onItemEnter: function (index) {
|
||||
this._itemEntered(index);
|
||||
},
|
||||
|
||||
addSeparator: function () {
|
||||
let box = new St.Bin({ style_class: 'separator' })
|
||||
let box = new St.Bin({ style_class: 'separator' });
|
||||
this._separator = box;
|
||||
this._list.add_actor(box);
|
||||
},
|
||||
|
||||
highlight: function(index, justOutline) {
|
||||
if (this._highlighted != -1)
|
||||
this._items[this._highlighted].style_class = 'item-box';
|
||||
if (this._highlighted != -1) {
|
||||
this._items[this._highlighted].remove_style_pseudo_class('outlined');
|
||||
this._items[this._highlighted].remove_style_pseudo_class('selected');
|
||||
}
|
||||
|
||||
this._highlighted = index;
|
||||
|
||||
if (this._highlighted != -1) {
|
||||
if (justOutline)
|
||||
this._items[this._highlighted].style_class = 'outlined-item-box';
|
||||
this._items[this._highlighted].add_style_pseudo_class('outlined');
|
||||
else
|
||||
this._items[this._highlighted].style_class = 'selected-item-box';
|
||||
this._items[this._highlighted].add_style_pseudo_class('selected');
|
||||
}
|
||||
|
||||
let monitor = global.get_primary_monitor();
|
||||
let itemSize = this._items[index].allocation.x2 - this._items[index].allocation.x1;
|
||||
let [posX, posY] = this._items[index].get_transformed_position();
|
||||
posX += this.actor.x;
|
||||
if (posX + itemSize > monitor.width + monitor.x)
|
||||
this._scrollToRight();
|
||||
else if (posX < 0)
|
||||
this._scrollToLeft();
|
||||
|
||||
},
|
||||
|
||||
_scrollToLeft : function() {
|
||||
let x = this._items[this._highlighted].allocation.x1;
|
||||
this._scrollableRight = true;
|
||||
Tweener.addTween(this._list, { anchor_x: x,
|
||||
time: POPUP_SCROLL_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function () {
|
||||
if (this._highlighted == 0) {
|
||||
this._scrollableLeft = false;
|
||||
this.actor.queue_relayout();
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
_scrollToRight : function() {
|
||||
this._scrollableLeft = true;
|
||||
let monitor = global.get_primary_monitor();
|
||||
let padding = this.actor.get_theme_node().get_horizontal_padding();
|
||||
let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding();
|
||||
let x = this._items[this._highlighted].allocation.x2 - monitor.width + padding + parentPadding;
|
||||
Tweener.addTween(this._list, { anchor_x: x,
|
||||
time: POPUP_SCROLL_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function () {
|
||||
if (this._highlighted == this._items.length - 1) {
|
||||
this._scrollableRight = false;
|
||||
this.actor.queue_relayout();
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
_itemActivated: function(n) {
|
||||
@ -512,7 +717,8 @@ SwitcherList.prototype = {
|
||||
|
||||
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
||||
alloc.min_size = this._items.length * maxChildMin + separatorWidth + totalSpacing;
|
||||
alloc.nat_size = this._items.length * maxChildNat + separatorWidth + totalSpacing;
|
||||
alloc.natural_size = alloc.min_size;
|
||||
this._minSize = alloc.min_size;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||
@ -528,11 +734,11 @@ SwitcherList.prototype = {
|
||||
if (this._squareItems) {
|
||||
let [childMin, childNat] = this._maxChildWidth(-1);
|
||||
maxChildMin = Math.max(childMin, maxChildMin);
|
||||
maxChildNat = Math.max(childNat, maxChildNat);
|
||||
maxChildNat = maxChildMin;
|
||||
}
|
||||
|
||||
alloc.min_size = maxChildMin;
|
||||
alloc.nat_size = maxChildNat;
|
||||
alloc.natural_size = maxChildNat;
|
||||
},
|
||||
|
||||
_allocate: function (actor, box, flags) {
|
||||
@ -553,6 +759,18 @@ SwitcherList.prototype = {
|
||||
let x = 0;
|
||||
let children = this._list.get_children();
|
||||
let childBox = new Clutter.ActorBox();
|
||||
|
||||
let primary = global.get_primary_monitor();
|
||||
let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
|
||||
if (this.actor.allocation.x2 == primary.x + primary.width - parentRightPadding) {
|
||||
if (this._squareItems)
|
||||
childWidth = childHeight;
|
||||
else {
|
||||
let [childMin, childNat] = children[0].get_preferred_width(childHeight);
|
||||
childWidth = childMin;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
if (this._items.indexOf(children[i]) != -1) {
|
||||
let [childMin, childNat] = children[i].get_preferred_height(childWidth);
|
||||
@ -577,6 +795,14 @@ SwitcherList.prototype = {
|
||||
// we don't allocate it.
|
||||
}
|
||||
}
|
||||
|
||||
let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
|
||||
let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
|
||||
let topPadding = this.actor.get_theme_node().get_padding(St.Side.TOP);
|
||||
let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
|
||||
|
||||
// Clip the area for scrolling
|
||||
this._clipBin.set_clip(0, -topPadding, (this.actor.allocation.x2 - this.actor.allocation.x1) - leftPadding - rightPadding, this.actor.height + bottomPadding);
|
||||
}
|
||||
};
|
||||
|
||||
@ -589,23 +815,31 @@ function AppIcon(app) {
|
||||
AppIcon.prototype = {
|
||||
_init: function(app) {
|
||||
this.app = app;
|
||||
this.actor = new St.BoxLayout({ style_class: "alt-tab-app",
|
||||
this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
|
||||
vertical: true });
|
||||
this._icon = this.app.create_icon_texture(POPUP_APPICON_SIZE);
|
||||
this.actor.add(this._icon, { x_fill: false, y_fill: false } );
|
||||
this._label = new St.Label({ text: this.app.get_name() });
|
||||
this.actor.add(this._label, { x_fill: false });
|
||||
}
|
||||
}
|
||||
this.icon = null;
|
||||
this._iconBin = new St.Bin();
|
||||
|
||||
function AppSwitcher(apps) {
|
||||
this._init(apps);
|
||||
this.actor.add(this._iconBin, { x_fill: false, y_fill: false } );
|
||||
this.label = new St.Label({ text: this.app.get_name() });
|
||||
this.actor.add(this.label, { x_fill: false });
|
||||
},
|
||||
|
||||
set_size: function(size) {
|
||||
this.icon = this.app.create_icon_texture(size);
|
||||
this._iconBin.set_size(size, size);
|
||||
this._iconBin.child = this.icon;
|
||||
}
|
||||
};
|
||||
|
||||
function AppSwitcher(apps, altTabPopup) {
|
||||
this._init(apps, altTabPopup);
|
||||
}
|
||||
|
||||
AppSwitcher.prototype = {
|
||||
__proto__ : SwitcherList.prototype,
|
||||
|
||||
_init : function(apps) {
|
||||
_init : function(apps, altTabPopup) {
|
||||
SwitcherList.prototype._init.call(this, true);
|
||||
|
||||
// Construct the AppIcons, sort by time, add to the popup
|
||||
@ -636,6 +870,52 @@ AppSwitcher.prototype = {
|
||||
this._addIcon(otherIcons[i]);
|
||||
|
||||
this._curApp = -1;
|
||||
this._iconSize = 0;
|
||||
this._altTabPopup = altTabPopup;
|
||||
this._mouseTimeOutId = 0;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (actor, forWidth, alloc) {
|
||||
let j = 0;
|
||||
while(this._items.length > 1 && this._items[j].style_class != 'item-box') {
|
||||
j++;
|
||||
}
|
||||
let themeNode = this._items[j].get_theme_node();
|
||||
let iconPadding = themeNode.get_horizontal_padding();
|
||||
let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT);
|
||||
let [iconMinHeight, iconNaturalHeight] = this.icons[j].label.get_preferred_height(-1);
|
||||
let iconSpacing = iconNaturalHeight + iconPadding + iconBorder;
|
||||
let totalSpacing = this._list.spacing * (this._items.length - 1);
|
||||
if (this._separator)
|
||||
totalSpacing += this._separator.width + this._list.spacing;
|
||||
|
||||
// We just assume the whole screen here due to weirdness happing with the passed width
|
||||
let primary = global.get_primary_monitor();
|
||||
let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding();
|
||||
let availWidth = primary.width - parentPadding - this.actor.get_theme_node().get_horizontal_padding();
|
||||
let height = 0;
|
||||
|
||||
for(let i = 0; i < iconSizes.length; i++) {
|
||||
this._iconSize = iconSizes[i];
|
||||
height = iconSizes[i] + iconSpacing;
|
||||
let w = height * this._items.length + totalSpacing;
|
||||
if (w <= availWidth)
|
||||
break;
|
||||
}
|
||||
|
||||
if (this._items.length == 1) {
|
||||
this._iconSize = iconSizes[0];
|
||||
height = iconSizes[0] + iconSpacing;
|
||||
}
|
||||
|
||||
for(let i = 0; i < this.icons.length; i++) {
|
||||
if (this.icons[i].icon != null)
|
||||
break;
|
||||
this.icons[i].set_size(this._iconSize);
|
||||
}
|
||||
|
||||
alloc.min_size = height;
|
||||
alloc.natural_size = height;
|
||||
},
|
||||
|
||||
_allocate: function (actor, box, flags) {
|
||||
@ -657,22 +937,42 @@ AppSwitcher.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
// We override SwitcherList's _onItemEnter method to delay
|
||||
// activation when the thumbnail list is open
|
||||
_onItemEnter: function (index) {
|
||||
if (this._mouseTimeOutId != 0)
|
||||
Mainloop.source_remove(this._mouseTimeOutId);
|
||||
if (this._altTabPopup.thumbnailsVisible) {
|
||||
this._mouseTimeOutId = Mainloop.timeout_add(APP_ICON_HOVER_TIMEOUT,
|
||||
Lang.bind(this, function () {
|
||||
this._enterItem(index);
|
||||
this._mouseTimeOutId = 0;
|
||||
return false;
|
||||
}));
|
||||
} else
|
||||
this._itemEntered(index);
|
||||
},
|
||||
|
||||
_enterItem: function(index) {
|
||||
let [x, y, mask] = global.get_pointer();
|
||||
let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
|
||||
if (this._items[index].contains(pickedActor))
|
||||
this._itemEntered(index);
|
||||
},
|
||||
|
||||
// We override SwitcherList's highlight() method to also deal with
|
||||
// the AppSwitcher->ThumbnailList arrows. Apps with only 1 window
|
||||
// will hide their arrows by default, but show them when their
|
||||
// thumbnails are visible (ie, when the app icon is supposed to be
|
||||
// in justOutline mode). Apps with multiple windows will normally
|
||||
// show a dim arrow, but show a bright arrow when they are
|
||||
// highlighted; their redraw handler will use the right color
|
||||
// based on this._curApp; we just need to do a queue_relayout() to
|
||||
// force it to redraw. (queue_redraw() doesn't work because
|
||||
// ShellDrawingArea only redraws on allocate.)
|
||||
// highlighted.
|
||||
highlight : function(n, justOutline) {
|
||||
if (this._curApp != -1) {
|
||||
if (this.icons[this._curApp].cachedWindows.length == 1)
|
||||
this._arrows[this._curApp].hide();
|
||||
else
|
||||
this._arrows[this._curApp].queue_relayout();
|
||||
this._arrows[this._curApp].remove_style_pseudo_class('highlighted');
|
||||
}
|
||||
|
||||
SwitcherList.prototype.highlight.call(this, n, justOutline);
|
||||
@ -682,7 +982,7 @@ AppSwitcher.prototype = {
|
||||
if (justOutline && this.icons[this._curApp].cachedWindows.length == 1)
|
||||
this._arrows[this._curApp].show();
|
||||
else
|
||||
this._arrows[this._curApp].queue_relayout();
|
||||
this._arrows[this._curApp].add_style_pseudo_class('highlighted');
|
||||
}
|
||||
},
|
||||
|
||||
@ -691,12 +991,10 @@ AppSwitcher.prototype = {
|
||||
this.addItem(appIcon.actor);
|
||||
|
||||
let n = this._arrows.length;
|
||||
let arrow = new St.DrawingArea();
|
||||
arrow.connect('redraw', Lang.bind(this,
|
||||
function (area, texture) {
|
||||
Shell.draw_box_pointer(texture, Shell.PointerDirection.DOWN,
|
||||
TRANSPARENT_COLOR,
|
||||
this._curApp == n ? POPUP_ARROW_COLOR : POPUP_UNFOCUSED_ARROW_COLOR);
|
||||
let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' });
|
||||
arrow.connect('repaint', Lang.bind(this,
|
||||
function (area) {
|
||||
Shell.draw_box_pointer(area, Shell.PointerDirection.DOWN);
|
||||
}));
|
||||
this._list.add_actor(arrow);
|
||||
this._arrows.push(arrow);
|
||||
@ -731,44 +1029,72 @@ ThumbnailList.prototype = {
|
||||
|
||||
let activeWorkspace = global.screen.get_active_workspace();
|
||||
|
||||
// We fake the value of "separatorAdded" when the app has no window
|
||||
// We fake the value of 'separatorAdded' when the app has no window
|
||||
// on the current workspace, to avoid displaying a useless separator in
|
||||
// that case.
|
||||
let separatorAdded = windows.length == 0 || windows[0].get_workspace() != activeWorkspace;
|
||||
|
||||
this._labels = new Array();
|
||||
this._thumbnailBins = new Array();
|
||||
this._clones = new Array();
|
||||
this._windows = windows;
|
||||
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
if (!separatorAdded && windows[i].get_workspace() != activeWorkspace) {
|
||||
this.addSeparator();
|
||||
separatorAdded = true;
|
||||
}
|
||||
|
||||
let mutterWindow = windows[i].get_compositor_private();
|
||||
let windowTexture = mutterWindow.get_texture ();
|
||||
let [width, height] = windowTexture.get_size();
|
||||
let scale = Math.min(1.0, THUMBNAIL_SIZE / width, THUMBNAIL_SIZE / height);
|
||||
|
||||
let box = new St.BoxLayout({ style_class: "thumbnail-box",
|
||||
let box = new St.BoxLayout({ style_class: 'thumbnail-box',
|
||||
vertical: true });
|
||||
|
||||
let bin = new St.Bin({ style_class: "thumbnail" });
|
||||
let clone = new Clutter.Clone ({ source: windowTexture,
|
||||
reactive: true,
|
||||
width: width * scale,
|
||||
height: height * scale });
|
||||
let bin = new St.Bin({ style_class: 'thumbnail' });
|
||||
|
||||
bin.add_actor(clone);
|
||||
box.add_actor(bin);
|
||||
this._thumbnailBins.push(bin);
|
||||
|
||||
let title = windows[i].get_title();
|
||||
if (title) {
|
||||
let name = new St.Label({ text: title });
|
||||
// St.Label doesn't support text-align so use a Bin
|
||||
let bin = new St.Bin({ x_align: St.Align.MIDDLE });
|
||||
this._labels.push(bin);
|
||||
bin.add_actor(name);
|
||||
box.add_actor(bin);
|
||||
}
|
||||
|
||||
this.addItem(box);
|
||||
}
|
||||
},
|
||||
|
||||
addClones : function (availHeight) {
|
||||
if (!this._thumbnailBins.length)
|
||||
return;
|
||||
let totalPadding = this._items[0].get_theme_node().get_horizontal_padding() + this._items[0].get_theme_node().get_vertical_padding();
|
||||
totalPadding += this.actor.get_theme_node().get_horizontal_padding() + this.actor.get_theme_node().get_vertical_padding();
|
||||
let [labelMinHeight, labelNaturalHeight] = this._labels[0].get_preferred_height(-1);
|
||||
let spacing = this._items[0].child.get_theme_node().get_length('spacing');
|
||||
|
||||
availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, THUMBNAIL_DEFAULT_SIZE);
|
||||
let binHeight = availHeight + this._items[0].get_theme_node().get_vertical_padding() + this.actor.get_theme_node().get_vertical_padding() - spacing;
|
||||
binHeight = Math.min(THUMBNAIL_DEFAULT_SIZE, binHeight);
|
||||
|
||||
for (let i = 0; i < this._thumbnailBins.length; i++) {
|
||||
let mutterWindow = this._windows[i].get_compositor_private();
|
||||
let windowTexture = mutterWindow.get_texture ();
|
||||
let [width, height] = windowTexture.get_size();
|
||||
let scale = Math.min(1.0, THUMBNAIL_DEFAULT_SIZE / width, availHeight / height);
|
||||
let clone = new Clutter.Clone ({ source: windowTexture,
|
||||
reactive: true,
|
||||
width: width * scale,
|
||||
height: height * scale });
|
||||
|
||||
this._thumbnailBins[i].set_height(binHeight);
|
||||
this._thumbnailBins[i].add_actor(clone);
|
||||
this._clones.push(clone);
|
||||
}
|
||||
|
||||
// Make sure we only do this once
|
||||
this._thumbnailBins = new Array();
|
||||
}
|
||||
};
|
||||
|
1128
js/ui/appDisplay.js
@ -3,19 +3,21 @@
|
||||
const Shell = imports.gi.Shell;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
|
||||
function AppFavorites() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
AppFavorites.prototype = {
|
||||
FAVORITE_APPS_KEY: 'favorite_apps',
|
||||
FAVORITE_APPS_KEY: 'favorite-apps',
|
||||
|
||||
_init: function() {
|
||||
this._favorites = {};
|
||||
this._gconf = Shell.GConf.get_default();
|
||||
this._gconf.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged));
|
||||
global.settings.connect('changed::' + this.FAVORITE_APPS_KEY, Lang.bind(this, this._onFavsChanged));
|
||||
this._reload();
|
||||
},
|
||||
|
||||
@ -25,7 +27,7 @@ AppFavorites.prototype = {
|
||||
},
|
||||
|
||||
_reload: function() {
|
||||
let ids = Shell.GConf.get_default().get_string_list('favorite_apps');
|
||||
let ids = global.settings.get_strv(this.FAVORITE_APPS_KEY);
|
||||
let appSys = Shell.AppSystem.get_default();
|
||||
let apps = ids.map(function (id) {
|
||||
return appSys.get_app(id);
|
||||
@ -61,23 +63,66 @@ AppFavorites.prototype = {
|
||||
return appId in this._favorites;
|
||||
},
|
||||
|
||||
addFavorite: function(appId) {
|
||||
_addFavorite: function(appId, pos) {
|
||||
if (appId in this._favorites)
|
||||
return;
|
||||
return false;
|
||||
|
||||
let app = Shell.AppSystem.get_default().get_app(appId);
|
||||
|
||||
if (!app)
|
||||
return;
|
||||
return false;
|
||||
|
||||
let ids = this._getIds();
|
||||
ids.push(appId);
|
||||
this._gconf.set_string_list(this.FAVORITE_APPS_KEY, ids);
|
||||
if (pos == -1)
|
||||
ids.push(appId);
|
||||
else
|
||||
ids.splice(pos, 0, appId);
|
||||
global.settings.set_strv(this.FAVORITE_APPS_KEY, ids);
|
||||
this._favorites[appId] = app;
|
||||
return true;
|
||||
},
|
||||
|
||||
addFavoriteAtPos: function(appId, pos) {
|
||||
if (!this._addFavorite(appId, pos))
|
||||
return;
|
||||
|
||||
let app = Shell.AppSystem.get_default().get_app(appId);
|
||||
|
||||
Main.overview.shellInfo.setMessage(_("%s has been added to your favorites.").format(app.get_name()), Lang.bind(this, function () {
|
||||
this._removeFavorite(appId);
|
||||
}));
|
||||
},
|
||||
|
||||
addFavorite: function(appId) {
|
||||
this.addFavoriteAtPos(appId, -1);
|
||||
},
|
||||
|
||||
moveFavoriteToPos: function(appId, pos) {
|
||||
this._removeFavorite(appId);
|
||||
this._addFavorite(appId, pos);
|
||||
},
|
||||
|
||||
_removeFavorite: function(appId) {
|
||||
if (!appId in this._favorites)
|
||||
return false;
|
||||
|
||||
let ids = this._getIds().filter(function (id) { return id != appId; });
|
||||
global.settings.set_strv(this.FAVORITE_APPS_KEY, ids);
|
||||
return true;
|
||||
},
|
||||
|
||||
removeFavorite: function(appId) {
|
||||
if (!appId in this._favorites)
|
||||
let ids = this._getIds();
|
||||
let pos = ids.indexOf(appId);
|
||||
|
||||
let app = this._favorites[appId];
|
||||
if (!this._removeFavorite(appId))
|
||||
return;
|
||||
let ids = this._getIds().filter(function (id) { return id != appId; });
|
||||
this._gconf.set_string_list(this.FAVORITE_APPS_KEY, ids);
|
||||
|
||||
Main.overview.shellInfo.setMessage(_("%s has been removed from your favorites.").format(app.get_name()),
|
||||
Lang.bind(this, function () {
|
||||
this._addFavorite(appId, pos);
|
||||
}));
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(AppFavorites.prototype);
|
||||
|
591
js/ui/appIcon.js
@ -1,591 +0,0 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const GenericDisplay = imports.ui.genericDisplay;
|
||||
const AppFavorites = imports.ui.appFavorites;
|
||||
const Main = imports.ui.main;
|
||||
const Workspaces = imports.ui.workspaces;
|
||||
|
||||
const GLOW_COLOR = new Clutter.Color();
|
||||
GLOW_COLOR.from_pixel(0x4f6ba4ff);
|
||||
const GLOW_PADDING_HORIZONTAL = 3;
|
||||
const GLOW_PADDING_VERTICAL = 3;
|
||||
|
||||
const APPICON_DEFAULT_ICON_SIZE = 48;
|
||||
|
||||
const APPICON_PADDING = 1;
|
||||
const APPICON_BORDER_WIDTH = 1;
|
||||
const APPICON_CORNER_RADIUS = 4;
|
||||
|
||||
const APPICON_MENU_POPUP_TIMEOUT_MS = 600;
|
||||
|
||||
const APPICON_DEFAULT_BORDER_COLOR = new Clutter.Color();
|
||||
APPICON_DEFAULT_BORDER_COLOR.from_pixel(0x787878ff);
|
||||
const APPICON_MENU_BACKGROUND_COLOR = new Clutter.Color();
|
||||
APPICON_MENU_BACKGROUND_COLOR.from_pixel(0x292929ff);
|
||||
const APPICON_MENU_FONT = 'Sans 14px';
|
||||
const APPICON_MENU_COLOR = new Clutter.Color();
|
||||
APPICON_MENU_COLOR.from_pixel(0xffffffff);
|
||||
const APPICON_MENU_SELECTED_COLOR = new Clutter.Color();
|
||||
APPICON_MENU_SELECTED_COLOR.from_pixel(0x005b97ff);
|
||||
const APPICON_MENU_SEPARATOR_COLOR = new Clutter.Color();
|
||||
APPICON_MENU_SEPARATOR_COLOR.from_pixel(0x787878ff);
|
||||
const APPICON_MENU_BORDER_WIDTH = 1;
|
||||
const APPICON_MENU_ARROW_SIZE = 12;
|
||||
const APPICON_MENU_CORNER_RADIUS = 4;
|
||||
const APPICON_MENU_PADDING = 4;
|
||||
|
||||
const TRANSPARENT_COLOR = new Clutter.Color();
|
||||
TRANSPARENT_COLOR.from_pixel(0x00000000);
|
||||
|
||||
const MenuType = { NONE: 0, ON_RIGHT: 1, BELOW: 2 };
|
||||
|
||||
function AppIcon(params) {
|
||||
this._init(params);
|
||||
}
|
||||
|
||||
AppIcon.prototype = {
|
||||
_init : function(params) {
|
||||
this.app = params.app;
|
||||
if (!this.app)
|
||||
throw new Error('AppIcon constructor requires "app" param');
|
||||
|
||||
this._menuType = ('menuType' in params) ? params.menuType : MenuType.NONE;
|
||||
this._iconSize = ('size' in params) ? params.size : APPICON_DEFAULT_ICON_SIZE;
|
||||
this._showGlow = ('glow' in params) ? params.glow : false;
|
||||
|
||||
this.actor = new Shell.ButtonBox({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
border: APPICON_BORDER_WIDTH,
|
||||
corner_radius: APPICON_CORNER_RADIUS,
|
||||
padding: APPICON_PADDING,
|
||||
reactive: true });
|
||||
this.actor._delegate = this;
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
|
||||
this.highlight_border_color = APPICON_DEFAULT_BORDER_COLOR;
|
||||
|
||||
if (this._menuType != MenuType.NONE) {
|
||||
this.actor.connect('button-press-event', Lang.bind(this, this._updateMenuOnButtonPress));
|
||||
this.actor.connect('notify::hover', Lang.bind(this, this._updateMenuOnHoverChanged));
|
||||
this.actor.connect('activate', Lang.bind(this, this._updateMenuOnActivate));
|
||||
|
||||
this._menuTimeoutId = 0;
|
||||
this._menu = null;
|
||||
}
|
||||
|
||||
let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
x_align: Big.BoxAlignment.CENTER,
|
||||
y_align: Big.BoxAlignment.CENTER,
|
||||
width: this._iconSize,
|
||||
height: this._iconSize });
|
||||
this.icon = this.app.create_icon_texture(this._iconSize);
|
||||
iconBox.append(this.icon, Big.BoxPackFlags.NONE);
|
||||
|
||||
this.actor.append(iconBox, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
let nameBox = new Shell.GenericContainer();
|
||||
nameBox.connect('get-preferred-width', Lang.bind(this, this._nameBoxGetPreferredWidth));
|
||||
nameBox.connect('get-preferred-height', Lang.bind(this, this._nameBoxGetPreferredHeight));
|
||||
nameBox.connect('allocate', Lang.bind(this, this._nameBoxAllocate));
|
||||
this._nameBox = nameBox;
|
||||
|
||||
this._name = new St.Label({ style_class: "app-icon-label",
|
||||
text: this.app.get_name() });
|
||||
this._name.clutter_text.line_alignment = Pango.Alignment.CENTER;
|
||||
nameBox.add_actor(this._name);
|
||||
if (this._showGlow) {
|
||||
this._glowBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
|
||||
this._nameBox.add_actor(this._glowBox);
|
||||
this._glowBox.lower(this._name);
|
||||
this._appWindowChangedId = this.app.connect('windows-changed', Lang.bind(this, this._rerenderGlow));
|
||||
this._rerenderGlow();
|
||||
} else {
|
||||
this._glowBox = null;
|
||||
this._appWindowChangedId = 0;
|
||||
}
|
||||
|
||||
this.actor.append(nameBox, Big.BoxPackFlags.NONE);
|
||||
},
|
||||
|
||||
_nameBoxGetPreferredWidth: function (nameBox, forHeight, alloc) {
|
||||
let [min, natural] = this._name.get_preferred_width(forHeight);
|
||||
alloc.min_size = min + GLOW_PADDING_HORIZONTAL * 2;
|
||||
alloc.natural_size = natural + GLOW_PADDING_HORIZONTAL * 2;
|
||||
},
|
||||
|
||||
_nameBoxGetPreferredHeight: function (nameBox, forWidth, alloc) {
|
||||
let [min, natural] = this._name.get_preferred_height(forWidth);
|
||||
alloc.min_size = min + GLOW_PADDING_VERTICAL * 2;
|
||||
alloc.natural_size = natural + GLOW_PADDING_VERTICAL * 2;
|
||||
},
|
||||
|
||||
_nameBoxAllocate: function (nameBox, box, flags) {
|
||||
let childBox = new Clutter.ActorBox();
|
||||
let [minWidth, naturalWidth] = this._name.get_preferred_width(-1);
|
||||
let [minHeight, naturalHeight] = this._name.get_preferred_height(-1);
|
||||
let availWidth = box.x2 - box.x1;
|
||||
let availHeight = box.y2 - box.y1;
|
||||
let targetWidth = availWidth;
|
||||
let xPadding = 0;
|
||||
if (naturalWidth < availWidth) {
|
||||
xPadding = Math.floor((availWidth - naturalWidth) / 2);
|
||||
}
|
||||
childBox.x1 = xPadding;
|
||||
childBox.x2 = availWidth - xPadding;
|
||||
childBox.y1 = GLOW_PADDING_VERTICAL;
|
||||
childBox.y2 = availHeight - GLOW_PADDING_VERTICAL;
|
||||
this._name.allocate(childBox, flags);
|
||||
|
||||
// Now the glow
|
||||
if (this._glowBox != null) {
|
||||
let glowPaddingHoriz = Math.max(0, xPadding - GLOW_PADDING_HORIZONTAL);
|
||||
glowPaddingHoriz = Math.max(GLOW_PADDING_HORIZONTAL, glowPaddingHoriz);
|
||||
childBox.x1 = glowPaddingHoriz;
|
||||
childBox.x2 = availWidth - glowPaddingHoriz;
|
||||
childBox.y1 = 0;
|
||||
childBox.y2 = availHeight;
|
||||
this._glowBox.allocate(childBox, flags);
|
||||
}
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
if (this._appWindowChangedId > 0)
|
||||
this.app.disconnect(this._appWindowChangedId);
|
||||
},
|
||||
|
||||
_rerenderGlow: function() {
|
||||
if (!this._showGlow)
|
||||
return;
|
||||
this._glowBox.get_children().forEach(function (a) { a.destroy(); });
|
||||
let glowPath = GLib.filename_to_uri(global.imagedir + 'app-well-glow.png', '');
|
||||
let windows = this.app.get_windows();
|
||||
for (let i = 0; i < windows.length && i < 3; i++) {
|
||||
let glow = Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.FOREVER,
|
||||
glowPath, -1, -1);
|
||||
glow.keep_aspect_ratio = false;
|
||||
this._glowBox.append(glow, Big.BoxPackFlags.EXPAND);
|
||||
}
|
||||
},
|
||||
|
||||
// AppIcon itself is not a draggable, but if you want to make
|
||||
// a subclass of it draggable, you can use this method to create
|
||||
// a drag actor
|
||||
createDragActor: function() {
|
||||
return this.app.create_icon_texture(this._iconSize);
|
||||
},
|
||||
|
||||
setHighlight: function(highlight) {
|
||||
if (highlight) {
|
||||
this.actor.border_color = this.highlight_border_color;
|
||||
} else {
|
||||
this.actor.border_color = TRANSPARENT_COLOR;
|
||||
}
|
||||
},
|
||||
|
||||
_updateMenuOnActivate: function(actor, event) {
|
||||
if (this._menuTimeoutId != 0) {
|
||||
Mainloop.source_remove(this._menuTimeoutId);
|
||||
this._menuTimeoutId = 0;
|
||||
}
|
||||
this.emit('activate');
|
||||
return false;
|
||||
},
|
||||
|
||||
_updateMenuOnHoverChanged: function() {
|
||||
if (!this.actor.hover && this._menuTimeoutId != 0) {
|
||||
Mainloop.source_remove(this._menuTimeoutId);
|
||||
this._menuTimeoutId = 0;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
_updateMenuOnButtonPress: function(actor, event) {
|
||||
let button = event.get_button();
|
||||
if (button == 1) {
|
||||
if (this._menuTimeoutId != 0)
|
||||
Mainloop.source_remove(this._menuTimeoutId);
|
||||
this._menuTimeoutId = Mainloop.timeout_add(APPICON_MENU_POPUP_TIMEOUT_MS,
|
||||
Lang.bind(this, function () { this.popupMenu(button); }));
|
||||
} else if (button == 3) {
|
||||
this.popupMenu(button);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
popupMenu: function(activatingButton) {
|
||||
if (this._menuTimeoutId != 0) {
|
||||
Mainloop.source_remove(this._menuTimeoutId);
|
||||
this._menuTimeoutId = 0;
|
||||
}
|
||||
|
||||
this.actor.fake_release();
|
||||
|
||||
if (!this._menu) {
|
||||
this._menu = new AppIconMenu(this, this._menuType);
|
||||
this._menu.connect('highlight-window', Lang.bind(this, function (menu, window) {
|
||||
this.highlightWindow(window);
|
||||
}));
|
||||
this._menu.connect('activate-window', Lang.bind(this, function (menu, window) {
|
||||
this.activateWindow(window);
|
||||
}));
|
||||
this._menu.connect('popup', Lang.bind(this, function (menu, isPoppedUp) {
|
||||
if (isPoppedUp)
|
||||
this.menuPoppedUp();
|
||||
else
|
||||
this.menuPoppedDown();
|
||||
}));
|
||||
}
|
||||
|
||||
this._menu.popup(activatingButton);
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
// Default implementations; AppDisplay.RunningWellItem overrides these
|
||||
highlightWindow: function(window) {
|
||||
this.emit('highlight-window', window);
|
||||
},
|
||||
|
||||
activateWindow: function(window) {
|
||||
this.emit('activate-window', window);
|
||||
},
|
||||
|
||||
menuPoppedUp: function() {
|
||||
this.emit('menu-popped-up', this._menu);
|
||||
},
|
||||
|
||||
menuPoppedDown: function() {
|
||||
this.emit('menu-popped-down', this._menu);
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(AppIcon.prototype);
|
||||
|
||||
function AppIconMenu(source, type) {
|
||||
this._init(source, type);
|
||||
}
|
||||
|
||||
AppIconMenu.prototype = {
|
||||
_init: function(source, type) {
|
||||
this._source = source;
|
||||
this._type = type;
|
||||
|
||||
this.actor = new Shell.GenericContainer({ reactive: true });
|
||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||
|
||||
this._windowContainer = new Shell.Menu({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
border_color: source.highlight_border_color,
|
||||
border: APPICON_MENU_BORDER_WIDTH,
|
||||
background_color: APPICON_MENU_BACKGROUND_COLOR,
|
||||
padding: 4,
|
||||
corner_radius: APPICON_MENU_CORNER_RADIUS,
|
||||
width: Main.overview._dash.actor.width * 0.75 });
|
||||
this._windowContainer.connect('unselected', Lang.bind(this, this._onItemUnselected));
|
||||
this._windowContainer.connect('selected', Lang.bind(this, this._onItemSelected));
|
||||
this._windowContainer.connect('cancelled', Lang.bind(this, this._onWindowSelectionCancelled));
|
||||
this._windowContainer.connect('activate', Lang.bind(this, this._onItemActivate));
|
||||
this.actor.add_actor(this._windowContainer);
|
||||
|
||||
// Stay popped up on release over application icon
|
||||
this._windowContainer.set_persistent_source(this._source.actor);
|
||||
|
||||
// Intercept events while the menu has the pointer grab to do window-related effects
|
||||
this._windowContainer.connect('enter-event', Lang.bind(this, this._onMenuEnter));
|
||||
this._windowContainer.connect('leave-event', Lang.bind(this, this._onMenuLeave));
|
||||
this._windowContainer.connect('button-release-event', Lang.bind(this, this._onMenuButtonRelease));
|
||||
|
||||
this._arrow = new St.DrawingArea();
|
||||
this._arrow.connect('redraw', Lang.bind(this, function (area, texture) {
|
||||
Shell.draw_box_pointer(texture,
|
||||
this._type == MenuType.ON_RIGHT ? Shell.PointerDirection.LEFT : Shell.PointerDirection.UP,
|
||||
source.highlight_border_color,
|
||||
APPICON_MENU_BACKGROUND_COLOR);
|
||||
}));
|
||||
this.actor.add_actor(this._arrow);
|
||||
|
||||
// Chain our visibility and lifecycle to that of the source
|
||||
source.actor.connect('notify::mapped', Lang.bind(this, function () {
|
||||
if (!source.actor.mapped)
|
||||
this._windowContainer.popdown();
|
||||
}));
|
||||
source.actor.connect('destroy', Lang.bind(this, function () { this.actor.destroy(); }));
|
||||
|
||||
global.stage.add_actor(this.actor);
|
||||
},
|
||||
|
||||
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||
let [min, natural] = this._windowContainer.get_preferred_width(forHeight);
|
||||
if (this._type == MenuType.ON_RIGHT) {
|
||||
min += APPICON_MENU_ARROW_SIZE;
|
||||
natural += APPICON_MENU_ARROW_SIZE;
|
||||
}
|
||||
alloc.min_size = min;
|
||||
alloc.natural_size = natural;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||
let [min, natural] = this._windowContainer.get_preferred_height(forWidth);
|
||||
if (this._type == MenuType.BELOW) {
|
||||
min += APPICON_MENU_ARROW_SIZE;
|
||||
natural += APPICON_MENU_ARROW_SIZE;
|
||||
}
|
||||
alloc.min_size = min;
|
||||
alloc.natural_size = natural;
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
let childBox = new Clutter.ActorBox();
|
||||
|
||||
let width = box.x2 - box.x1;
|
||||
let height = box.y2 - box.y1;
|
||||
|
||||
if (this._type == MenuType.ON_RIGHT) {
|
||||
childBox.x1 = 0;
|
||||
childBox.x2 = APPICON_MENU_ARROW_SIZE;
|
||||
childBox.y1 = Math.floor((height / 2) - (APPICON_MENU_ARROW_SIZE / 2));
|
||||
childBox.y2 = childBox.y1 + APPICON_MENU_ARROW_SIZE;
|
||||
this._arrow.allocate(childBox, flags);
|
||||
|
||||
childBox.x1 = APPICON_MENU_ARROW_SIZE - APPICON_MENU_BORDER_WIDTH;
|
||||
childBox.x2 = width;
|
||||
childBox.y1 = 0;
|
||||
childBox.y2 = height;
|
||||
this._windowContainer.allocate(childBox, flags);
|
||||
} else /* MenuType.BELOW */ {
|
||||
childBox.x1 = Math.floor((width / 2) - (APPICON_MENU_ARROW_SIZE / 2));
|
||||
childBox.x2 = childBox.x1 + APPICON_MENU_ARROW_SIZE;
|
||||
childBox.y1 = 0;
|
||||
childBox.y2 = APPICON_MENU_ARROW_SIZE;
|
||||
this._arrow.allocate(childBox, flags);
|
||||
|
||||
childBox.x1 = 0;
|
||||
childBox.x2 = width;
|
||||
childBox.y1 = APPICON_MENU_ARROW_SIZE - APPICON_MENU_BORDER_WIDTH;
|
||||
childBox.y2 = height;
|
||||
this._windowContainer.allocate(childBox, flags);
|
||||
}
|
||||
},
|
||||
|
||||
_redisplay: function() {
|
||||
this._windowContainer.remove_all();
|
||||
|
||||
let windows = this._source.app.get_windows();
|
||||
|
||||
this._windowContainer.show();
|
||||
|
||||
let iconsDiffer = false;
|
||||
let texCache = Shell.TextureCache.get_default();
|
||||
if (windows.length > 0) {
|
||||
let firstIcon = windows[0].mini_icon;
|
||||
for (let i = 1; i < windows.length; i++) {
|
||||
if (!texCache.pixbuf_equal(windows[i].mini_icon, firstIcon)) {
|
||||
iconsDiffer = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display the app windows menu items and the separator between windows
|
||||
// of the current desktop and other windows.
|
||||
let activeWorkspace = global.screen.get_active_workspace();
|
||||
let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;
|
||||
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
if (!separatorShown && windows[i].get_workspace() != activeWorkspace) {
|
||||
this._appendSeparator();
|
||||
separatorShown = true;
|
||||
}
|
||||
|
||||
let icon = null;
|
||||
if (iconsDiffer)
|
||||
icon = Shell.TextureCache.get_default().bind_pixbuf_property(windows[i], "mini-icon");
|
||||
|
||||
let box = this._appendMenuItem(icon, windows[i].title);
|
||||
box._window = windows[i];
|
||||
}
|
||||
|
||||
if (windows.length > 0)
|
||||
this._appendSeparator();
|
||||
|
||||
let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());
|
||||
|
||||
this._newWindowMenuItem = windows.length > 0 ? this._appendMenuItem(null, _("New Window")) : null;
|
||||
|
||||
if (windows.length > 0)
|
||||
this._appendSeparator();
|
||||
this._toggleFavoriteMenuItem = this._appendMenuItem(null, isFavorite ? _("Remove from Favorites")
|
||||
: _("Add to Favorites"));
|
||||
|
||||
this._highlightedItem = null;
|
||||
},
|
||||
|
||||
_appendSeparator: function () {
|
||||
let box = new Big.Box({ padding_top: 2, padding_bottom: 2 });
|
||||
box.append(new Clutter.Rectangle({ height: 1,
|
||||
color: APPICON_MENU_SEPARATOR_COLOR }),
|
||||
Big.BoxPackFlags.EXPAND);
|
||||
this._windowContainer.append_separator(box, Big.BoxPackFlags.NONE);
|
||||
},
|
||||
|
||||
_appendMenuItem: function(iconTexture, labelText) {
|
||||
/* Use padding here rather than spacing in the box above so that
|
||||
* we have a larger reactive area.
|
||||
*/
|
||||
let box = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
padding_top: 4,
|
||||
padding_bottom: 4,
|
||||
spacing: 4,
|
||||
reactive: true });
|
||||
let vCenter;
|
||||
if (iconTexture != null) {
|
||||
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
||||
vCenter.append(iconTexture, Big.BoxPackFlags.NONE);
|
||||
box.append(vCenter, Big.BoxPackFlags.NONE);
|
||||
}
|
||||
vCenter = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
||||
let label = new Clutter.Text({ text: labelText,
|
||||
font_name: APPICON_MENU_FONT,
|
||||
ellipsize: Pango.EllipsizeMode.END,
|
||||
color: APPICON_MENU_COLOR });
|
||||
vCenter.append(label, Big.BoxPackFlags.NONE);
|
||||
box.append(vCenter, Big.BoxPackFlags.NONE);
|
||||
this._windowContainer.append(box, Big.BoxPackFlags.NONE);
|
||||
return box;
|
||||
},
|
||||
|
||||
popup: function(activatingButton) {
|
||||
let [stageX, stageY] = this._source.actor.get_transformed_position();
|
||||
let [stageWidth, stageHeight] = this._source.actor.get_transformed_size();
|
||||
|
||||
this._redisplay();
|
||||
|
||||
this._windowContainer.popup(activatingButton, global.get_current_time());
|
||||
|
||||
this.emit('popup', true);
|
||||
|
||||
let x, y;
|
||||
if (this._type == MenuType.ON_RIGHT) {
|
||||
x = Math.floor(stageX + stageWidth);
|
||||
y = Math.floor(stageY + (stageHeight / 2) - (this.actor.height / 2));
|
||||
} else {
|
||||
x = Math.floor(stageX + (stageWidth / 2) - (this.actor.width / 2));
|
||||
y = Math.floor(stageY + stageHeight);
|
||||
}
|
||||
|
||||
this.actor.set_position(x, y);
|
||||
this.actor.show();
|
||||
},
|
||||
|
||||
popdown: function() {
|
||||
this._windowContainer.popdown();
|
||||
this.emit('popup', false);
|
||||
this.actor.hide();
|
||||
},
|
||||
|
||||
selectWindow: function(metaWindow) {
|
||||
this._selectMenuItemForWindow(metaWindow);
|
||||
},
|
||||
|
||||
_findMetaWindowForActor: function (actor) {
|
||||
if (actor._delegate instanceof Workspaces.WindowClone)
|
||||
return actor._delegate.metaWindow;
|
||||
else if (actor.get_meta_window)
|
||||
return actor.get_meta_window();
|
||||
return null;
|
||||
},
|
||||
|
||||
// This function is called while the menu has a pointer grab; what we want
|
||||
// to do is see if the mouse was released over a window representation
|
||||
_onMenuButtonRelease: function (actor, event) {
|
||||
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
||||
if (metaWindow) {
|
||||
this.emit('activate-window', metaWindow);
|
||||
}
|
||||
},
|
||||
|
||||
_updateHighlight: function (item) {
|
||||
if (this._highlightedItem) {
|
||||
this._highlightedItem.background_color = TRANSPARENT_COLOR;
|
||||
this.emit('highlight-window', null);
|
||||
}
|
||||
this._highlightedItem = item;
|
||||
if (this._highlightedItem) {
|
||||
this._highlightedItem.background_color = APPICON_MENU_SELECTED_COLOR;
|
||||
let window = this._highlightedItem._window;
|
||||
if (window)
|
||||
this.emit('highlight-window', window);
|
||||
}
|
||||
},
|
||||
|
||||
_selectMenuItemForWindow: function (metaWindow) {
|
||||
let children = this._windowContainer.get_children();
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let child = children[i];
|
||||
let menuMetaWindow = child._window;
|
||||
if (menuMetaWindow == metaWindow)
|
||||
this._updateHighlight(child);
|
||||
}
|
||||
},
|
||||
|
||||
// Called while menu has a pointer grab
|
||||
_onMenuEnter: function (actor, event) {
|
||||
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
||||
if (metaWindow) {
|
||||
this._selectMenuItemForWindow(metaWindow);
|
||||
}
|
||||
},
|
||||
|
||||
// Called while menu has a pointer grab
|
||||
_onMenuLeave: function (actor, event) {
|
||||
let metaWindow = this._findMetaWindowForActor(event.get_source());
|
||||
if (metaWindow) {
|
||||
this._updateHighlight(null);
|
||||
}
|
||||
},
|
||||
|
||||
_onItemUnselected: function (actor, child) {
|
||||
this._updateHighlight(null);
|
||||
},
|
||||
|
||||
_onItemSelected: function (actor, child) {
|
||||
this._updateHighlight(child);
|
||||
},
|
||||
|
||||
_onItemActivate: function (actor, child) {
|
||||
if (child._window) {
|
||||
let metaWindow = child._window;
|
||||
this.emit('activate-window', metaWindow);
|
||||
} else if (child == this._newWindowMenuItem) {
|
||||
this._source.app.launch();
|
||||
this.emit('activate-window', null);
|
||||
} else if (child == this._toggleFavoriteMenuItem) {
|
||||
let favs = AppFavorites.getAppFavorites();
|
||||
let isFavorite = favs.isFavorite(this._source.app.get_id());
|
||||
if (isFavorite)
|
||||
favs.removeFavorite(this._source.app.get_id());
|
||||
else
|
||||
favs.addFavorite(this._source.app.get_id());
|
||||
}
|
||||
this.popdown();
|
||||
},
|
||||
|
||||
_onWindowSelectionCancelled: function () {
|
||||
this.emit('highlight-window', null);
|
||||
this.popdown();
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(AppIconMenu.prototype);
|
481
js/ui/boxpointer.js
Normal file
@ -0,0 +1,481 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const POPUP_ANIMATION_TIME = 0.15;
|
||||
|
||||
/**
|
||||
* BoxPointer:
|
||||
* @side: side to draw the arrow on
|
||||
* @binProperties: Properties to set on contained bin
|
||||
*
|
||||
* An actor which displays a triangle "arrow" pointing to a given
|
||||
* side. The .bin property is a container in which content can be
|
||||
* placed. The arrow position may be controlled via setArrowOrigin().
|
||||
*
|
||||
*/
|
||||
function BoxPointer(side, binProperties) {
|
||||
this._init(side, binProperties);
|
||||
}
|
||||
|
||||
BoxPointer.prototype = {
|
||||
_init: function(arrowSide, binProperties) {
|
||||
this._arrowSide = arrowSide;
|
||||
this._arrowOrigin = 0;
|
||||
this._arrowCorner = null;
|
||||
this.actor = new St.Bin({ x_fill: true,
|
||||
y_fill: true });
|
||||
this._container = new Shell.GenericContainer();
|
||||
this.actor.set_child(this._container);
|
||||
this._container.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this._container.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this._container.connect('allocate', Lang.bind(this, this._allocate));
|
||||
this.bin = new St.Bin(binProperties);
|
||||
this._container.add_actor(this.bin);
|
||||
this._border = new St.DrawingArea();
|
||||
this._border.connect('repaint', Lang.bind(this, this._drawBorder));
|
||||
this._container.add_actor(this._border);
|
||||
this.bin.raise(this._border);
|
||||
},
|
||||
|
||||
show: function(animate, onComplete) {
|
||||
let x = this.actor.x;
|
||||
let y = this.actor.y;
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
let rise = themeNode.get_length('-arrow-rise');
|
||||
|
||||
this.actor.opacity = 0;
|
||||
this.actor.show();
|
||||
|
||||
if (animate) {
|
||||
switch (this._arrowSide) {
|
||||
case St.Side.TOP:
|
||||
this.actor.y -= rise;
|
||||
break;
|
||||
case St.Side.BOTTOM:
|
||||
this.actor.y += rise;
|
||||
break;
|
||||
case St.Side.LEFT:
|
||||
this.actor.x -= rise;
|
||||
break;
|
||||
case St.Side.RIGHT:
|
||||
this.actor.x += rise;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Tweener.addTween(this.actor, { opacity: 255,
|
||||
x: x,
|
||||
y: y,
|
||||
transition: "linear",
|
||||
onComplete: onComplete,
|
||||
time: POPUP_ANIMATION_TIME });
|
||||
},
|
||||
|
||||
hide: function(animate, onComplete) {
|
||||
let x = this.actor.x;
|
||||
let y = this.actor.y;
|
||||
let originalX = this.actor.x;
|
||||
let originalY = this.actor.y;
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
let rise = themeNode.get_length('-arrow-rise');
|
||||
|
||||
if (animate) {
|
||||
switch (this._arrowSide) {
|
||||
case St.Side.TOP:
|
||||
y += rise;
|
||||
break;
|
||||
case St.Side.BOTTOM:
|
||||
y -= rise;
|
||||
break;
|
||||
case St.Side.LEFT:
|
||||
x += rise;
|
||||
break;
|
||||
case St.Side.RIGHT:
|
||||
x -= rise;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Tweener.addTween(this.actor, { opacity: 0,
|
||||
x: x,
|
||||
y: y,
|
||||
transition: "linear",
|
||||
time: POPUP_ANIMATION_TIME,
|
||||
onComplete: Lang.bind(this, function () {
|
||||
this.actor.hide();
|
||||
this.actor.x = originalX;
|
||||
this.actor.y = originalY;
|
||||
if (onComplete)
|
||||
onComplete();
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
_adjustAllocationForArrow: function(isWidth, alloc) {
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
let borderWidth = themeNode.get_length('-arrow-border-width');
|
||||
alloc.min_size += borderWidth * 2;
|
||||
alloc.natural_size += borderWidth * 2;
|
||||
if ((!isWidth && (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM))
|
||||
|| (isWidth && (this._arrowSide == St.Side.LEFT || this._arrowSide == St.Side.RIGHT))) {
|
||||
let rise = themeNode.get_length('-arrow-rise');
|
||||
alloc.min_size += rise;
|
||||
alloc.natural_size += rise;
|
||||
}
|
||||
},
|
||||
|
||||
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||
let [minInternalSize, natInternalSize] = this.bin.get_preferred_width(forHeight);
|
||||
alloc.min_size = minInternalSize;
|
||||
alloc.natural_size = natInternalSize;
|
||||
this._adjustAllocationForArrow(true, alloc);
|
||||
},
|
||||
|
||||
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||
let [minSize, naturalSize] = this.bin.get_preferred_height(forWidth);
|
||||
alloc.min_size = minSize;
|
||||
alloc.natural_size = naturalSize;
|
||||
this._adjustAllocationForArrow(false, alloc);
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
let borderWidth = themeNode.get_length('-arrow-border-width');
|
||||
let rise = themeNode.get_length('-arrow-rise');
|
||||
let childBox = new Clutter.ActorBox();
|
||||
let availWidth = box.x2 - box.x1;
|
||||
let availHeight = box.y2 - box.y1;
|
||||
|
||||
childBox.x1 = 0;
|
||||
childBox.y1 = 0;
|
||||
childBox.x2 = availWidth;
|
||||
childBox.y2 = availHeight;
|
||||
this._border.allocate(childBox, flags);
|
||||
|
||||
childBox.x1 = borderWidth;
|
||||
childBox.y1 = borderWidth;
|
||||
childBox.x2 = availWidth - borderWidth;
|
||||
childBox.y2 = availHeight - borderWidth;
|
||||
switch (this._arrowSide) {
|
||||
case St.Side.TOP:
|
||||
childBox.y1 += rise;
|
||||
break;
|
||||
case St.Side.BOTTOM:
|
||||
childBox.y2 -= rise;
|
||||
break;
|
||||
case St.Side.LEFT:
|
||||
childBox.x1 += rise;
|
||||
break;
|
||||
case St.Side.RIGHT:
|
||||
childBox.x2 -= rise;
|
||||
break;
|
||||
}
|
||||
this.bin.allocate(childBox, flags);
|
||||
},
|
||||
|
||||
_drawBorder: function(area) {
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
|
||||
let borderWidth = themeNode.get_length('-arrow-border-width');
|
||||
let base = themeNode.get_length('-arrow-base');
|
||||
let rise = themeNode.get_length('-arrow-rise');
|
||||
let borderRadius = themeNode.get_length('-arrow-border-radius');
|
||||
|
||||
let halfBorder = borderWidth / 2;
|
||||
let halfBase = Math.floor(base/2);
|
||||
|
||||
let borderColor = new Clutter.Color();
|
||||
themeNode.get_color('-arrow-border-color', borderColor);
|
||||
let backgroundColor = new Clutter.Color();
|
||||
themeNode.get_color('-arrow-background-color', backgroundColor);
|
||||
|
||||
let [width, height] = area.get_surface_size();
|
||||
let [boxWidth, boxHeight] = [width, height];
|
||||
if (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM) {
|
||||
boxHeight -= rise;
|
||||
} else {
|
||||
boxWidth -= rise;
|
||||
}
|
||||
let cr = area.get_context();
|
||||
Clutter.cairo_set_source_color(cr, borderColor);
|
||||
|
||||
// Translate so that box goes from 0,0 to boxWidth,boxHeight,
|
||||
// with the arrow poking out of that
|
||||
if (this._arrowSide == St.Side.TOP) {
|
||||
cr.translate(0, rise);
|
||||
} else if (this._arrowSide == St.Side.LEFT) {
|
||||
cr.translate(rise, 0);
|
||||
}
|
||||
|
||||
let [x1, y1] = [halfBorder, halfBorder];
|
||||
let [x2, y2] = [boxWidth - halfBorder, boxHeight - halfBorder];
|
||||
|
||||
cr.moveTo(x1 + borderRadius, y1);
|
||||
if (this._arrowSide == St.Side.TOP) {
|
||||
if (this._arrowCorner == St.Corner.TOPLEFT) {
|
||||
cr.moveTo(x1, y1);
|
||||
cr.lineTo(x1, y1 - rise);
|
||||
cr.lineTo(x1 + halfBase, y1);
|
||||
cr.lineTo(x2 - borderRadius, y1);
|
||||
} else if (this._arrowCorner == St.Corner.TOPRIGHT) {
|
||||
cr.lineTo(x2 - halfBase, y1);
|
||||
cr.lineTo(x2, y1 - rise);
|
||||
} else if (this._arrowOrigin < (x1 + (borderRadius + halfBase))) {
|
||||
cr.lineTo(this._arrowOrigin, y1);
|
||||
cr.lineTo(this._arrowOrigin, y1 - rise);
|
||||
cr.lineTo(this._arrowOrigin + halfBase, y1);
|
||||
cr.lineTo(x2 - borderRadius, y1);
|
||||
} else if (this._arrowOrigin > (x2 - (borderRadius + halfBase))) {
|
||||
cr.lineTo(this._arrowOrigin - halfBase, y1);
|
||||
cr.lineTo(this._arrowOrigin, y1 - rise);
|
||||
cr.lineTo(this._arrowOrigin, y1);
|
||||
cr.lineTo(x2 - borderRadius, y1);
|
||||
} else {
|
||||
cr.lineTo(this._arrowOrigin - halfBase, y1);
|
||||
cr.lineTo(this._arrowOrigin, y1 - rise);
|
||||
cr.lineTo(this._arrowOrigin + halfBase, y1);
|
||||
cr.lineTo(x2 - borderRadius, y1);
|
||||
}
|
||||
} else
|
||||
cr.lineTo(x2 - borderRadius, y1);
|
||||
|
||||
// top-right corner
|
||||
if (this._arrowCorner != St.Corner.TOPRIGHT)
|
||||
cr.arc(x2 - borderRadius, y1 + borderRadius, borderRadius,
|
||||
3*Math.PI/2, Math.PI*2);
|
||||
|
||||
if (this._arrowSide == St.Side.RIGHT) {
|
||||
if (this._arrowCorner == St.Corner.TOPRIGHT) {
|
||||
cr.lineTo(x2, y1);
|
||||
cr.lineTo(x2 + rise, y1);
|
||||
cr.lineTo(x2, y1 + halfBase);
|
||||
cr.lineTo(x2, y2 - borderRadius);
|
||||
} else if (this._arrowCorner == St.Corner.BOTTOMRIGHT) {
|
||||
cr.moveTo(x2, y2 - halfBase);
|
||||
cr.lineTo(x2 + rise, y2);
|
||||
} else if (this._arrowOrigin < (y1 + (borderRadius + halfBase))) {
|
||||
cr.lineTo(x2, this._arrowOrigin);
|
||||
cr.lineTo(x2 + rise, this._arrowOrigin);
|
||||
cr.lineTo(x2, this._arrowOrigin + halfBase);
|
||||
cr.lineTo(x2, y2 - borderRadius);
|
||||
} else if (this._arrowOrigin > (y2 - (borderRadius + halfBase))) {
|
||||
cr.lineTo(x2, this._arrowOrigin - halfBase);
|
||||
cr.lineTo(x2 + rise, this._arrowOrigin);
|
||||
cr.lineTo(x2, this._arrowOrigin);
|
||||
cr.lineTo(x2, y2 - borderRadius);
|
||||
} else {
|
||||
cr.lineTo(x2, this._arrowOrigin - halfBase);
|
||||
cr.lineTo(x2 + rise, this._arrowOrigin);
|
||||
cr.lineTo(x2, this._arrowOrigin + halfBase);
|
||||
cr.lineTo(x2, y2 - borderRadius);
|
||||
}
|
||||
} else
|
||||
cr.lineTo(x2, y2 - borderRadius);
|
||||
|
||||
// bottom-right corner
|
||||
if (this._arrowCorner != St.Corner.BOTTOMRIGHT)
|
||||
cr.arc(x2 - borderRadius, y2 - borderRadius, borderRadius,
|
||||
0, Math.PI/2);
|
||||
|
||||
if (this._arrowSide == St.Side.BOTTOM) {
|
||||
if (this._arrowCorner == St.Corner.BOTTOMLEFT) {
|
||||
cr.lineTo(x1 + halfBase, y2);
|
||||
cr.lineTo(x1, y2 + rise);
|
||||
} else if (this._arrowCorner == St.Corner.BOTTOMRIGHT) {
|
||||
cr.lineTo(x2, y2 + rise);
|
||||
cr.lineTo(x2 - halfBase, y2);
|
||||
cr.lineTo(x1 + borderRadius, y2);
|
||||
} else if (this._arrowOrigin < (x1 + (borderRadius + halfBase))) {
|
||||
cr.lineTo(this._arrowOrigin + halfBase, y2);
|
||||
cr.lineTo(this._arrowOrigin, y2 + rise);
|
||||
cr.lineTo(this._arrowOrigin, y2);
|
||||
cr.lineTo(x1 + borderRadius, y2);
|
||||
} else if (this._arrowOrigin > (x2 - (borderRadius + halfBase))) {
|
||||
cr.lineTo(this._arrowOrigin, y2);
|
||||
cr.lineTo(this._arrowOrigin, y2 + rise);
|
||||
cr.lineTo(this._arrowOrigin - halfBase, y2);
|
||||
cr.lineTo(x1 + borderRadius, y2);
|
||||
} else {
|
||||
cr.lineTo(this._arrowOrigin + halfBase, y2);
|
||||
cr.lineTo(this._arrowOrigin, y2 + rise);
|
||||
cr.lineTo(this._arrowOrigin - halfBase, y2);
|
||||
cr.lineTo(x1 + borderRadius, y2);
|
||||
}
|
||||
} else
|
||||
cr.lineTo(x1 + borderRadius, y2);
|
||||
|
||||
// bottom-left corner
|
||||
if (this._arrowCorner != St.Corner.BOTTOMLEFT)
|
||||
cr.arc(x1 + borderRadius, y2 - borderRadius, borderRadius,
|
||||
Math.PI/2, Math.PI);
|
||||
|
||||
if (this._arrowSide == St.Side.LEFT) {
|
||||
if (this._arrowCorner == St.Corner.TOPLEFT) {
|
||||
cr.lineTo(x2, y1 + halfBase);
|
||||
cr.lineTo(x1 - rise, y1);
|
||||
} else if (this._arrowCorner == St.Corner.BOTTOMLEFT) {
|
||||
cr.lineTo(x1 + rise, y2);
|
||||
cr.moveTo(x1, y2 - halfBase);
|
||||
} else if (this._arrowOrigin < (y1 + (borderRadius + halfBase))) {
|
||||
cr.lineTo(x1, this._arrowOrigin + halfBase);
|
||||
cr.lineTo(x1 - rise, this._arrowOrigin);
|
||||
cr.lineTo(x1, this._arrowOrigin);
|
||||
cr.lineTo(x1, y1 + borderRadius);
|
||||
} else if (this._arrowOrigin > (y2 - (borderRadius + halfBase))) {
|
||||
cr.lineTo(x1, this._arrowOrigin);
|
||||
cr.lineTo(x1 - rise, this._arrowOrigin);
|
||||
cr.lineTo(x1, this._arrowOrigin - halfBase);
|
||||
cr.lineTo(x1, y1 + borderRadius);
|
||||
} else {
|
||||
cr.lineTo(x1, this._arrowOrigin + halfBase);
|
||||
cr.lineTo(x1 - rise, this._arrowOrigin);
|
||||
cr.lineTo(x1, this._arrowOrigin - halfBase);
|
||||
cr.lineTo(x1, y1 + borderRadius);
|
||||
}
|
||||
} else
|
||||
cr.lineTo(x1, y1 + borderRadius);
|
||||
|
||||
// top-left corner
|
||||
if (this._arrowCorner != St.Corner.TOPLEFT)
|
||||
cr.arc(x1 + borderRadius, y1 + borderRadius, borderRadius,
|
||||
Math.PI, 3*Math.PI/2);
|
||||
else
|
||||
cr.lineTo(x1, y1);
|
||||
|
||||
Clutter.cairo_set_source_color(cr, backgroundColor);
|
||||
cr.fillPreserve();
|
||||
Clutter.cairo_set_source_color(cr, borderColor);
|
||||
cr.setLineWidth(borderWidth);
|
||||
cr.stroke();
|
||||
},
|
||||
|
||||
setPosition: function(sourceActor, gap, alignment) {
|
||||
// We need to show it now to force an allocation,
|
||||
// so that we can query the correct size.
|
||||
this.actor.show();
|
||||
|
||||
// Position correctly relative to the sourceActor
|
||||
let [sourceX, sourceY] = sourceActor.get_transformed_position();
|
||||
let [sourceWidth, sourceHeight] = sourceActor.get_transformed_size();
|
||||
let [sourceCenterX, sourceCenterY] = [sourceX + (sourceWidth / 2), sourceY + (sourceHeight / 2)];
|
||||
let [minWidth, minHeight, natWidth, natHeight] = this.actor.get_preferred_size();
|
||||
|
||||
// We also want to keep it onscreen, and separated from the
|
||||
// edge by the same distance as the main part of the box is
|
||||
// separated from its sourceActor
|
||||
let primary = global.get_primary_monitor();
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
let halfBorder = themeNode.get_length('-arrow-border-width') / 2;
|
||||
let halfBase = themeNode.get_length('-arrow-base') / 2;
|
||||
let borderRadius = themeNode.get_length('-arrow-border-radius');
|
||||
|
||||
let margin = 2 * borderRadius + halfBorder;
|
||||
|
||||
let resX, resY;
|
||||
this._arrowCorner = null;
|
||||
|
||||
switch (this._arrowSide) {
|
||||
case St.Side.TOP:
|
||||
resY = sourceY + sourceHeight + gap;
|
||||
break;
|
||||
case St.Side.BOTTOM:
|
||||
resY = sourceY - natHeight - gap;
|
||||
break;
|
||||
case St.Side.LEFT:
|
||||
resX = sourceX + sourceWidth + gap;
|
||||
break;
|
||||
case St.Side.RIGHT:
|
||||
resX = sourceX - natWidth - gap;
|
||||
break;
|
||||
}
|
||||
|
||||
// Now align and position the pointing axis, making sure
|
||||
// it fits on screen
|
||||
switch (this._arrowSide) {
|
||||
case St.Side.TOP:
|
||||
case St.Side.BOTTOM:
|
||||
switch (alignment) {
|
||||
case St.Align.START:
|
||||
resX = sourceCenterX - (halfBase + borderRadius + halfBorder);
|
||||
break;
|
||||
case St.Align.MIDDLE:
|
||||
resX = sourceCenterX - (natWidth / 2);
|
||||
break;
|
||||
case St.Align.END:
|
||||
resX = sourceCenterX - natWidth + (halfBase + borderRadius + halfBorder);
|
||||
break;
|
||||
}
|
||||
if (sourceCenterX < margin) {
|
||||
// Not enough space to the top
|
||||
this._arrowCorner = (this._arrowSide == St.Side.TOP) ? St.Corner.TOPLEFT : St.Corner.BOTTOMLEFT;
|
||||
resX = primary.x + 10;
|
||||
} else if (sourceCenterX > (primary.x + primary.width - margin)) {
|
||||
// Not enough space to the botom
|
||||
this._arrowCorner = (this._arrowSide == St.Side.TOP) ? St.Corner.TOPRIGHT : St.Corner.BOTTOMRIGHT;
|
||||
resX = primary.x + primary.width - (10 + natWidth);
|
||||
}
|
||||
|
||||
resX = Math.max(resX, primary.x + 10);
|
||||
resX = Math.min(resX, primary.x + primary.width - (10 + natWidth));
|
||||
this.setArrowOrigin(sourceCenterX - resX);
|
||||
break;
|
||||
|
||||
case St.Side.LEFT:
|
||||
case St.Side.RIGHT:
|
||||
switch (alignment) {
|
||||
case St.Align.START:
|
||||
resY = sourceCenterY - (halfBase + borderRadius + halfBorder);
|
||||
break;
|
||||
case St.Align.MIDDLE:
|
||||
resY = sourceCenterY - (natHeight / 2);
|
||||
break;
|
||||
case St.Align.END:
|
||||
resY = sourceCenterY - natHeight + (halfBase + borderRadius + halfBorder);
|
||||
break;
|
||||
}
|
||||
if (sourceCenterY < margin) {
|
||||
// Not enough space to the left
|
||||
this._arrowCorner = (this._arrowSide == St.Side.LEFT) ? St.Corner.TOPLEFT : St.Corner.TORIGHT;
|
||||
resY = 10;
|
||||
}
|
||||
else if (sourceCenterY > (primary.y + primary.height - margin)) {
|
||||
// Not enough space to the right
|
||||
this._arrowCorner = (this._arrowSide == St.Side.LEFT) ? St.Corner.BOTTOMLEFT : St.Corner.BOTTOMRIGHT;
|
||||
resY = primary.y + primary.height - (10 + natHeight);
|
||||
}
|
||||
|
||||
resY = Math.max(resY, primary.y + 10);
|
||||
resY = Math.min(resY, primary.y + primary.height - (10 + natHeight));
|
||||
|
||||
this.setArrowOrigin(sourceCenterY - resY);
|
||||
break;
|
||||
}
|
||||
|
||||
let parent = this.actor.get_parent();
|
||||
let success, x, y;
|
||||
while (!success) {
|
||||
[success, x, y] = parent.transform_stage_point(resX, resY);
|
||||
parent = parent.get_parent();
|
||||
}
|
||||
|
||||
// Actually set the position
|
||||
this.actor.x = Math.floor(x);
|
||||
this.actor.y = Math.floor(y);
|
||||
},
|
||||
|
||||
// @origin: Coordinate specifying middle of the arrow, along
|
||||
// the Y axis for St.Side.LEFT, St.Side.RIGHT from the top and X axis from
|
||||
// the left for St.Side.TOP and St.Side.BOTTOM.
|
||||
setArrowOrigin: function(origin) {
|
||||
if (this._arrowOrigin != origin) {
|
||||
this._arrowOrigin = origin;
|
||||
this._border.queue_repaint();
|
||||
}
|
||||
}
|
||||
};
|
@ -1,12 +1,23 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Gettext_gtk20 = imports.gettext.domain('gtk20');
|
||||
const Signals = imports.signals;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Gettext_gtk30 = imports.gettext.domain('gtk30');
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||
const WEEKDATE_HEADER_WIDTH_DIGITS = 3;
|
||||
const SHOW_WEEKDATE_KEY = 'show-weekdate';
|
||||
|
||||
// in org.gnome.desktop.interface
|
||||
const CLOCK_FORMAT_KEY = 'clock-format';
|
||||
|
||||
function _sameDay(dateA, dateB) {
|
||||
return (dateA.getDate() == dateB.getDate() &&
|
||||
@ -14,91 +25,451 @@ function _sameDay(dateA, dateB) {
|
||||
dateA.getYear() == dateB.getYear());
|
||||
}
|
||||
|
||||
function Calendar() {
|
||||
this._init();
|
||||
function _sameYear(dateA, dateB) {
|
||||
return (dateA.getYear() == dateB.getYear());
|
||||
}
|
||||
|
||||
/* TODO: maybe needs config - right now we assume that Saturday and
|
||||
* Sunday are non-work days (not true in e.g. Israel, it's Sunday and
|
||||
* Monday there)
|
||||
*/
|
||||
function _isWorkDay(date) {
|
||||
return date.getDay() != 0 && date.getDay() != 6;
|
||||
}
|
||||
|
||||
function _getBeginningOfDay(date) {
|
||||
let ret = new Date(date.getTime());
|
||||
ret.setHours(0);
|
||||
ret.setMinutes(0);
|
||||
ret.setSeconds(0);
|
||||
ret.setMilliseconds(0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function _getEndOfDay(date) {
|
||||
let ret = new Date(date.getTime());
|
||||
ret.setHours(23);
|
||||
ret.setMinutes(59);
|
||||
ret.setSeconds(59);
|
||||
ret.setMilliseconds(999);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function _formatEventTime(event, clockFormat) {
|
||||
let ret;
|
||||
if (event.allDay) {
|
||||
/* Translators: Shown in calendar event list for all day events */
|
||||
ret = _("All Day");
|
||||
} else {
|
||||
switch (clockFormat) {
|
||||
case '24h':
|
||||
ret = event.date.toLocaleFormat('%H:%M');
|
||||
break;
|
||||
|
||||
default:
|
||||
/* explicit fall-through */
|
||||
case '12h':
|
||||
ret = event.date.toLocaleFormat('%l:%M %p');
|
||||
break;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function _getCalendarWeekForDate(date) {
|
||||
// Based on the algorithms found here:
|
||||
// http://en.wikipedia.org/wiki/Talk:ISO_week_date
|
||||
let midnightDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
// Need to get Monday to be 1 ... Sunday to be 7
|
||||
let dayOfWeek = 1 + ((midnightDate.getDay() + 6) % 7);
|
||||
let nearestThursday = new Date(midnightDate.getFullYear(), midnightDate.getMonth(),
|
||||
midnightDate.getDate() + (4 - dayOfWeek));
|
||||
|
||||
let jan1st = new Date(nearestThursday.getFullYear(), 0, 1);
|
||||
let diffDate = nearestThursday - jan1st;
|
||||
let dayNumber = Math.floor(Math.abs(diffDate) / MSECS_IN_DAY);
|
||||
let weekNumber = Math.floor(dayNumber / 7) + 1;
|
||||
|
||||
return weekNumber;
|
||||
}
|
||||
|
||||
function _getDigitWidth(actor){
|
||||
let context = actor.get_pango_context();
|
||||
let themeNode = actor.get_theme_node();
|
||||
let font = themeNode.get_font();
|
||||
let metrics = context.get_metrics(font, context.get_language());
|
||||
let width = metrics.get_approximate_digit_width();
|
||||
return width;
|
||||
}
|
||||
|
||||
function _getCalendarDayAbbreviation(dayNumber) {
|
||||
let abbreviations = [
|
||||
/* Translators: Calendar grid abbreviation for Sunday.
|
||||
*
|
||||
* NOTE: These abbreviations are always shown together and in
|
||||
* order, e.g. "S M T W T F S".
|
||||
*/
|
||||
_("S"),
|
||||
/* Translators: Calendar grid abbreviation for Monday */
|
||||
_("M"),
|
||||
/* Translators: Calendar grid abbreviation for Tuesday */
|
||||
_("T"),
|
||||
/* Translators: Calendar grid abbreviation for Wednesday */
|
||||
_("W"),
|
||||
/* Translators: Calendar grid abbreviation for Thursday */
|
||||
_("T"),
|
||||
/* Translators: Calendar grid abbreviation for Friday */
|
||||
_("F"),
|
||||
/* Translators: Calendar grid abbreviation for Saturday */
|
||||
_("S")
|
||||
];
|
||||
return abbreviations[dayNumber];
|
||||
}
|
||||
|
||||
function _getEventDayAbbreviation(dayNumber) {
|
||||
let abbreviations = [
|
||||
/* Translators: Event list abbreviation for Sunday.
|
||||
*
|
||||
* NOTE: These abbreviations are normally not shown together
|
||||
* so they need to be unique (e.g. Tuesday and Thursday cannot
|
||||
* both be 'T').
|
||||
*/
|
||||
_("Su"),
|
||||
/* Translators: Event list abbreviation for Monday */
|
||||
_("M"),
|
||||
/* Translators: Event list abbreviation for Tuesday */
|
||||
_("T"),
|
||||
/* Translators: Event list abbreviation for Wednesday */
|
||||
_("W"),
|
||||
/* Translators: Event list abbreviation for Thursday */
|
||||
_("Th"),
|
||||
/* Translators: Event list abbreviation for Friday */
|
||||
_("F"),
|
||||
/* Translators: Event list abbreviation for Saturday */
|
||||
_("S")
|
||||
];
|
||||
return abbreviations[dayNumber];
|
||||
}
|
||||
|
||||
// Abstraction for an appointment/event in a calendar
|
||||
|
||||
function CalendarEvent(date, summary, allDay) {
|
||||
this._init(date, summary, allDay);
|
||||
}
|
||||
|
||||
CalendarEvent.prototype = {
|
||||
_init: function(date, summary, allDay) {
|
||||
this.date = date;
|
||||
this.summary = summary;
|
||||
this.allDay = allDay;
|
||||
}
|
||||
};
|
||||
|
||||
Calendar.prototype = {
|
||||
// Interface for appointments/events - e.g. the contents of a calendar
|
||||
//
|
||||
|
||||
// First, an implementation with no events
|
||||
function EmptyEventSource() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
EmptyEventSource.prototype = {
|
||||
_init: function() {
|
||||
},
|
||||
|
||||
requestRange: function(begin, end) {
|
||||
},
|
||||
|
||||
getEvents: function(begin, end) {
|
||||
let result = [];
|
||||
return result;
|
||||
},
|
||||
|
||||
hasEvents: function(day) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(EmptyEventSource.prototype);
|
||||
|
||||
// Second, wrap native Evolution event source
|
||||
function EvolutionEventSource() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
EvolutionEventSource.prototype = {
|
||||
_init: function() {
|
||||
this._native = new Shell.EvolutionEventSource();
|
||||
this._native.connect('changed', Lang.bind(this, function() {
|
||||
this.emit('changed');
|
||||
}));
|
||||
},
|
||||
|
||||
requestRange: function(begin, end) {
|
||||
this._native.request_range(begin.getTime(), end.getTime());
|
||||
},
|
||||
|
||||
getEvents: function(begin, end) {
|
||||
let result = [];
|
||||
let nativeEvents = this._native.get_events(begin.getTime(), end.getTime());
|
||||
for (let n = 0; n < nativeEvents.length; n++) {
|
||||
let nativeEvent = nativeEvents[n];
|
||||
result.push(new CalendarEvent(new Date(nativeEvent.msec_begin), nativeEvent.summary, nativeEvent.all_day));
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
hasEvents: function(day) {
|
||||
let dayBegin = _getBeginningOfDay(day);
|
||||
let dayEnd = _getEndOfDay(day);
|
||||
|
||||
let events = this.getEvents(dayBegin, dayEnd);
|
||||
|
||||
if (events.length == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(EvolutionEventSource.prototype);
|
||||
|
||||
// Finally, an implementation with fake events
|
||||
function FakeEventSource() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
FakeEventSource.prototype = {
|
||||
_init: function() {
|
||||
|
||||
this._fakeEvents = [];
|
||||
|
||||
// Generate fake events
|
||||
//
|
||||
let midnightToday = _getBeginningOfDay(new Date());
|
||||
let summary = '';
|
||||
|
||||
// '10-oclock pow-wow' is an event occuring IN THE PAST every four days at 10am
|
||||
for (let n = 0; n < 10; n++) {
|
||||
let t = new Date(midnightToday.getTime() - n * 4 * 86400 * 1000);
|
||||
t.setHours(10);
|
||||
summary = '10-oclock pow-wow (n=' + n + ')';
|
||||
this._fakeEvents.push(new CalendarEvent(t, summary, false));
|
||||
}
|
||||
|
||||
// '11-oclock thing' is an event occuring every three days at 11am
|
||||
for (let n = 0; n < 10; n++) {
|
||||
let t = new Date(midnightToday.getTime() + n * 3 * 86400 * 1000);
|
||||
t.setHours(11);
|
||||
summary = '11-oclock thing (n=' + n + ')';
|
||||
this._fakeEvents.push(new CalendarEvent(t, summary, false));
|
||||
}
|
||||
|
||||
// 'Weekly Meeting' is an event occuring every seven days at 1:45pm (two days displaced)
|
||||
for (let n = 0; n < 5; n++) {
|
||||
let t = new Date(midnightToday.getTime() + (n * 7 + 2) * 86400 * 1000);
|
||||
t.setHours(13);
|
||||
t.setMinutes(45);
|
||||
summary = 'Weekly Meeting (n=' + n + ')';
|
||||
this._fakeEvents.push(new CalendarEvent(t, summary, false));
|
||||
}
|
||||
|
||||
// 'Fun All Day' is an all-day event occuring every fortnight (three days displayed)
|
||||
for (let n = 0; n < 10; n++) {
|
||||
let t = new Date(midnightToday.getTime() + (n * 14 + 3) * 86400 * 1000);
|
||||
summary = 'Fun All Day (n=' + n + ')';
|
||||
this._fakeEvents.push(new CalendarEvent(t, summary, true));
|
||||
}
|
||||
|
||||
// 'Get Married' is an event that actually reflects reality (Dec 4, 2010) :-)
|
||||
this._fakeEvents.push(new CalendarEvent(new Date(2010, 11, 4, 16, 0), 'Get Married', false));
|
||||
|
||||
// ditto for 'NE Patriots vs NY Jets'
|
||||
this._fakeEvents.push(new CalendarEvent(new Date(2010, 11, 6, 20, 30), 'NE Patriots vs NY Jets', false));
|
||||
|
||||
// An event for tomorrow @6:30pm that is added/removed every five
|
||||
// seconds (to check that the ::changed signal works)
|
||||
let transientEventDate = new Date(midnightToday.getTime() + 86400 * 1000);
|
||||
transientEventDate.setHours(18);
|
||||
transientEventDate.setMinutes(30);
|
||||
transientEventDate.setSeconds(0);
|
||||
Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
|
||||
this._includeTransientEvent = false;
|
||||
this._transientEvent = new CalendarEvent(transientEventDate, 'A Transient Event', false);
|
||||
this._transientEventCounter = 1;
|
||||
},
|
||||
|
||||
_updateTransientEvent: function() {
|
||||
this._includeTransientEvent = !this._includeTransientEvent;
|
||||
this._transientEventCounter = this._transientEventCounter + 1;
|
||||
this._transientEvent.summary = 'A Transient Event (' + this._transientEventCounter + ')';
|
||||
this.emit('changed');
|
||||
Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
|
||||
},
|
||||
|
||||
requestRange: function(begin, end) {
|
||||
},
|
||||
|
||||
getEvents: function(begin, end) {
|
||||
let result = [];
|
||||
//log('begin:' + begin);
|
||||
//log('end: ' + end);
|
||||
for(let n = 0; n < this._fakeEvents.length; n++) {
|
||||
let event = this._fakeEvents[n];
|
||||
if (event.date >= begin && event.date <= end) {
|
||||
result.push(event);
|
||||
}
|
||||
//log('when:' + event.date + ' summary:' + event.summary);
|
||||
}
|
||||
if (this._includeTransientEvent && this._transientEvent.date >= begin && this._transientEvent.date <= end)
|
||||
result.push(this._transientEvent);
|
||||
result.sort(function(event1, event2) {
|
||||
return event1.date.getTime() - event2.date.getTime();
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
hasEvents: function(day) {
|
||||
let dayBegin = _getBeginningOfDay(day);
|
||||
let dayEnd = _getEndOfDay(day);
|
||||
|
||||
let events = this.getEvents(dayBegin, dayEnd);
|
||||
|
||||
if (events.length == 0)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
};
|
||||
Signals.addSignalMethods(FakeEventSource.prototype);
|
||||
|
||||
// Calendar:
|
||||
// @eventSource: is an object implementing the EventSource API, e.g. the
|
||||
// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
|
||||
function Calendar(eventSource) {
|
||||
this._init(eventSource);
|
||||
}
|
||||
|
||||
Calendar.prototype = {
|
||||
_init: function(eventSource) {
|
||||
this._eventSource = eventSource;
|
||||
|
||||
this._eventSource.connect('changed', Lang.bind(this, this._update));
|
||||
|
||||
// FIXME: This is actually the fallback method for GTK+ for the week start;
|
||||
// GTK+ by preference uses nl_langinfo (NL_TIME_FIRST_WEEKDAY). We probably
|
||||
// should add a C function so we can do the full handling.
|
||||
this._weekStart = NaN;
|
||||
let weekStartString = Gettext_gtk20.gettext("calendar:week_start:0");
|
||||
if (weekStartString.indexOf("calendar:week_start:") == 0) {
|
||||
this._weekdate = NaN;
|
||||
this._digitWidth = NaN;
|
||||
this._settings = new Gio.Settings({ schema: 'org.gnome.shell.calendar' });
|
||||
|
||||
this._settings.connect('changed::' + SHOW_WEEKDATE_KEY, Lang.bind(this, this._onSettingsChange));
|
||||
this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY);
|
||||
|
||||
let weekStartString = Gettext_gtk30.gettext('calendar:week_start:0');
|
||||
if (weekStartString.indexOf('calendar:week_start:') == 0) {
|
||||
this._weekStart = parseInt(weekStartString.substring(20));
|
||||
}
|
||||
|
||||
if (isNaN(this._weekStart) || this._weekStart < 0 || this._weekStart > 6) {
|
||||
log("Translation of 'calendar:week_start:0' in GTK+ is not correct");
|
||||
this.weekStart = 0;
|
||||
log('Translation of "calendar:week_start:0" in GTK+ is not correct');
|
||||
this._weekStart = 0;
|
||||
}
|
||||
|
||||
// Find the ordering for month/year in the calendar heading
|
||||
switch (Gettext_gtk20.gettext("calendar:MY")) {
|
||||
case "calendar:MY":
|
||||
this._headerFormat = "%B %Y";
|
||||
this._headerFormatWithoutYear = '%B';
|
||||
switch (Gettext_gtk30.gettext('calendar:MY')) {
|
||||
case 'calendar:MY':
|
||||
this._headerFormat = '%B %Y';
|
||||
break;
|
||||
case "calendar:YM":
|
||||
this._headerFormat = "%Y %B";
|
||||
case 'calendar:YM':
|
||||
this._headerFormat = '%Y %B';
|
||||
break;
|
||||
default:
|
||||
log("Translation of 'calendar:MY' in GTK+ is not correct");
|
||||
this._headerFormat = "%B %Y";
|
||||
log('Translation of "calendar:MY" in GTK+ is not correct');
|
||||
this._headerFormat = '%B %Y';
|
||||
break;
|
||||
}
|
||||
|
||||
// Start off with the current date
|
||||
this.date = new Date();
|
||||
this._selectedDate = new Date();
|
||||
|
||||
this.actor = new St.Table({ homogeneous: false,
|
||||
style_class: "calendar",
|
||||
style_class: 'calendar',
|
||||
reactive: true });
|
||||
|
||||
this.actor.connect('scroll-event',
|
||||
Lang.bind(this, this._onScroll));
|
||||
|
||||
// Top line of the calendar '<| September 2009 |>'
|
||||
this._topBox = new St.BoxLayout();
|
||||
this.actor.add(this._topBox,
|
||||
{ row: 0, col: 0, col_span: 7 });
|
||||
|
||||
let back = new St.Button({ label: "<", style_class: 'calendar-change-month' });
|
||||
this._topBox.add(back);
|
||||
back.connect("clicked", Lang.bind(this, this._prevMonth));
|
||||
|
||||
this._dateLabel = new St.Label();
|
||||
this._topBox.add(this._dateLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
|
||||
|
||||
let forward = new St.Button({ label: ">", style_class: 'calendar-change-month' });
|
||||
this._topBox.add(forward);
|
||||
forward.connect("clicked", Lang.bind(this, this._nextMonth));
|
||||
|
||||
// We need to figure out the abbreviated localized names for the days of the week;
|
||||
// we do this by just getting the next 7 days starting from right now and then putting
|
||||
// them in the right cell in the table. It doesn't matter if we add them in order
|
||||
let iter = new Date(this.date);
|
||||
iter.setSeconds(0); // Leap second protection. Hah!
|
||||
iter.setHours(12);
|
||||
for (let i = 0; i < 7; i++) {
|
||||
this.actor.add(new St.Label({ text: iter.toLocaleFormat("%a") }),
|
||||
{ row: 1,
|
||||
col: (7 + iter.getDay() - this._weekStart) % 7,
|
||||
x_fill: false, x_align: 1.0 });
|
||||
iter.setTime(iter.getTime() + MSECS_IN_DAY);
|
||||
}
|
||||
|
||||
// All the children after this are days, and get removed when we update the calendar
|
||||
this._firstDayIndex = this.actor.get_children().length;
|
||||
|
||||
this._buildHeader ();
|
||||
this._update();
|
||||
},
|
||||
|
||||
// Sets the calendar to show a specific date
|
||||
setDate: function(date) {
|
||||
if (!_sameDay(date, this.date)) {
|
||||
this.date = date;
|
||||
if (!_sameDay(date, this._selectedDate)) {
|
||||
this._selectedDate = date;
|
||||
this._update();
|
||||
this.emit('selected-date-changed', new Date(this._selectedDate));
|
||||
}
|
||||
},
|
||||
|
||||
_buildHeader: function() {
|
||||
let offsetCols = this._useWeekdate ? 1 : 0;
|
||||
this.actor.destroy_children();
|
||||
|
||||
// Top line of the calendar '<| September 2009 |>'
|
||||
this._topBox = new St.BoxLayout();
|
||||
this.actor.add(this._topBox,
|
||||
{ row: 0, col: 0, col_span: offsetCols + 7 });
|
||||
|
||||
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChange));
|
||||
|
||||
let back = new St.Button({ style_class: 'calendar-change-month-back' });
|
||||
this._topBox.add(back);
|
||||
back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
|
||||
|
||||
this._monthLabel = new St.Label({style_class: 'calendar-month-label'});
|
||||
this._topBox.add(this._monthLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
|
||||
|
||||
let forward = new St.Button({ style_class: 'calendar-change-month-forward' });
|
||||
this._topBox.add(forward);
|
||||
forward.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
|
||||
|
||||
// Add weekday labels...
|
||||
//
|
||||
// We need to figure out the abbreviated localized names for the days of the week;
|
||||
// we do this by just getting the next 7 days starting from right now and then putting
|
||||
// them in the right cell in the table. It doesn't matter if we add them in order
|
||||
let iter = new Date(this._selectedDate);
|
||||
iter.setSeconds(0); // Leap second protection. Hah!
|
||||
iter.setHours(12);
|
||||
for (let i = 0; i < 7; i++) {
|
||||
// Could use iter.toLocaleFormat('%a') but that normally gives three characters
|
||||
// and we want, ideally, a single character for e.g. S M T W T F S
|
||||
let customDayAbbrev = _getCalendarDayAbbreviation(iter.getDay());
|
||||
let label = new St.Label({ style_class: 'calendar-day-base calendar-day-heading',
|
||||
text: customDayAbbrev });
|
||||
this.actor.add(label,
|
||||
{ row: 1,
|
||||
col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7,
|
||||
x_fill: false, x_align: St.Align.MIDDLE });
|
||||
iter.setTime(iter.getTime() + MSECS_IN_DAY);
|
||||
}
|
||||
|
||||
// All the children after this are days, and get removed when we update the calendar
|
||||
this._firstDayIndex = this.actor.get_children().length;
|
||||
},
|
||||
|
||||
_onStyleChange: function(actor, event) {
|
||||
// width of a digit in pango units
|
||||
this._digitWidth = _getDigitWidth(this.actor) / Pango.SCALE;
|
||||
this._setWeekdateHeaderWidth();
|
||||
},
|
||||
|
||||
_setWeekdateHeaderWidth: function() {
|
||||
if (this.digitWidth != NaN && this._useWeekdate && this._weekdateHeader) {
|
||||
this._weekdateHeader.set_width (this._digitWidth * WEEKDATE_HEADER_WIDTH_DIGITS);
|
||||
}
|
||||
},
|
||||
|
||||
@ -106,37 +477,72 @@ Calendar.prototype = {
|
||||
switch (event.get_scroll_direction()) {
|
||||
case Clutter.ScrollDirection.UP:
|
||||
case Clutter.ScrollDirection.LEFT:
|
||||
this._prevMonth();
|
||||
this._onPrevMonthButtonClicked();
|
||||
break;
|
||||
case Clutter.ScrollDirection.DOWN:
|
||||
case Clutter.ScrollDirection.RIGHT:
|
||||
this._nextMonth();
|
||||
this._onNextMonthButtonClicked();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
_prevMonth: function() {
|
||||
if (this.date.getMonth() == 0) {
|
||||
this.date.setMonth(11);
|
||||
this.date.setFullYear(this.date.getFullYear() - 1);
|
||||
} else {
|
||||
this.date.setMonth(this.date.getMonth() - 1);
|
||||
_onPrevMonthButtonClicked: function() {
|
||||
let newDate = new Date(this._selectedDate);
|
||||
let oldMonth = newDate.getMonth();
|
||||
if (oldMonth == 0) {
|
||||
newDate.setMonth(11);
|
||||
newDate.setFullYear(newDate.getFullYear() - 1);
|
||||
if (newDate.getMonth() != 11) {
|
||||
let day = 32 - new Date(newDate.getFullYear() - 1, 11, 32).getDate();
|
||||
newDate = new Date(newDate.getFullYear() - 1, 11, day);
|
||||
}
|
||||
}
|
||||
this._update();
|
||||
else {
|
||||
newDate.setMonth(oldMonth - 1);
|
||||
if (newDate.getMonth() != oldMonth - 1) {
|
||||
let day = 32 - new Date(newDate.getFullYear(), oldMonth - 1, 32).getDate();
|
||||
newDate = new Date(newDate.getFullYear(), oldMonth - 1, day);
|
||||
}
|
||||
}
|
||||
|
||||
this.setDate(newDate);
|
||||
},
|
||||
|
||||
_nextMonth: function() {
|
||||
if (this.date.getMonth() == 11) {
|
||||
this.date.setMonth(0);
|
||||
this.date.setFullYear(this.date.getFullYear() + 1);
|
||||
} else {
|
||||
this.date.setMonth(this.date.getMonth() + 1);
|
||||
_onNextMonthButtonClicked: function() {
|
||||
let newDate = new Date(this._selectedDate);
|
||||
let oldMonth = newDate.getMonth();
|
||||
if (oldMonth == 11) {
|
||||
newDate.setMonth(0);
|
||||
newDate.setFullYear(newDate.getFullYear() + 1);
|
||||
if (newDate.getMonth() != 0) {
|
||||
let day = 32 - new Date(newDate.getFullYear() + 1, 0, 32).getDate();
|
||||
newDate = new Date(newDate.getFullYear() + 1, 0, day);
|
||||
}
|
||||
}
|
||||
else {
|
||||
newDate.setMonth(oldMonth + 1);
|
||||
if (newDate.getMonth() != oldMonth + 1) {
|
||||
let day = 32 - new Date(newDate.getFullYear(), oldMonth + 1, 32).getDate();
|
||||
newDate = new Date(newDate.getFullYear(), oldMonth + 1, day);
|
||||
}
|
||||
}
|
||||
|
||||
this.setDate(newDate);
|
||||
},
|
||||
|
||||
_onSettingsChange: function() {
|
||||
this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY);
|
||||
this._buildHeader();
|
||||
this._update();
|
||||
},
|
||||
|
||||
_update: function() {
|
||||
this._dateLabel.text = this.date.toLocaleFormat(this._headerFormat);
|
||||
let now = new Date();
|
||||
|
||||
if (_sameYear(this._selectedDate, now))
|
||||
this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear);
|
||||
else
|
||||
this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat);
|
||||
|
||||
// Remove everything but the topBox and the weekday labels
|
||||
let children = this.actor.get_children();
|
||||
@ -144,34 +550,215 @@ Calendar.prototype = {
|
||||
children[i].destroy();
|
||||
|
||||
// Start at the beginning of the week before the start of the month
|
||||
let iter = new Date(this.date);
|
||||
iter.setDate(1);
|
||||
iter.setSeconds(0);
|
||||
iter.setHours(12);
|
||||
iter.setTime(iter.getTime() - (iter.getDay() - this._weekStart) * MSECS_IN_DAY);
|
||||
|
||||
let now = new Date();
|
||||
let beginDate = new Date(this._selectedDate);
|
||||
beginDate.setDate(1);
|
||||
beginDate.setSeconds(0);
|
||||
beginDate.setHours(12);
|
||||
let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7;
|
||||
beginDate.setTime(beginDate.getTime() - daysToWeekStart * MSECS_IN_DAY);
|
||||
|
||||
let iter = new Date(beginDate);
|
||||
let row = 2;
|
||||
while (true) {
|
||||
let label = new St.Label({ text: iter.getDate().toString() });
|
||||
if (_sameDay(now, iter))
|
||||
label.style_class = "calendar-day calendar-today";
|
||||
else if (iter.getMonth() != this.date.getMonth())
|
||||
label.style_class = "calendar-day calendar-other-month-day";
|
||||
let button = new St.Button({ label: iter.getDate().toString() });
|
||||
|
||||
let iterStr = iter.toUTCString();
|
||||
button.connect('clicked', Lang.bind(this, function() {
|
||||
let newlySelectedDate = new Date(iterStr);
|
||||
this.setDate(newlySelectedDate);
|
||||
}));
|
||||
|
||||
let hasEvents = this._eventSource.hasEvents(iter);
|
||||
let styleClass = 'calendar-day-base calendar-day';
|
||||
if (_isWorkDay(iter))
|
||||
styleClass += ' calendar-work-day'
|
||||
else
|
||||
label.style_class = "calendar-day";
|
||||
this.actor.add(label,
|
||||
{ row: row, col: (7 + iter.getDay() - this._weekStart) % 7,
|
||||
x_fill: false, x_align: 1.0 });
|
||||
styleClass += ' calendar-nonwork-day'
|
||||
|
||||
// Hack used in lieu of border-collapse - see gnome-shell.css
|
||||
if (row == 2)
|
||||
styleClass = 'calendar-day-top ' + styleClass;
|
||||
if (iter.getDay() == this._weekStart)
|
||||
styleClass = 'calendar-day-left ' + styleClass;
|
||||
|
||||
if (_sameDay(now, iter))
|
||||
styleClass += ' calendar-today';
|
||||
else if (iter.getMonth() != this._selectedDate.getMonth())
|
||||
styleClass += ' calendar-other-month-day';
|
||||
|
||||
if (_sameDay(this._selectedDate, iter))
|
||||
button.add_style_pseudo_class('active');
|
||||
|
||||
if (hasEvents)
|
||||
styleClass += ' calendar-day-with-events'
|
||||
|
||||
button.style_class = styleClass;
|
||||
|
||||
let offsetCols = this._useWeekdate ? 1 : 0;
|
||||
this.actor.add(button,
|
||||
{ row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 });
|
||||
|
||||
if (this._useWeekdate && iter.getDay() == 4) {
|
||||
let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(),
|
||||
style_class: 'calendar-day-base calendar-week-number'});
|
||||
this.actor.add(label,
|
||||
{ row: row, col: 0, y_align: St.Align.MIDDLE });
|
||||
}
|
||||
|
||||
iter.setTime(iter.getTime() + MSECS_IN_DAY);
|
||||
if (iter.getDay() == this._weekStart) {
|
||||
// We stop on the first "first day of the week" after the month we are displaying
|
||||
if (iter.getMonth() > this.date.getMonth() || iter.getYear() > this.date.getYear())
|
||||
if (iter.getMonth() > this._selectedDate.getMonth() || iter.getYear() > this._selectedDate.getYear())
|
||||
break;
|
||||
row++;
|
||||
}
|
||||
}
|
||||
// Signal to the event source that we are interested in events
|
||||
// only from this date range
|
||||
this._eventSource.requestRange(beginDate, iter);
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(Calendar.prototype);
|
||||
|
||||
function EventsList(eventSource) {
|
||||
this._init(eventSource);
|
||||
}
|
||||
|
||||
EventsList.prototype = {
|
||||
_init: function(eventSource) {
|
||||
this.actor = new St.BoxLayout({ vertical: true, style_class: 'events-header-vbox'});
|
||||
this._date = new Date();
|
||||
this._eventSource = eventSource;
|
||||
this._eventSource.connect('changed', Lang.bind(this, this._update));
|
||||
this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
|
||||
this._desktopSettings.connect('changed', Lang.bind(this, this._update));
|
||||
let weekStartString = Gettext_gtk30.gettext('calendar:week_start:0');
|
||||
if (weekStartString.indexOf('calendar:week_start:') == 0) {
|
||||
this._weekStart = parseInt(weekStartString.substring(20));
|
||||
}
|
||||
|
||||
if (isNaN(this._weekStart) ||
|
||||
this._weekStart < 0 ||
|
||||
this._weekStart > 6) {
|
||||
log('Translation of "calendar:week_start:0" in GTK+ is not correct');
|
||||
this._weekStart = 0;
|
||||
}
|
||||
|
||||
this._update();
|
||||
},
|
||||
|
||||
_addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
|
||||
if (includeDayName) {
|
||||
dayNameBox.add(new St.Label( { style_class: 'events-day-dayname',
|
||||
text: day } ),
|
||||
{ x_fill: true } );
|
||||
}
|
||||
timeBox.add(new St.Label( { style_class: 'events-day-time',
|
||||
text: time} ),
|
||||
{ x_fill: true } );
|
||||
eventTitleBox.add(new St.Label( { style_class: 'events-day-task',
|
||||
text: desc} ));
|
||||
},
|
||||
|
||||
_addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
|
||||
let events = this._eventSource.getEvents(begin, end);
|
||||
|
||||
let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
|
||||
|
||||
if (events.length == 0 && !showNothingScheduled)
|
||||
return;
|
||||
|
||||
let vbox = new St.BoxLayout( {vertical: true} );
|
||||
this.actor.add(vbox);
|
||||
|
||||
vbox.add(new St.Label({ style_class: 'events-day-header', text: header }));
|
||||
let box = new St.BoxLayout({style_class: 'events-header-hbox'});
|
||||
let dayNameBox = new St.BoxLayout({ vertical: true, style_class: 'events-day-name-box' });
|
||||
let timeBox = new St.BoxLayout({ vertical: true, style_class: 'events-time-box' });
|
||||
let eventTitleBox = new St.BoxLayout({ vertical: true, style_class: 'events-event-box' });
|
||||
box.add(dayNameBox, {x_fill: false});
|
||||
box.add(timeBox, {x_fill: false});
|
||||
box.add(eventTitleBox, {expand: true});
|
||||
vbox.add(box);
|
||||
|
||||
for (let n = 0; n < events.length; n++) {
|
||||
let event = events[n];
|
||||
let dayString = _getEventDayAbbreviation(event.date.getDay());
|
||||
let timeString = _formatEventTime(event, clockFormat);
|
||||
let summaryString = event.summary;
|
||||
this._addEvent(dayNameBox, timeBox, eventTitleBox, includeDayName, dayString, timeString, summaryString);
|
||||
}
|
||||
|
||||
if (events.length == 0 && showNothingScheduled) {
|
||||
let now = new Date();
|
||||
/* Translators: Text to show if there are no events */
|
||||
let nothingEvent = new CalendarEvent(now, _("Nothing Scheduled"), true);
|
||||
let timeString = _formatEventTime(nothingEvent, clockFormat);
|
||||
this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingEvent.summary);
|
||||
}
|
||||
},
|
||||
|
||||
_showOtherDay: function(day) {
|
||||
this.actor.destroy_children();
|
||||
|
||||
let dayBegin = _getBeginningOfDay(day);
|
||||
let dayEnd = _getEndOfDay(day);
|
||||
|
||||
let dayString;
|
||||
let now = new Date();
|
||||
if (_sameYear(day, now))
|
||||
dayString = day.toLocaleFormat('%A, %B %d');
|
||||
else
|
||||
dayString = day.toLocaleFormat('%A, %B %d, %Y');
|
||||
this._addPeriod(dayString, dayBegin, dayEnd, false, true);
|
||||
},
|
||||
|
||||
_showToday: function() {
|
||||
this.actor.destroy_children();
|
||||
|
||||
let now = new Date();
|
||||
let dayBegin = _getBeginningOfDay(now);
|
||||
let dayEnd = _getEndOfDay(now);
|
||||
this._addPeriod(_("Today"), dayBegin, dayEnd, false, true);
|
||||
|
||||
let tomorrowBegin = new Date(dayBegin.getTime() + 86400 * 1000);
|
||||
let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000);
|
||||
this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true);
|
||||
|
||||
if (dayEnd.getDay() <= 4 + this._weekStart) {
|
||||
/* If now is within the first 5 days we show "This week" and
|
||||
* include events up until and including Saturday/Sunday
|
||||
* (depending on whether a week starts on Sunday/Monday).
|
||||
*/
|
||||
let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
|
||||
let thisWeekEnd = new Date(dayEnd.getTime() + (6 + this._weekStart - dayEnd.getDay()) * 86400 * 1000);
|
||||
this._addPeriod(_("This week"), thisWeekBegin, thisWeekEnd, true, false);
|
||||
} else {
|
||||
/* otherwise it's one of the two last days of the week ... show
|
||||
* "Next week" and include events up until and including *next*
|
||||
* Saturday/Sunday
|
||||
*/
|
||||
let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
|
||||
let nextWeekEnd = new Date(dayEnd.getTime() + (13 + this._weekStart - dayEnd.getDay()) * 86400 * 1000);
|
||||
this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, true, false);
|
||||
}
|
||||
},
|
||||
|
||||
// Sets the event list to show events from a specific date
|
||||
setDate: function(date) {
|
||||
if (!_sameDay(date, this._date)) {
|
||||
this._date = date;
|
||||
this._update();
|
||||
}
|
||||
},
|
||||
|
||||
_update: function() {
|
||||
let today = new Date();
|
||||
if (_sameDay (this._date, today)) {
|
||||
this._showToday();
|
||||
} else {
|
||||
this._showOtherDay(this._date);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
176
js/ui/chrome.js
@ -1,10 +1,10 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
@ -13,6 +13,19 @@ const Params = imports.misc.params;
|
||||
// normal mode (ie, outside the Overview), that surrounds the main
|
||||
// workspace content.
|
||||
|
||||
const Visibility = {
|
||||
FULL: 1,
|
||||
FULLSCREEN: 2,
|
||||
OVERVIEW: 3
|
||||
};
|
||||
|
||||
const defaultParams = {
|
||||
visibleInOverview: false,
|
||||
visibleInFullscreen: false,
|
||||
affectsStruts: true,
|
||||
affectsInputRegion: true
|
||||
};
|
||||
|
||||
function Chrome() {
|
||||
this._init();
|
||||
}
|
||||
@ -20,12 +33,13 @@ function Chrome() {
|
||||
Chrome.prototype = {
|
||||
_init: function() {
|
||||
// The group itself has zero size so it doesn't interfere with DND
|
||||
this.actor = new Clutter.Group({ width: 0, height: 0 });
|
||||
global.stage.add_actor(this.actor);
|
||||
this.nonOverviewActor = new Clutter.Group();
|
||||
this.actor.add_actor(this.nonOverviewActor);
|
||||
this.actor = new Shell.GenericContainer({ width: 0, height: 0 });
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocated));
|
||||
|
||||
this._obscuredByFullscreen = false;
|
||||
this._inFullscreen = false;
|
||||
this._inOverview = false;
|
||||
this.visibility = Visibility.FULL;
|
||||
|
||||
this._trackedActors = [];
|
||||
|
||||
@ -44,13 +58,10 @@ Chrome.prototype = {
|
||||
this._queueUpdateRegions();
|
||||
},
|
||||
|
||||
_verifyAncestry: function(actor, ancestor) {
|
||||
while (actor) {
|
||||
if (actor == ancestor)
|
||||
return true;
|
||||
actor = actor.get_parent();
|
||||
}
|
||||
return false;
|
||||
_allocated: function(actor, box, flags) {
|
||||
let children = this.actor.get_children();
|
||||
for (let i = 0; i < children.length; i++)
|
||||
children[i].allocate_preferred_size(flags);
|
||||
},
|
||||
|
||||
// addActor:
|
||||
@ -67,19 +78,15 @@ Chrome.prototype = {
|
||||
//
|
||||
// If %visibleInOverview is %true in @params, @actor will remain
|
||||
// visible when the overview is brought up. Otherwise it will
|
||||
// automatically be hidden. If %affectsStruts or %affectsInputRegion
|
||||
// is %false, the actor will not have the indicated effect.
|
||||
// automatically be hidden. Likewise, if %visibleInFullscreen is
|
||||
// %true, the actor will be visible even when a fullscreen window
|
||||
// should be covering it.
|
||||
//
|
||||
// If %affectsStruts or %affectsInputRegion is %false, the actor
|
||||
// will not have the indicated effect.
|
||||
addActor: function(actor, params) {
|
||||
params = Params.parse(params, { visibleInOverview: false,
|
||||
affectsStruts: true,
|
||||
affectsInputRegion: true });
|
||||
|
||||
if (params.visibleInOverview)
|
||||
this.actor.add_actor(actor);
|
||||
else
|
||||
this.nonOverviewActor.add_actor(actor);
|
||||
|
||||
this._trackActor(actor, params.affectsInputRegion, params.affectsStruts);
|
||||
this.actor.add_actor(actor);
|
||||
this._trackActor(actor, params);
|
||||
},
|
||||
|
||||
// trackActor:
|
||||
@ -89,13 +96,32 @@ Chrome.prototype = {
|
||||
// Tells the chrome to track @actor, which must be a descendant
|
||||
// of an actor added via addActor(). This can be used to extend the
|
||||
// struts or input region to cover specific children.
|
||||
//
|
||||
// @params can have any of the same values as in addActor(), though
|
||||
// some possibilities don't make sense (eg, trying to have a
|
||||
// %visibleInOverview child of a non-%visibleInOverview parent).
|
||||
// By default, @actor has the same params as its chrome ancestor.
|
||||
trackActor: function(actor, params) {
|
||||
if (!this._verifyAncestry(actor, this.actor))
|
||||
let ancestor = actor.get_parent();
|
||||
let index = this._findActor(ancestor);
|
||||
while (ancestor && index == -1) {
|
||||
ancestor = ancestor.get_parent();
|
||||
index = this._findActor(ancestor);
|
||||
}
|
||||
if (!ancestor)
|
||||
throw new Error('actor is not a descendent of the chrome layer');
|
||||
|
||||
params = Params.parse(params, { affectsStruts: true,
|
||||
affectsInputRegion: true });
|
||||
this._trackActor(actor, params.affectsInputRegion, params.affectsStruts);
|
||||
let ancestorData = this._trackedActors[index];
|
||||
if (!params)
|
||||
params = {};
|
||||
// We can't use Params.parse here because we want to drop
|
||||
// the extra values like ancestorData.actor
|
||||
for (let prop in defaultParams) {
|
||||
if (!params[prop])
|
||||
params[prop] = ancestorData[prop];
|
||||
}
|
||||
|
||||
this._trackActor(actor, params);
|
||||
},
|
||||
|
||||
// untrackActor:
|
||||
@ -111,10 +137,7 @@ Chrome.prototype = {
|
||||
//
|
||||
// Removes @actor from the chrome layer
|
||||
removeActor: function(actor) {
|
||||
if (actor.get_parent() == this.nonOverviewActor)
|
||||
this.nonOverviewActor.remove_actor(actor);
|
||||
else
|
||||
this.actor.remove_actor(actor);
|
||||
this.actor.remove_actor(actor);
|
||||
this._untrackActor(actor);
|
||||
},
|
||||
|
||||
@ -127,16 +150,12 @@ Chrome.prototype = {
|
||||
return -1;
|
||||
},
|
||||
|
||||
_trackActor: function(actor, inputRegion, strut) {
|
||||
let actorData;
|
||||
|
||||
_trackActor: function(actor, params) {
|
||||
if (this._findActor(actor) != -1)
|
||||
throw new Error('trying to re-track existing chrome actor');
|
||||
|
||||
actorData = { actor: actor,
|
||||
inputRegion: inputRegion,
|
||||
strut: strut };
|
||||
|
||||
let actorData = Params.parse(params, defaultParams);
|
||||
actorData.actor = actor;
|
||||
actorData.visibleId = actor.connect('notify::visible',
|
||||
Lang.bind(this, this._queueUpdateRegions));
|
||||
actorData.allocationId = actor.connect('notify::allocation',
|
||||
@ -166,20 +185,44 @@ Chrome.prototype = {
|
||||
},
|
||||
|
||||
_actorReparented: function(actor, oldParent) {
|
||||
if (!this._verifyAncestry(actor, this.actor))
|
||||
if (!this.actor.contains(actor))
|
||||
this._untrackActor(actor);
|
||||
},
|
||||
|
||||
_updateVisibility: function() {
|
||||
for (let i = 0; i < this._trackedActors.length; i++) {
|
||||
let actorData = this._trackedActors[i];
|
||||
if (this._inOverview && !actorData.visibleInOverview)
|
||||
this.actor.set_skip_paint(actorData.actor, true);
|
||||
else if (!this._inOverview && this._inFullscreen && !actorData.visibleInFullscreen)
|
||||
this.actor.set_skip_paint(actorData.actor, true);
|
||||
else
|
||||
this.actor.set_skip_paint(actorData.actor, false);
|
||||
}
|
||||
|
||||
let newVisibility;
|
||||
if (this._inOverview)
|
||||
newVisibility = Visibility.OVERVIEW;
|
||||
else if (this._inFullscreen)
|
||||
newVisibility = Visibility.FULLSCREEN;
|
||||
else
|
||||
newVisibility = Visibility.FULL;
|
||||
|
||||
if (newVisibility != this.visibility) {
|
||||
this.visibility = newVisibility;
|
||||
this.emit('visibility-changed', this.visibility);
|
||||
}
|
||||
},
|
||||
|
||||
_overviewShowing: function() {
|
||||
this.actor.show();
|
||||
this.nonOverviewActor.hide();
|
||||
this._inOverview = true;
|
||||
this._updateVisibility();
|
||||
this._queueUpdateRegions();
|
||||
},
|
||||
|
||||
_overviewHidden: function() {
|
||||
if (this._obscuredByFullscreen)
|
||||
this.actor.hide();
|
||||
this.nonOverviewActor.show();
|
||||
this._inOverview = false;
|
||||
this._updateVisibility();
|
||||
this._queueUpdateRegions();
|
||||
},
|
||||
|
||||
@ -190,13 +233,13 @@ Chrome.prototype = {
|
||||
},
|
||||
|
||||
_windowsRestacked: function() {
|
||||
let windows = global.get_windows();
|
||||
let windows = global.get_window_actors();
|
||||
let primary = global.get_primary_monitor();
|
||||
|
||||
// The chrome layer should be visible unless there is a window
|
||||
// with layer FULLSCREEN, or a window with layer
|
||||
// OVERRIDE_REDIRECT that covers the whole screen.
|
||||
// ("override_redirect" is not actually a layer above all
|
||||
// ('override_redirect' is not actually a layer above all
|
||||
// other windows, but this seems to be how mutter treats it
|
||||
// currently...) If we wanted to be extra clever, we could
|
||||
// figure out when an OVERRIDE_REDIRECT window was trying to
|
||||
@ -205,26 +248,40 @@ Chrome.prototype = {
|
||||
|
||||
// @windows is sorted bottom to top.
|
||||
|
||||
this._obscuredByFullscreen = false;
|
||||
let wasInFullscreen = this._inFullscreen;
|
||||
this._inFullscreen = false;
|
||||
for (let i = windows.length - 1; i > -1; i--) {
|
||||
let layer = windows[i].get_meta_window().get_layer();
|
||||
|
||||
if (layer == Meta.StackLayer.OVERRIDE_REDIRECT ||
|
||||
layer == Meta.StackLayer.FULLSCREEN) {
|
||||
// There are 3 cases we check here for:
|
||||
// 1.) Monitor sized window
|
||||
// 2.) Window with a position somewhere on the primary screen having the _NET_WM_FULLSCREEN flag set
|
||||
// 3.) Window that is partly off screen (tries to hide its decorations) which might have negative coords
|
||||
// We check for 1.) and 2.) by checking if the upper right corner is on the primary monitor, but avoid the case
|
||||
// where it overlaps with the secondary screen (like window.x + window.width == primary.x + primary.width)
|
||||
// For 3.) we just ignore negative values as they don't really make sense
|
||||
|
||||
if (layer == Meta.StackLayer.FULLSCREEN) {
|
||||
if (Math.max(windows[i].x, 0) >= primary.x && Math.max(windows[i].x, 0) < primary.x + primary.width &&
|
||||
Math.max(windows[i].y, 0) >= primary.y && Math.max(windows[i].y, 0) < primary.y + primary.height) {
|
||||
this._inFullscreen = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (layer == Meta.StackLayer.OVERRIDE_REDIRECT) {
|
||||
if (windows[i].x <= primary.x &&
|
||||
windows[i].x + windows[i].width >= primary.x + primary.width &&
|
||||
windows[i].y <= primary.y &&
|
||||
windows[i].y + windows[i].height >= primary.y + primary.height) {
|
||||
this._obscuredByFullscreen = true;
|
||||
this._inFullscreen = true;
|
||||
break;
|
||||
}
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
let shouldBeVisible = !this._obscuredByFullscreen || Main.overview.visible;
|
||||
if (this.actor.visible != shouldBeVisible) {
|
||||
this.actor.visible = shouldBeVisible;
|
||||
if (this._inFullscreen != wasInFullscreen) {
|
||||
this._updateVisibility();
|
||||
this._queueUpdateRegions();
|
||||
}
|
||||
},
|
||||
@ -236,7 +293,7 @@ Chrome.prototype = {
|
||||
|
||||
for (i = 0; i < this._trackedActors.length; i++) {
|
||||
let actorData = this._trackedActors[i];
|
||||
if (!actorData.inputRegion && !actorData.strut)
|
||||
if (!actorData.affectsInputRegion && !actorData.affectsStruts)
|
||||
continue;
|
||||
|
||||
let [x, y] = actorData.actor.get_transformed_position();
|
||||
@ -247,10 +304,12 @@ Chrome.prototype = {
|
||||
h = Math.round(h);
|
||||
let rect = new Meta.Rectangle({ x: x, y: y, width: w, height: h});
|
||||
|
||||
if (actorData.inputRegion && actorData.actor.get_paint_visibility())
|
||||
if (actorData.affectsInputRegion &&
|
||||
actorData.actor.get_paint_visibility() &&
|
||||
!this.actor.get_skip_paint(actorData.actor))
|
||||
rects.push(rect);
|
||||
|
||||
if (!actorData.strut)
|
||||
if (!actorData.affectsStruts)
|
||||
continue;
|
||||
|
||||
// Metacity wants to know what side of the screen the
|
||||
@ -302,3 +361,4 @@ Chrome.prototype = {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(Chrome.prototype);
|
||||
|
254
js/ui/ctrlAltTab.js
Normal file
@ -0,0 +1,254 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const AltTab = imports.ui.altTab;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const POPUP_APPICON_SIZE = 96;
|
||||
const POPUP_FADE_TIME = 0.1; // seconds
|
||||
|
||||
function CtrlAltTabManager() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
CtrlAltTabManager.prototype = {
|
||||
_init: function() {
|
||||
this._items = [];
|
||||
this._focusManager = St.FocusManager.get_for_stage(global.stage);
|
||||
Main.wm.setKeybindingHandler('switch_panels', Lang.bind(this,
|
||||
function (shellwm, binding, window, backwards) {
|
||||
this.popup(backwards);
|
||||
}));
|
||||
},
|
||||
|
||||
addGroup: function(root, name, icon) {
|
||||
this._items.push({ root: root, name: name, iconName: icon });
|
||||
root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); }));
|
||||
this._focusManager.add_group(root);
|
||||
},
|
||||
|
||||
removeGroup: function(root) {
|
||||
this._focusManager.remove_group(root);
|
||||
for (let i = 0; i < this._items.length; i++) {
|
||||
if (this._items[i].root == root) {
|
||||
this._items.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
focusGroup: function(root) {
|
||||
if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE ||
|
||||
global.stage_input_mode == Shell.StageInputMode.NORMAL)
|
||||
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
|
||||
root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
||||
},
|
||||
|
||||
popup: function(backwards) {
|
||||
// Start with the set of focus groups that are currently mapped
|
||||
let items = this._items.filter(function (item) { return item.root.mapped; });
|
||||
|
||||
// And add the windows metacity would show in its Ctrl-Alt-Tab list
|
||||
let screen = global.screen;
|
||||
let display = screen.get_display();
|
||||
let windows = display.get_tab_list(Meta.TabList.DOCKS, screen, screen.get_active_workspace ());
|
||||
let windowTracker = Shell.WindowTracker.get_default();
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let icon;
|
||||
let app = windowTracker.get_window_app(windows[i]);
|
||||
if (app)
|
||||
icon = app.create_icon_texture(POPUP_APPICON_SIZE);
|
||||
else
|
||||
icon = textureCache.bind_pixbuf_property(windows[i], 'icon');
|
||||
items.push({ window: windows[i],
|
||||
name: windows[i].title,
|
||||
iconActor: icon });
|
||||
}
|
||||
|
||||
if (!items.length)
|
||||
return;
|
||||
|
||||
new CtrlAltTabPopup().show(items, backwards);
|
||||
}
|
||||
};
|
||||
|
||||
function mod(a, b) {
|
||||
return (a + b) % b;
|
||||
}
|
||||
|
||||
function CtrlAltTabPopup() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
CtrlAltTabPopup.prototype = {
|
||||
_init : function() {
|
||||
let primary = global.get_primary_monitor();
|
||||
this.actor = new St.BoxLayout({ name: 'ctrlAltTabPopup',
|
||||
reactive: true,
|
||||
x: primary.x + primary.width / 2,
|
||||
y: primary.y + primary.height / 2,
|
||||
anchor_gravity: Clutter.Gravity.CENTER });
|
||||
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
|
||||
this._haveModal = false;
|
||||
this._selection = 0;
|
||||
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
},
|
||||
|
||||
show : function(items, startBackwards) {
|
||||
if (!Main.pushModal(this.actor))
|
||||
return false;
|
||||
this._haveModal = true;
|
||||
|
||||
this._keyPressEventId = this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
|
||||
this._keyReleaseEventId = this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
|
||||
|
||||
this._items = items;
|
||||
this._switcher = new CtrlAltTabSwitcher(items);
|
||||
this.actor.add_actor(this._switcher.actor);
|
||||
|
||||
if (startBackwards)
|
||||
this._selection = this._items.length - 1;
|
||||
this._select(this._selection);
|
||||
|
||||
let [x, y, mods] = global.get_pointer();
|
||||
if (!(mods & Gdk.ModifierType.MOD1_MASK)) {
|
||||
this._finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.actor.opacity = 0;
|
||||
this.actor.show();
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
time: POPUP_FADE_TIME,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_next : function() {
|
||||
return mod(this._selection + 1, this._items.length);
|
||||
},
|
||||
|
||||
_previous : function() {
|
||||
return mod(this._selection - 1, this._items.length);
|
||||
},
|
||||
|
||||
_keyPressEvent : function(actor, event) {
|
||||
let keysym = event.get_key_symbol();
|
||||
let shift = (Shell.get_event_state(event) & Clutter.ModifierType.SHIFT_MASK);
|
||||
if (shift && keysym == Clutter.KEY_Tab)
|
||||
keysym = Clutter.ISO_Left_Tab;
|
||||
|
||||
if (keysym == Clutter.KEY_Escape)
|
||||
this.destroy();
|
||||
else if (keysym == Clutter.KEY_Tab)
|
||||
this._select(this._next());
|
||||
else if (keysym == Clutter.KEY_ISO_Left_Tab)
|
||||
this._select(this._previous());
|
||||
else if (keysym == Clutter.KEY_Left)
|
||||
this._select(this._previous());
|
||||
else if (keysym == Clutter.KEY_Right)
|
||||
this._select(this._next());
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_keyReleaseEvent : function(actor, event) {
|
||||
let [x, y, mods] = global.get_pointer();
|
||||
let state = mods & Clutter.ModifierType.MOD1_MASK;
|
||||
|
||||
if (state == 0)
|
||||
this._finish();
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_finish : function() {
|
||||
this.destroy();
|
||||
|
||||
let item = this._items[this._selection];
|
||||
if (item.root)
|
||||
Main.ctrlAltTabManager.focusGroup(item.root);
|
||||
else
|
||||
Main.activateWindow(item.window);
|
||||
},
|
||||
|
||||
_popModal: function() {
|
||||
if (this._haveModal) {
|
||||
Main.popModal(this.actor);
|
||||
this._haveModal = false;
|
||||
}
|
||||
},
|
||||
|
||||
destroy : function() {
|
||||
this._popModal();
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: POPUP_FADE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this.actor.destroy();
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
_onDestroy : function() {
|
||||
if (this._keyPressEventId)
|
||||
this.actor.disconnect(this._keyPressEventId);
|
||||
if (this._keyReleaseEventId)
|
||||
this.actor.disconnect(this._keyReleaseEventId);
|
||||
},
|
||||
|
||||
_select : function(num) {
|
||||
this._selection = num;
|
||||
this._switcher.highlight(num);
|
||||
}
|
||||
};
|
||||
|
||||
function CtrlAltTabSwitcher(items) {
|
||||
this._init(items);
|
||||
}
|
||||
|
||||
CtrlAltTabSwitcher.prototype = {
|
||||
__proto__ : AltTab.SwitcherList.prototype,
|
||||
|
||||
_init : function(items) {
|
||||
AltTab.SwitcherList.prototype._init.call(this, true);
|
||||
|
||||
for (let i = 0; i < items.length; i++)
|
||||
this._addIcon(items[i]);
|
||||
},
|
||||
|
||||
_addIcon : function(item) {
|
||||
let box = new St.BoxLayout({ style_class: 'alt-tab-app',
|
||||
vertical: true });
|
||||
|
||||
let icon = item.iconActor;
|
||||
if (!icon) {
|
||||
icon = new St.Icon({ icon_name: item.iconName,
|
||||
icon_type: St.IconType.SYMBOLIC,
|
||||
icon_size: POPUP_APPICON_SIZE });
|
||||
}
|
||||
box.add(icon, { x_fill: false, y_fill: false } );
|
||||
|
||||
let text = new St.Label({ text: item.name });
|
||||
box.add(text, { x_fill: false });
|
||||
|
||||
this.addItem(box);
|
||||
}
|
||||
};
|
1189
js/ui/dash.js
212
js/ui/dateMenu.js
Normal file
@ -0,0 +1,212 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Cairo = imports.cairo;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Util = imports.misc.util;
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Calendar = imports.ui.calendar;
|
||||
|
||||
// in org.gnome.desktop.interface
|
||||
const CLOCK_FORMAT_KEY = 'clock-format';
|
||||
|
||||
// in org.gnome.shell.clock
|
||||
const CLOCK_SHOW_DATE_KEY = 'show-date';
|
||||
const CLOCK_SHOW_SECONDS_KEY = 'show-seconds';
|
||||
|
||||
function _onVertSepRepaint (area)
|
||||
{
|
||||
let cr = area.get_context();
|
||||
let themeNode = area.get_theme_node();
|
||||
let [width, height] = area.get_surface_size();
|
||||
let stippleColor = new Clutter.Color();
|
||||
let stippleWidth = themeNode.get_length('-stipple-width');
|
||||
let x = Math.floor(width/2) + 0.5;
|
||||
themeNode.lookup_color('-stipple-color', false, stippleColor);
|
||||
cr.moveTo(x, 0);
|
||||
cr.lineTo(x, height);
|
||||
Clutter.cairo_set_source_color(cr, stippleColor);
|
||||
cr.setDash([1, 3], 1); // Hard-code for now
|
||||
cr.setLineWidth(stippleWidth);
|
||||
cr.stroke();
|
||||
};
|
||||
|
||||
function DateMenuButton() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
DateMenuButton.prototype = {
|
||||
__proto__: PanelMenu.Button.prototype,
|
||||
|
||||
_init: function() {
|
||||
let item;
|
||||
let hbox;
|
||||
let vbox;
|
||||
|
||||
//this._eventSource = new Calendar.EmptyEventSource();
|
||||
//this._eventSource = new Calendar.FakeEventSource();
|
||||
this._eventSource = new Calendar.EvolutionEventSource();
|
||||
|
||||
PanelMenu.Button.prototype._init.call(this, St.Align.START);
|
||||
|
||||
this._clock = new St.Label();
|
||||
this.actor.set_child(this._clock);
|
||||
|
||||
hbox = new St.BoxLayout({name: 'calendarArea'});
|
||||
this.menu.addActor(hbox);
|
||||
|
||||
// Fill up the first column
|
||||
|
||||
vbox = new St.BoxLayout({vertical: true});
|
||||
hbox.add(vbox);
|
||||
|
||||
// Date
|
||||
this._date = new St.Label();
|
||||
this._date.style_class = 'datemenu-date-label';
|
||||
vbox.add(this._date);
|
||||
|
||||
this._eventList = new Calendar.EventsList(this._eventSource);
|
||||
|
||||
// Calendar
|
||||
this._calendar = new Calendar.Calendar(this._eventSource);
|
||||
this._calendar.connect('selected-date-changed',
|
||||
Lang.bind(this, function(calendar, date) {
|
||||
this._eventList.setDate(date);
|
||||
}));
|
||||
vbox.add(this._calendar.actor);
|
||||
|
||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
item.setColumnWidths(1);
|
||||
vbox.add(item.actor, {y_align: St.Align.END, expand: true, y_fill: false});
|
||||
item = new PopupMenu.PopupMenuItem(_("Date and Time Settings"));
|
||||
item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
|
||||
vbox.add(item.actor);
|
||||
|
||||
// Add vertical separator
|
||||
|
||||
item = new St.DrawingArea({ style_class: 'calendar-vertical-separator',
|
||||
pseudo_class: 'highlighted' });
|
||||
item.connect('repaint', Lang.bind(this, _onVertSepRepaint));
|
||||
hbox.add(item);
|
||||
|
||||
// Fill up the second column
|
||||
|
||||
vbox = new St.BoxLayout({vertical: true});
|
||||
hbox.add(vbox);
|
||||
|
||||
// Event list
|
||||
vbox.add(this._eventList.actor);
|
||||
|
||||
item = new PopupMenu.PopupMenuItem(_("Open Calendar"));
|
||||
item.connect('activate', Lang.bind(this, this._onOpenCalendarActivate));
|
||||
vbox.add(item.actor, {y_align: St.Align.END, expand: true, y_fill: false});
|
||||
|
||||
// Whenever the menu is opened, select today
|
||||
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
|
||||
if (isOpen) {
|
||||
let now = new Date();
|
||||
this._calendar.setDate(now);
|
||||
// No need to update this._eventList as ::selected-date-changed
|
||||
// signal will fire
|
||||
}
|
||||
}));
|
||||
|
||||
// Done with hbox for calendar and event list
|
||||
|
||||
// Track changes to clock settings
|
||||
this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
|
||||
this._clockSettings = new Gio.Settings({ schema: 'org.gnome.shell.clock' });
|
||||
this._desktopSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
|
||||
this._clockSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
|
||||
|
||||
// Start the clock
|
||||
this._updateClockAndDate();
|
||||
},
|
||||
|
||||
_updateClockAndDate: function() {
|
||||
let format = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
|
||||
let showDate = this._clockSettings.get_boolean(CLOCK_SHOW_DATE_KEY);
|
||||
let showSeconds = this._clockSettings.get_boolean(CLOCK_SHOW_SECONDS_KEY);
|
||||
|
||||
let clockFormat;
|
||||
let dateFormat;
|
||||
|
||||
switch (format) {
|
||||
case '24h':
|
||||
if (showDate)
|
||||
/* Translators: This is the time format with date used
|
||||
in 24-hour mode. */
|
||||
clockFormat = showSeconds ? _("%a %b %e, %R:%S")
|
||||
: _("%a %b %e, %R");
|
||||
else
|
||||
/* Translators: This is the time format without date used
|
||||
in 24-hour mode. */
|
||||
clockFormat = showSeconds ? _("%a %R:%S")
|
||||
: _("%a %R");
|
||||
break;
|
||||
case '12h':
|
||||
default:
|
||||
if (showDate)
|
||||
/* Translators: This is a time format with date used
|
||||
for AM/PM. */
|
||||
clockFormat = showSeconds ? _("%a %b %e, %l:%M:%S %p")
|
||||
: _("%a %b %e, %l:%M %p");
|
||||
else
|
||||
/* Translators: This is a time format without date used
|
||||
for AM/PM. */
|
||||
clockFormat = showSeconds ? _("%a %l:%M:%S %p")
|
||||
: _("%a %l:%M %p");
|
||||
break;
|
||||
}
|
||||
|
||||
let displayDate = new Date();
|
||||
let msecRemaining;
|
||||
if (showSeconds) {
|
||||
msecRemaining = 1000 - displayDate.getMilliseconds();
|
||||
if (msecRemaining < 50) {
|
||||
displayDate.setSeconds(displayDate.getSeconds() + 1);
|
||||
msecRemaining += 1000;
|
||||
}
|
||||
} else {
|
||||
msecRemaining = 60000 - (1000 * displayDate.getSeconds() +
|
||||
displayDate.getMilliseconds());
|
||||
if (msecRemaining < 500) {
|
||||
displayDate.setMinutes(displayDate.getMinutes() + 1);
|
||||
msecRemaining += 60000;
|
||||
}
|
||||
}
|
||||
|
||||
this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
|
||||
|
||||
/* Translators: This is the date format to use when the calendar popup is
|
||||
* shown - it is shown just below the time in the shell (e.g. "Tue 9:29 AM").
|
||||
*/
|
||||
dateFormat = _("%A %B %e, %Y");
|
||||
this._date.set_text(displayDate.toLocaleFormat(dateFormat));
|
||||
|
||||
Mainloop.timeout_add(msecRemaining, Lang.bind(this, this._updateClockAndDate));
|
||||
return false;
|
||||
},
|
||||
|
||||
_onPreferencesActivate: function() {
|
||||
this.menu.close();
|
||||
Util.spawnDesktop('gnome-datetime-panel');
|
||||
},
|
||||
|
||||
_onOpenCalendarActivate: function() {
|
||||
this.menu.close();
|
||||
// TODO: pass '-c calendar' (to force the calendar at startup)
|
||||
// TODO: pass the selected day
|
||||
Util.spawnDesktop('evolution');
|
||||
},
|
||||
};
|
404
js/ui/dnd.js
@ -1,22 +1,52 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil -*- */
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const St = imports.gi.St;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const Params = imports.misc.params;
|
||||
|
||||
// Time to scale down to maxDragActorSize
|
||||
const SCALE_ANIMATION_TIME = 0.25;
|
||||
// Time to animate to original position on cancel
|
||||
const SNAP_BACK_ANIMATION_TIME = 0.25;
|
||||
// Time to animate to original position on success
|
||||
const REVERT_ANIMATION_TIME = 0.75;
|
||||
|
||||
const DragMotionResult = {
|
||||
NO_DROP: 0,
|
||||
COPY_DROP: 1,
|
||||
MOVE_DROP: 2,
|
||||
CONTINUE: 3
|
||||
};
|
||||
|
||||
const DRAG_CURSOR_MAP = {
|
||||
0: Shell.Cursor.DND_UNSUPPORTED_TARGET,
|
||||
1: Shell.Cursor.DND_COPY,
|
||||
2: Shell.Cursor.DND_MOVE
|
||||
};
|
||||
|
||||
const DragDropResult = {
|
||||
FAILURE: 0,
|
||||
SUCCESS: 1,
|
||||
CONTINUE: 2
|
||||
};
|
||||
|
||||
let eventHandlerActor = null;
|
||||
let currentDraggable = null;
|
||||
let dragMonitors = [];
|
||||
|
||||
function _getEventHandlerActor() {
|
||||
if (!eventHandlerActor) {
|
||||
eventHandlerActor = new Clutter.Rectangle();
|
||||
eventHandlerActor.width = 0;
|
||||
eventHandlerActor.height = 0;
|
||||
global.stage.add_actor(eventHandlerActor);
|
||||
Main.uiGroup.add_actor(eventHandlerActor);
|
||||
// We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen
|
||||
// when you've grabbed the pointer.
|
||||
eventHandlerActor.connect('event',
|
||||
@ -27,31 +57,74 @@ function _getEventHandlerActor() {
|
||||
return eventHandlerActor;
|
||||
}
|
||||
|
||||
function _Draggable(actor, manualMode) {
|
||||
this._init(actor, manualMode);
|
||||
function addDragMonitor(monitor) {
|
||||
dragMonitors.push(monitor);
|
||||
}
|
||||
|
||||
function removeMonitor(monitor) {
|
||||
for (let i = 0; i < dragMonitors.length; i++)
|
||||
if (dragMonitors[i] == monitor) {
|
||||
dragMonitors.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function _Draggable(actor, params) {
|
||||
this._init(actor, params);
|
||||
}
|
||||
|
||||
_Draggable.prototype = {
|
||||
_init : function(actor, manualMode) {
|
||||
_init : function(actor, params) {
|
||||
params = Params.parse(params, { manualMode: false,
|
||||
restoreOnSuccess: false,
|
||||
dragActorMaxSize: undefined,
|
||||
dragActorOpacity: undefined });
|
||||
|
||||
this.actor = actor;
|
||||
if (!manualMode)
|
||||
if (!params.manualMode)
|
||||
this.actor.connect('button-press-event',
|
||||
Lang.bind(this, this._onButtonPress));
|
||||
|
||||
this.actor.connect('destroy', Lang.bind(this, function() {
|
||||
this._actorDestroyed = true;
|
||||
if (this._dragInProgress)
|
||||
this._cancelDrag(global.get_current_time());
|
||||
this.disconnectAll();
|
||||
}));
|
||||
this._onEventId = null;
|
||||
|
||||
this._restoreOnSuccess = params.restoreOnSuccess;
|
||||
this._dragActorMaxSize = params.dragActorMaxSize;
|
||||
this._dragActorOpacity = params.dragActorOpacity;
|
||||
|
||||
this._buttonDown = false; // The mouse button has been pressed and has not yet been released.
|
||||
this._dragInProgress = false; // The drag has been started, and has not been dropped or cancelled yet.
|
||||
this._snapBackInProgress = false; // The drag has been cancelled and the item is in the process of snapping back.
|
||||
this._animationInProgress = false; // The drag is over and the item is in the process of animating to its original position (snapping back or reverting).
|
||||
|
||||
// During the drag, we eat enter/leave events so that actors don't prelight or show
|
||||
// tooltips. But we remember the relevant events (first leave, last enter) so we can
|
||||
// fix up the hover state after the drag ends.
|
||||
this._firstLeaveEvent = null;
|
||||
this._lastEnterEvent = null;
|
||||
|
||||
this._eventsGrabbed = false;
|
||||
},
|
||||
|
||||
_onButtonPress : function (actor, event) {
|
||||
// FIXME: we should make sure it's button 1, but we can't currently
|
||||
// check that from JavaScript
|
||||
if (event.get_button() != 1)
|
||||
return false;
|
||||
|
||||
if (Tweener.getTweenCount(actor))
|
||||
return false;
|
||||
|
||||
this._buttonDown = true;
|
||||
this._grabActor();
|
||||
// special case St.Clickable: grabbing the pointer would mess up the
|
||||
// internal state, so we start the drag manually on hover change
|
||||
if (this.actor instanceof St.Clickable)
|
||||
this.actor.connect('notify::hover',
|
||||
Lang.bind(this, this._onClickableHoverChanged));
|
||||
else
|
||||
this._grabActor();
|
||||
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
this._dragStartX = stageX;
|
||||
@ -59,7 +132,16 @@ _Draggable.prototype = {
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
_onClickableHoverChanged: function(button) {
|
||||
if (button.hover || !button.held)
|
||||
return;
|
||||
|
||||
button.fake_release();
|
||||
this.startDrag(this._dragStartX, this._dragStartY,
|
||||
global.get_current_time());
|
||||
},
|
||||
|
||||
_grabActor: function() {
|
||||
Clutter.grab_pointer(this.actor);
|
||||
this._onEventId = this.actor.connect('event',
|
||||
@ -73,13 +155,19 @@ _Draggable.prototype = {
|
||||
},
|
||||
|
||||
_grabEvents: function() {
|
||||
Clutter.grab_pointer(_getEventHandlerActor());
|
||||
Clutter.grab_keyboard(_getEventHandlerActor());
|
||||
if (!this._eventsGrabbed) {
|
||||
Clutter.grab_pointer(_getEventHandlerActor());
|
||||
Clutter.grab_keyboard(_getEventHandlerActor());
|
||||
this._eventsGrabbed = true;
|
||||
}
|
||||
},
|
||||
|
||||
_ungrabEvents: function() {
|
||||
Clutter.ungrab_pointer();
|
||||
Clutter.ungrab_keyboard();
|
||||
if (this._eventsGrabbed) {
|
||||
Clutter.ungrab_pointer();
|
||||
Clutter.ungrab_keyboard();
|
||||
this._eventsGrabbed = false;
|
||||
}
|
||||
},
|
||||
|
||||
_onEvent: function(actor, event) {
|
||||
@ -91,7 +179,7 @@ _Draggable.prototype = {
|
||||
this._buttonDown = false;
|
||||
if (this._dragInProgress) {
|
||||
return this._dragActorDropped(event);
|
||||
} else if (this._dragActor != null && !this._snapBackInProgress) {
|
||||
} else if (this._dragActor != null && !this._animationInProgress) {
|
||||
// Drag must have been cancelled with Esc.
|
||||
this._dragComplete();
|
||||
return true;
|
||||
@ -116,6 +204,11 @@ _Draggable.prototype = {
|
||||
this._cancelDrag(event.get_time());
|
||||
return true;
|
||||
}
|
||||
} else if (event.type() == Clutter.EventType.LEAVE) {
|
||||
if (this._firstLeaveEvent == null)
|
||||
this._firstLeaveEvent = event;
|
||||
} else if (event.type() == Clutter.EventType.ENTER) {
|
||||
this._lastEnterEvent = event;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -139,9 +232,10 @@ _Draggable.prototype = {
|
||||
if (this._onEventId)
|
||||
this._ungrabActor();
|
||||
this._grabEvents();
|
||||
global.set_cursor(Shell.Cursor.DND_IN_DRAG);
|
||||
|
||||
this._dragStartX = stageX;
|
||||
this._dragStartY = stageY;
|
||||
this._dragX = this._dragStartX = stageX;
|
||||
this._dragY = this._dragStartY = stageY;
|
||||
|
||||
if (this.actor._delegate && this.actor._delegate.getDragActor) {
|
||||
this._dragActor = this.actor._delegate.getDragActor(this._dragStartX, this._dragStartY);
|
||||
@ -154,10 +248,9 @@ _Draggable.prototype = {
|
||||
// the dragActor over it. Otherwise, center it
|
||||
// around the pointer
|
||||
let [sourceX, sourceY] = this._dragActorSource.get_transformed_position();
|
||||
let [sourceWidth, sourceHeight] = this._dragActorSource.get_transformed_size();
|
||||
let x, y;
|
||||
if (stageX > sourceX && stageX <= sourceX + sourceWidth &&
|
||||
stageY > sourceY && stageY <= sourceY + sourceHeight) {
|
||||
if (stageX > sourceX && stageX <= sourceX + this._dragActor.width &&
|
||||
stageY > sourceY && stageY <= sourceY + this._dragActor.height) {
|
||||
x = sourceX;
|
||||
y = sourceY;
|
||||
} else {
|
||||
@ -193,6 +286,46 @@ _Draggable.prototype = {
|
||||
|
||||
this._dragActor.reparent(this.actor.get_stage());
|
||||
this._dragActor.raise_top();
|
||||
Shell.util_set_hidden_from_pick(this._dragActor, true);
|
||||
|
||||
this._dragOrigOpacity = this._dragActor.opacity;
|
||||
if (this._dragActorOpacity != undefined)
|
||||
this._dragActor.opacity = this._dragActorOpacity;
|
||||
|
||||
this._snapBackX = this._dragStartX + this._dragOffsetX;
|
||||
this._snapBackY = this._dragStartY + this._dragOffsetY;
|
||||
this._snapBackScale = this._dragActor.scale_x;
|
||||
|
||||
if (this._dragActorMaxSize != undefined) {
|
||||
let [scaledWidth, scaledHeight] = this._dragActor.get_transformed_size();
|
||||
let currentSize = Math.max(scaledWidth, scaledHeight);
|
||||
if (currentSize > this._dragActorMaxSize) {
|
||||
let scale = this._dragActorMaxSize / currentSize;
|
||||
let origScale = this._dragActor.scale_x;
|
||||
let origDragOffsetX = this._dragOffsetX;
|
||||
let origDragOffsetY = this._dragOffsetY;
|
||||
|
||||
// The position of the actor changes as we scale
|
||||
// around the drag position, but we can't just tween
|
||||
// to the final position because that tween would
|
||||
// fight with updates as the user continues dragging
|
||||
// the mouse; instead we do the position computations in
|
||||
// an onUpdate() function.
|
||||
Tweener.addTween(this._dragActor,
|
||||
{ scale_x: scale * origScale,
|
||||
scale_y: scale * origScale,
|
||||
time: SCALE_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onUpdate: function() {
|
||||
let currentScale = this._dragActor.scale_x / origScale;
|
||||
this._dragOffsetX = currentScale * origDragOffsetX;
|
||||
this._dragOffsetY = currentScale * origDragOffsetY;
|
||||
this._dragActor.set_position(this._dragX + this._dragOffsetX,
|
||||
this._dragY + this._dragOffsetY);
|
||||
},
|
||||
onUpdateScope: this });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_maybeStartDrag: function(event) {
|
||||
@ -211,57 +344,108 @@ _Draggable.prototype = {
|
||||
|
||||
_updateDragPosition : function (event) {
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
this._dragX = stageX;
|
||||
this._dragY = stageY;
|
||||
|
||||
// If we are dragging, update the position
|
||||
if (this._dragActor) {
|
||||
this._dragActor.set_position(stageX + this._dragOffsetX,
|
||||
stageY + this._dragOffsetY);
|
||||
// Because we want to find out what other actor is located at the current position of this._dragActor,
|
||||
// we have to temporarily hide this._dragActor.
|
||||
this._dragActor.hide();
|
||||
|
||||
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
||||
stageX + this._dragOffsetX,
|
||||
stageY + this._dragOffsetY);
|
||||
this._dragActor.show();
|
||||
stageX, stageY);
|
||||
|
||||
// We call observers only once per motion with the innermost
|
||||
// target actor. If necessary, the observer can walk the
|
||||
// parent itself.
|
||||
let dragEvent = {
|
||||
x: stageX,
|
||||
y: stageY,
|
||||
dragActor: this._dragActor,
|
||||
source: this.actor._delegate,
|
||||
targetActor: target
|
||||
};
|
||||
for (let i = 0; i < dragMonitors.length; i++) {
|
||||
let motionFunc = dragMonitors[i].dragMotion;
|
||||
if (motionFunc) {
|
||||
let result = motionFunc(dragEvent);
|
||||
if (result != DragMotionResult.CONTINUE) {
|
||||
global.set_cursor(DRAG_CURSOR_MAP[result]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
while (target) {
|
||||
if (target._delegate && target._delegate.handleDragOver) {
|
||||
let [targX, targY] = target.get_transformed_position();
|
||||
let [r, targX, targY] = target.transform_stage_point(stageX, stageY);
|
||||
// We currently loop through all parents on drag-over even if one of the children has handled it.
|
||||
// We can check the return value of the function and break the loop if it's true if we don't want
|
||||
// to continue checking the parents.
|
||||
target._delegate.handleDragOver(this.actor._delegate, this._dragActor,
|
||||
(stageX + this._dragOffsetX - targX) / target.scale_x,
|
||||
(stageY + this._dragOffsetY - targY) / target.scale_y,
|
||||
event.get_time());
|
||||
let result = target._delegate.handleDragOver(this.actor._delegate,
|
||||
this._dragActor,
|
||||
targX,
|
||||
targY,
|
||||
event.get_time());
|
||||
if (result != DragMotionResult.CONTINUE) {
|
||||
global.set_cursor(DRAG_CURSOR_MAP[result]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
target = target.get_parent();
|
||||
}
|
||||
global.set_cursor(Shell.Cursor.DND_IN_DRAG);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_dragActorDropped: function(event) {
|
||||
// Find a drop target. Because we want to find out what other actor is located at
|
||||
// the current position of this._dragActor, we have to temporarily hide this._dragActor.
|
||||
this._dragActor.hide();
|
||||
let [dropX, dropY] = event.get_coords();
|
||||
let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
|
||||
dropX, dropY);
|
||||
this._dragActor.show();
|
||||
|
||||
// We call observers only once per motion with the innermost
|
||||
// target actor. If necessary, the observer can walk the
|
||||
// parent itself.
|
||||
let dropEvent = {
|
||||
dropActor: this._dragActor,
|
||||
targetActor: target,
|
||||
clutterEvent: event
|
||||
};
|
||||
for (let i = 0; i < dragMonitors.length; i++) {
|
||||
let dropFunc = dragMonitors[i].dragDrop;
|
||||
if (dropFunc)
|
||||
switch (dropFunc(dropEvent)) {
|
||||
case DragDropResult.FAILURE:
|
||||
case DragDropResult.SUCCESS:
|
||||
return true;
|
||||
case DragDropResult.CONTINUE:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
while (target) {
|
||||
if (target._delegate && target._delegate.acceptDrop) {
|
||||
let [targX, targY] = target.get_transformed_position();
|
||||
if (target._delegate.acceptDrop(this.actor._delegate, this._dragActor,
|
||||
(dropX - targX) / target.scale_x,
|
||||
(dropY - targY) / target.scale_y,
|
||||
let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
|
||||
if (target._delegate.acceptDrop(this.actor._delegate,
|
||||
this._dragActor,
|
||||
targX,
|
||||
targY,
|
||||
event.get_time())) {
|
||||
if (this._actorDestroyed)
|
||||
return true;
|
||||
// If it accepted the drop without taking the actor,
|
||||
// destroy it.
|
||||
if (this._dragActor.get_parent() == this._dragActor.get_stage())
|
||||
this._dragActor.destroy();
|
||||
// handle it ourselves.
|
||||
if (this._dragActor.get_parent() == this._dragActor.get_stage()) {
|
||||
if (this._restoreOnSuccess) {
|
||||
this._restoreDragActor(event.get_time());
|
||||
return true;
|
||||
} else
|
||||
this._dragActor.destroy();
|
||||
}
|
||||
|
||||
this._dragInProgress = false;
|
||||
global.unset_cursor();
|
||||
this.emit('drag-end', event.get_time(), true);
|
||||
this._dragComplete();
|
||||
return true;
|
||||
@ -275,31 +459,85 @@ _Draggable.prototype = {
|
||||
return true;
|
||||
},
|
||||
|
||||
_cancelDrag: function(eventTime) {
|
||||
this._dragInProgress = false;
|
||||
// Snap back to the actor source if the source is still around, snap back
|
||||
// to the original location if the actor itself was being dragged or the
|
||||
// source is no longer around.
|
||||
let snapBackX = this._dragStartX + this._dragOffsetX;
|
||||
let snapBackY = this._dragStartY + this._dragOffsetY;
|
||||
_getRestoreLocation: function() {
|
||||
let x, y, scale;
|
||||
|
||||
if (this._dragActorSource && this._dragActorSource.visible) {
|
||||
[snapBackX, snapBackY] = this._dragActorSource.get_transformed_position();
|
||||
// Snap the clone back to its source
|
||||
[x, y] = this._dragActorSource.get_transformed_position();
|
||||
let [sourceScaledWidth, sourceScaledHeight] = this._dragActorSource.get_transformed_size();
|
||||
scale = this._dragActor.width / sourceScaledWidth;
|
||||
} else if (this._dragOrigParent) {
|
||||
// Snap the actor back to its original position within
|
||||
// its parent, adjusting for the fact that the parent
|
||||
// may have been moved or scaled
|
||||
let [parentX, parentY] = this._dragOrigParent.get_transformed_position();
|
||||
x = parentX + this._dragOrigParent.scale_x * this._dragOrigX;
|
||||
y = parentY + this._dragOrigParent.scale_y * this._dragOrigY;
|
||||
|
||||
let [parentWidth, parentHeight] = this._dragOrigParent.get_size();
|
||||
let [parentScaledWidth, parentScaledHeight] = this._dragOrigParent.get_transformed_size();
|
||||
let parentScale = parentScaledWidth / parentWidth;
|
||||
scale = this._dragOrigScale * parentScale;
|
||||
} else {
|
||||
// Snap back actor to its original stage position
|
||||
x = this._snapBackX;
|
||||
y = this._snapBackY;
|
||||
scale = this._snapBackScale;
|
||||
}
|
||||
|
||||
this._snapBackInProgress = true;
|
||||
return [x, y, scale];
|
||||
},
|
||||
|
||||
_cancelDrag: function(eventTime) {
|
||||
this._dragInProgress = false;
|
||||
let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();
|
||||
|
||||
if (this._actorDestroyed) {
|
||||
global.unset_cursor();
|
||||
if (!this._buttonDown)
|
||||
this._dragComplete();
|
||||
this.emit('drag-end', eventTime, false);
|
||||
return;
|
||||
}
|
||||
|
||||
this._animationInProgress = true;
|
||||
// No target, so snap back
|
||||
Tweener.addTween(this._dragActor,
|
||||
{ x: snapBackX,
|
||||
y: snapBackY,
|
||||
scale_x: snapBackScale,
|
||||
scale_y: snapBackScale,
|
||||
opacity: this._dragOrigOpacity,
|
||||
time: SNAP_BACK_ANIMATION_TIME,
|
||||
transition: "easeOutQuad",
|
||||
onComplete: this._onSnapBackComplete,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: this._onAnimationComplete,
|
||||
onCompleteScope: this,
|
||||
onCompleteParams: [this._dragActor, eventTime]
|
||||
});
|
||||
},
|
||||
|
||||
_onSnapBackComplete : function (dragActor, eventTime) {
|
||||
_restoreDragActor: function(eventTime) {
|
||||
this._dragInProgress = false;
|
||||
[restoreX, restoreY, restoreScale] = this._getRestoreLocation();
|
||||
|
||||
// fade the actor back in at its original location
|
||||
this._dragActor.set_position(restoreX, restoreY);
|
||||
this._dragActor.set_scale(restoreScale, restoreScale);
|
||||
this._dragActor.opacity = 0;
|
||||
|
||||
this._animationInProgress = true;
|
||||
Tweener.addTween(this._dragActor,
|
||||
{ opacity: this._dragOrigOpacity,
|
||||
time: REVERT_ANIMATION_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: this._onAnimationComplete,
|
||||
onCompleteScope: this,
|
||||
onCompleteParams: [this._dragActor, eventTime]
|
||||
});
|
||||
},
|
||||
|
||||
_onAnimationComplete : function (dragActor, eventTime) {
|
||||
if (this._dragOrigParent) {
|
||||
dragActor.reparent(this._dragOrigParent);
|
||||
dragActor.set_scale(this._dragOrigScale, this._dragOrigScale);
|
||||
@ -307,17 +545,49 @@ _Draggable.prototype = {
|
||||
} else {
|
||||
dragActor.destroy();
|
||||
}
|
||||
global.unset_cursor();
|
||||
this.emit('drag-end', eventTime, false);
|
||||
|
||||
this._snapBackInProgress = false;
|
||||
this._animationInProgress = false;
|
||||
if (!this._buttonDown)
|
||||
this._dragComplete();
|
||||
},
|
||||
|
||||
// Actor is an actor we might have entered or left during the drag; call
|
||||
// st_widget_sync_hover on all StWidget ancestors
|
||||
_syncHover: function(actor) {
|
||||
// If the actor was reparented from its original location and
|
||||
// destroyed, then start syncing hover at the original parent
|
||||
if (actor == this._dragActor && this._actorDestroyed)
|
||||
actor = this._dragOrigParent;
|
||||
|
||||
while (actor) {
|
||||
let parent = actor.get_parent();
|
||||
if (actor instanceof St.Widget)
|
||||
actor.sync_hover();
|
||||
|
||||
actor = parent;
|
||||
}
|
||||
},
|
||||
|
||||
_dragComplete: function() {
|
||||
if (!this._actorDestroyed)
|
||||
Shell.util_set_hidden_from_pick(this._dragActor, false);
|
||||
|
||||
this._ungrabEvents();
|
||||
|
||||
if (this._firstLeaveEvent) {
|
||||
this._syncHover(this._firstLeaveEvent.get_source());
|
||||
this._firstLeaveEvent = null;
|
||||
}
|
||||
|
||||
if (this._lastEnterEvent) {
|
||||
this._syncHover(this._lastEnterEvent.get_source());
|
||||
this._lastEnterEvent = null;
|
||||
}
|
||||
|
||||
this._dragActor = undefined;
|
||||
currentDraggable = null;
|
||||
this._ungrabEvents();
|
||||
}
|
||||
};
|
||||
|
||||
@ -326,10 +596,24 @@ Signals.addSignalMethods(_Draggable.prototype);
|
||||
/**
|
||||
* makeDraggable:
|
||||
* @actor: Source actor
|
||||
* @manualMode: If given, do not automatically start drag and drop on click
|
||||
* @params: (optional) Additional parameters
|
||||
*
|
||||
* Create an object which controls drag and drop for the given actor.
|
||||
*
|
||||
* If %manualMode is %true in @params, do not automatically start
|
||||
* drag and drop on click
|
||||
*
|
||||
* If %dragActorMaxSize is present in @params, the drag actor will
|
||||
* be scaled down to be no larger than that size in pixels.
|
||||
*
|
||||
* If %dragActorOpacity is present in @params, the drag actor will
|
||||
* will be set to have that opacity during the drag.
|
||||
*
|
||||
* Note that when the drag actor is the source actor and the drop
|
||||
* succeeds, the actor scale and opacity aren't reset; if the drop
|
||||
* target wants to reuse the actor, it's up to the drop target to
|
||||
* reset these values.
|
||||
*/
|
||||
function makeDraggable(actor, manualMode) {
|
||||
return new _Draggable(actor, manualMode);
|
||||
function makeDraggable(actor, params) {
|
||||
return new _Draggable(actor, params);
|
||||
}
|
||||
|
@ -1,481 +1,12 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const DocInfo = imports.misc.docInfo;
|
||||
const DND = imports.ui.dnd;
|
||||
const GenericDisplay = imports.ui.genericDisplay;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const Search = imports.ui.search;
|
||||
|
||||
const MAX_DASH_DOCS = 50;
|
||||
const DASH_DOCS_ICON_SIZE = 16;
|
||||
|
||||
const DEFAULT_SPACING = 4;
|
||||
|
||||
/* This class represents a single display item containing information about a document.
|
||||
* We take the current number of seconds in the constructor to avoid looking up the current
|
||||
* time for every item when they are created in a batch.
|
||||
*
|
||||
* docInfo - DocInfo object containing information about the document
|
||||
* currentSeconds - current number of seconds since the epoch
|
||||
*/
|
||||
function DocDisplayItem(docInfo, currentSecs) {
|
||||
this._init(docInfo, currentSecs);
|
||||
}
|
||||
|
||||
DocDisplayItem.prototype = {
|
||||
__proto__: GenericDisplay.GenericDisplayItem.prototype,
|
||||
|
||||
_init : function(docInfo, currentSecs) {
|
||||
GenericDisplay.GenericDisplayItem.prototype._init.call(this);
|
||||
this._docInfo = docInfo;
|
||||
|
||||
this._setItemInfo(docInfo.name, "");
|
||||
|
||||
this._timeoutTime = -1;
|
||||
this._resetTimeDisplay(currentSecs);
|
||||
},
|
||||
|
||||
//// Public methods ////
|
||||
|
||||
getUpdateTimeoutTime: function() {
|
||||
return this._timeoutTime;
|
||||
},
|
||||
|
||||
// Update any relative-time based displays for this item.
|
||||
redisplay: function(currentSecs) {
|
||||
this._resetTimeDisplay(currentSecs);
|
||||
},
|
||||
|
||||
//// Public method overrides ////
|
||||
|
||||
// Opens a document represented by this display item.
|
||||
launch : function() {
|
||||
this._docInfo.launch();
|
||||
},
|
||||
|
||||
//// Protected method overrides ////
|
||||
|
||||
// Returns an icon for the item.
|
||||
_createIcon : function() {
|
||||
return this._docInfo.createIcon(GenericDisplay.ITEM_DISPLAY_ICON_SIZE);
|
||||
},
|
||||
|
||||
// Returns a preview icon for the item.
|
||||
_createPreviewIcon : function() {
|
||||
return this._docInfo.createIcon(GenericDisplay.PREVIEW_ICON_SIZE);
|
||||
},
|
||||
|
||||
// Creates and returns a large preview icon, but only if this._docInfo is an image file
|
||||
// and we were able to generate a pixbuf from it successfully.
|
||||
_createLargePreviewIcon : function() {
|
||||
if (this._docInfo.mimeType == null || this._docInfo.mimeType.indexOf("image/") != 0)
|
||||
return null;
|
||||
|
||||
try {
|
||||
return Shell.TextureCache.get_default().load_uri_sync(Shell.TextureCachePolicy.NONE,
|
||||
this._docInfo.uri, -1, -1);
|
||||
} catch (e) {
|
||||
// An exception will be raised when the image format isn't know
|
||||
/* FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=591480: should
|
||||
* only ignore GDK_PIXBUF_ERROR_UNKNOWN_TYPE. */
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
//// Drag and Drop ////
|
||||
|
||||
shellWorkspaceLaunch: function() {
|
||||
this.launch();
|
||||
},
|
||||
|
||||
//// Private Methods ////
|
||||
|
||||
// Updates the last visited time displayed in the description text for the item.
|
||||
_resetTimeDisplay: function(currentSecs) {
|
||||
let lastSecs = this._docInfo.timestamp;
|
||||
let timeDelta = currentSecs - lastSecs;
|
||||
let [text, nextUpdate] = global.format_time_relative_pretty(timeDelta);
|
||||
this._timeoutTime = currentSecs + nextUpdate;
|
||||
this._setDescriptionText(text);
|
||||
}
|
||||
};
|
||||
|
||||
/* This class represents a display containing a collection of document items.
|
||||
* The documents are sorted by how recently they were last visited.
|
||||
*/
|
||||
function DocDisplay(flags) {
|
||||
this._init(flags);
|
||||
}
|
||||
|
||||
DocDisplay.prototype = {
|
||||
__proto__: GenericDisplay.GenericDisplay.prototype,
|
||||
|
||||
_init : function(flags) {
|
||||
GenericDisplay.GenericDisplay.prototype._init.call(this, flags);
|
||||
// We keep a single timeout callback for updating last visited times
|
||||
// for all the items in the display. This avoids creating individual
|
||||
// callbacks for each item in the display. So proper time updates
|
||||
// for individual items and item details depend on the item being
|
||||
// associated with one of the displays.
|
||||
this._updateTimeoutTargetTime = -1;
|
||||
this._updateTimeoutId = 0;
|
||||
|
||||
this._docManager = DocInfo.getDocManager();
|
||||
this._docsStale = true;
|
||||
this._docManager.connect('changed', Lang.bind(this, function(mgr, userData) {
|
||||
this._docsStale = true;
|
||||
// Changes in local recent files should not happen when we are in the Overview mode,
|
||||
// but redisplaying right away is cool when we use Zephyr.
|
||||
// Also, we might be displaying remote documents, like Google Docs, in the future
|
||||
// which might be edited by someone else.
|
||||
this._redisplay(GenericDisplay.RedisplayFlags.NONE);
|
||||
}));
|
||||
|
||||
this.connect('destroy', Lang.bind(this, function (o) {
|
||||
if (this._updateTimeoutId > 0)
|
||||
Mainloop.source_remove(this._updateTimeoutId);
|
||||
}));
|
||||
},
|
||||
|
||||
//// Protected method overrides ////
|
||||
|
||||
// Gets the list of recent items from the recent items manager.
|
||||
_refreshCache : function() {
|
||||
if (!this._docsStale)
|
||||
return true;
|
||||
this._allItems = {};
|
||||
Lang.copyProperties(this._docManager.getInfosByUri(), this._allItems);
|
||||
this._docsStale = false;
|
||||
return false;
|
||||
},
|
||||
|
||||
// Sets the list of the displayed items based on how recently they were last visited.
|
||||
_setDefaultList : function() {
|
||||
// It seems to be an implementation detail of the Mozilla JavaScript that object
|
||||
// properties are returned during the iteration in the same order in which they were
|
||||
// defined, but it is not a guarantee according to this
|
||||
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Statements/for...in
|
||||
// While this._allItems associative array seems to always be ordered by last added,
|
||||
// as the results of this._recentManager.get_items() based on which it is constructed are,
|
||||
// we should do the sorting manually because we want the order to be based on last visited.
|
||||
//
|
||||
// This function is called each time the search string is set back to '' or we display
|
||||
// the Overview, so we are doing the sorting over the same items multiple times if the list
|
||||
// of recent items didn't change. We could store an additional array of doc ids and sort
|
||||
// them once when they are returned by this._recentManager.get_items() to avoid having to do
|
||||
// this sorting each time, but the sorting seems to be very fast anyway, so there is no need
|
||||
// to introduce an additional class variable.
|
||||
this._matchedItems = {};
|
||||
this._matchedItemKeys = [];
|
||||
let docIdsToRemove = [];
|
||||
for (docId in this._allItems) {
|
||||
this._matchedItems[docId] = 1;
|
||||
this._matchedItemKeys.push(docId);
|
||||
}
|
||||
|
||||
for (docId in docIdsToRemove) {
|
||||
delete this._allItems[docId];
|
||||
}
|
||||
|
||||
this._matchedItemKeys.sort(Lang.bind(this, this._compareItems));
|
||||
},
|
||||
|
||||
// Compares items associated with the item ids based on how recently the items
|
||||
// were last visited.
|
||||
// Returns an integer value indicating the result of the comparison.
|
||||
_compareItems : function(itemIdA, itemIdB) {
|
||||
let docA = this._allItems[itemIdA];
|
||||
let docB = this._allItems[itemIdB];
|
||||
|
||||
return docB.timestamp - docA.timestamp;
|
||||
},
|
||||
|
||||
// Checks if the item info can be a match for the search string by checking
|
||||
// the name of the document. Item info is expected to be GtkRecentInfo.
|
||||
// Returns a boolean flag indicating if itemInfo is a match.
|
||||
_isInfoMatching : function(itemInfo, search) {
|
||||
if (!itemInfo.exists())
|
||||
return false;
|
||||
|
||||
if (search == null || search == '')
|
||||
return true;
|
||||
|
||||
let name = itemInfo.name.toLowerCase();
|
||||
if (name.indexOf(search) >= 0)
|
||||
return true;
|
||||
// TODO: we can also check doc URIs, so that
|
||||
// if you search for a directory name, we display recent files from it
|
||||
return false;
|
||||
},
|
||||
|
||||
// Creates a DocDisplayItem based on itemInfo, which is expected to be a DocInfo object.
|
||||
_createDisplayItem: function(itemInfo) {
|
||||
let currentSecs = new Date().getTime() / 1000;
|
||||
let docDisplayItem = new DocDisplayItem(itemInfo, currentSecs);
|
||||
this._updateTimeoutCallback(docDisplayItem, currentSecs);
|
||||
return docDisplayItem;
|
||||
},
|
||||
|
||||
//// Private Methods ////
|
||||
|
||||
// A callback function that redisplays the items, updating their descriptions,
|
||||
// and sets up a new timeout callback.
|
||||
_docTimeout: function () {
|
||||
let currentSecs = new Date().getTime() / 1000;
|
||||
this._updateTimeoutId = 0;
|
||||
this._updateTimeoutTargetTime = -1;
|
||||
for (let docId in this._displayedItems) {
|
||||
let docDisplayItem = this._displayedItems[docId];
|
||||
docDisplayItem.redisplay(currentSecs);
|
||||
this._updateTimeoutCallback(docDisplayItem, currentSecs);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// Updates the timeout callback if the timeout time for the docDisplayItem
|
||||
// is earlier than the target time for the current timeout callback.
|
||||
_updateTimeoutCallback: function (docDisplayItem, currentSecs) {
|
||||
let timeoutTime = docDisplayItem.getUpdateTimeoutTime();
|
||||
if (this._updateTimeoutTargetTime < 0 || timeoutTime < this._updateTimeoutTargetTime) {
|
||||
if (this._updateTimeoutId > 0)
|
||||
Mainloop.source_remove(this._updateTimeoutId);
|
||||
this._updateTimeoutId = Mainloop.timeout_add_seconds(timeoutTime - currentSecs, Lang.bind(this, this._docTimeout));
|
||||
this._updateTimeoutTargetTime = timeoutTime;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(DocDisplay.prototype);
|
||||
|
||||
function DashDocDisplayItem(docInfo) {
|
||||
this._init(docInfo);
|
||||
}
|
||||
|
||||
DashDocDisplayItem.prototype = {
|
||||
_init: function(docInfo) {
|
||||
this._info = docInfo;
|
||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: DEFAULT_SPACING,
|
||||
reactive: true });
|
||||
this.actor.connect('button-release-event', Lang.bind(this, function () {
|
||||
docInfo.launch();
|
||||
Main.overview.hide();
|
||||
}));
|
||||
|
||||
this.actor._delegate = this;
|
||||
|
||||
this._icon = docInfo.createIcon(DASH_DOCS_ICON_SIZE);
|
||||
let iconBox = new Big.Box({ y_align: Big.BoxAlignment.CENTER });
|
||||
iconBox.append(this._icon, Big.BoxPackFlags.NONE);
|
||||
this.actor.append(iconBox, Big.BoxPackFlags.NONE);
|
||||
let name = new St.Label({ style_class: 'dash-recent-docs-item',
|
||||
text: docInfo.name });
|
||||
this.actor.append(name, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
let draggable = DND.makeDraggable(this.actor);
|
||||
},
|
||||
|
||||
getUri: function() {
|
||||
return this._info.uri;
|
||||
},
|
||||
|
||||
getDragActorSource: function() {
|
||||
return this._icon;
|
||||
},
|
||||
|
||||
getDragActor: function(stageX, stageY) {
|
||||
this.dragActor = this._info.createIcon(DASH_DOCS_ICON_SIZE);
|
||||
return this.dragActor;
|
||||
},
|
||||
|
||||
//// Drag and drop functions ////
|
||||
|
||||
shellWorkspaceLaunch: function () {
|
||||
this._info.launch();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Class used to display two column recent documents in the dash
|
||||
*/
|
||||
function DashDocDisplay() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
DashDocDisplay.prototype = {
|
||||
_init: function() {
|
||||
this.actor = new Shell.GenericContainer();
|
||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||
this._workId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay));
|
||||
|
||||
this._actorsByUri = {};
|
||||
|
||||
this._docManager = DocInfo.getDocManager();
|
||||
this._docManager.connect('changed', Lang.bind(this, this._onDocsChanged));
|
||||
this._pendingDocsChange = true;
|
||||
this._checkDocExistence = false;
|
||||
},
|
||||
|
||||
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||
let children = actor.get_children();
|
||||
|
||||
// We use two columns maximum. Just take the min and natural size of the
|
||||
// first two items, even though strictly speaking it's not correct; we'd
|
||||
// need to calculate how many items we could fit for the height, then
|
||||
// take the biggest preferred width for each column.
|
||||
// In practice the dash gets a fixed width anyways.
|
||||
|
||||
// If we have one child, add its minimum and natural size
|
||||
if (children.length > 0) {
|
||||
let [minSize, naturalSize] = children[0].get_preferred_width(forHeight);
|
||||
alloc.min_size += minSize;
|
||||
alloc.natural_size += naturalSize;
|
||||
}
|
||||
// If we have two, add its size, plus DEFAULT_SPACING
|
||||
if (children.length > 1) {
|
||||
let [minSize, naturalSize] = children[1].get_preferred_width(forHeight);
|
||||
alloc.min_size += DEFAULT_SPACING + minSize;
|
||||
alloc.natural_size += DEFAULT_SPACING + naturalSize;
|
||||
}
|
||||
},
|
||||
|
||||
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||
let children = actor.get_children();
|
||||
|
||||
// Two columns, where we go vertically down first. So just take
|
||||
// the height of half of the children as our preferred height.
|
||||
|
||||
let firstColumnChildren = Math.ceil(children.length / 2);
|
||||
|
||||
let natural = 0;
|
||||
for (let i = 0; i < firstColumnChildren; i++) {
|
||||
let child = children[i];
|
||||
let [minSize, naturalSize] = child.get_preferred_height(-1);
|
||||
natural += naturalSize;
|
||||
|
||||
if (i > 0 && i < children.length - 1) {
|
||||
natural += DEFAULT_SPACING;
|
||||
}
|
||||
}
|
||||
alloc.natural_size = natural;
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
let width = box.x2 - box.x1;
|
||||
let height = box.y2 - box.y1;
|
||||
|
||||
let children = actor.get_children();
|
||||
|
||||
// The width of an item is our allocated width, minus spacing, divided in half.
|
||||
let itemWidth = Math.floor((width - DEFAULT_SPACING) / 2);
|
||||
let x = box.x1;
|
||||
let y = box.y1;
|
||||
let columnIndex = 0;
|
||||
let i = 0;
|
||||
// Loop over the children, going vertically down first. When we run
|
||||
// out of vertical space (our y variable is bigger than box.y2), switch
|
||||
// to the second column.
|
||||
while (i < children.length) {
|
||||
let child = children[i];
|
||||
|
||||
let [minSize, naturalSize] = child.get_preferred_height(-1);
|
||||
|
||||
if (y + naturalSize > box.y2) {
|
||||
// Is this the second column, or we're in
|
||||
// the first column and can't even fit one
|
||||
// item? In that case, break.
|
||||
if (columnIndex == 1 || i == 0) {
|
||||
break;
|
||||
}
|
||||
// Set x to the halfway point.
|
||||
columnIndex += 1;
|
||||
x = x + itemWidth + DEFAULT_SPACING;
|
||||
// And y is back to the top.
|
||||
y = box.y1;
|
||||
// Retry this same item, now that we're in the second column.
|
||||
// By looping back to the top here, we re-test the size
|
||||
// again for the second column.
|
||||
continue;
|
||||
}
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
childBox.x1 = x;
|
||||
childBox.y1 = y;
|
||||
childBox.x2 = childBox.x1 + itemWidth;
|
||||
childBox.y2 = y + naturalSize;
|
||||
|
||||
y = childBox.y2 + DEFAULT_SPACING;
|
||||
|
||||
child.show();
|
||||
child.allocate(childBox, flags);
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
if (this._checkDocExistence) {
|
||||
// Now we know how many docs we are displaying, queue a check to see if any of them
|
||||
// have been deleted. If they are deleted, then we'll get a 'changed' signal; since
|
||||
// we'll now be displaying items we weren't previously, we'll check again to see
|
||||
// if they were deleted, and so forth and so on.
|
||||
// TODO: We should change this to ask for as many as we can fit in the given space:
|
||||
// https://bugzilla.gnome.org/show_bug.cgi?id=603522#c23
|
||||
this._docManager.queueExistenceCheck(i);
|
||||
this._checkDocExistence = false;
|
||||
}
|
||||
|
||||
let skipPaint = [];
|
||||
for (; i < children.length; i++)
|
||||
this.actor.set_skip_paint(children[i], true);
|
||||
},
|
||||
|
||||
_onDocsChanged: function() {
|
||||
this._checkDocExistence = true;
|
||||
Main.queueDeferredWork(this._workId);
|
||||
},
|
||||
|
||||
_redisplay: function() {
|
||||
// Should be kept alive by the _actorsByUri
|
||||
this.actor.remove_all();
|
||||
let docs = this._docManager.getTimestampOrderedInfos();
|
||||
for (let i = 0; i < docs.length && i < MAX_DASH_DOCS; i++) {
|
||||
let doc = docs[i];
|
||||
let display = this._actorsByUri[doc.uri];
|
||||
if (display) {
|
||||
this.actor.add_actor(display.actor);
|
||||
} else {
|
||||
let display = new DashDocDisplayItem(doc);
|
||||
this.actor.add_actor(display.actor);
|
||||
this._actorsByUri[doc.uri] = display;
|
||||
}
|
||||
}
|
||||
// Any unparented actors must have been deleted
|
||||
for (let uri in this._actorsByUri) {
|
||||
let display = this._actorsByUri[uri];
|
||||
if (display.actor.get_parent() == null) {
|
||||
display.actor.destroy();
|
||||
delete this._actorsByUri[uri];
|
||||
}
|
||||
}
|
||||
this.emit('changed');
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(DashDocDisplay.prototype);
|
||||
|
||||
function DocSearchProvider() {
|
||||
this._init();
|
||||
@ -485,7 +16,7 @@ DocSearchProvider.prototype = {
|
||||
__proto__: Search.SearchProvider.prototype,
|
||||
|
||||
_init: function(name) {
|
||||
Search.SearchProvider.prototype._init.call(this, _("DOCUMENTS"));
|
||||
Search.SearchProvider.prototype._init.call(this, _("RECENT ITEMS"));
|
||||
this._docManager = DocInfo.getDocManager();
|
||||
},
|
||||
|
||||
@ -498,9 +29,12 @@ DocSearchProvider.prototype = {
|
||||
'icon': docInfo.createIcon(Search.RESULT_ICON_SIZE)};
|
||||
},
|
||||
|
||||
activateResult: function(id) {
|
||||
activateResult: function(id, params) {
|
||||
params = Params.parse(params, { workspace: null,
|
||||
timestamp: null });
|
||||
|
||||
let docInfo = this._docManager.lookupByUri(id);
|
||||
docInfo.launch();
|
||||
docInfo.launch(params.workspace ? params.workspace.index() : -1);
|
||||
},
|
||||
|
||||
getInitialResultSet: function(terms) {
|
||||
@ -512,6 +46,6 @@ DocSearchProvider.prototype = {
|
||||
},
|
||||
|
||||
expandSearch: function(terms) {
|
||||
log("TODO expand docs search");
|
||||
log('TODO expand docs search');
|
||||
}
|
||||
};
|
||||
|
504
js/ui/endSessionDialog.js
Normal file
@ -0,0 +1,504 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*-
|
||||
*
|
||||
* Copyright 2010 Red Hat, Inc
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2, or (at your option)
|
||||
* any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
|
||||
* 02111-1307, USA.
|
||||
*/
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdm = imports.gi.Gdm;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Pango = imports.gi.Pango;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const GnomeSession = imports.misc.gnomeSession
|
||||
const Lightbox = imports.ui.lightbox;
|
||||
const Main = imports.ui.main;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
let _endSessionDialog = null;
|
||||
|
||||
const _ITEM_ICON_SIZE = 48;
|
||||
const _DIALOG_ICON_SIZE = 32;
|
||||
|
||||
const GSM_SESSION_MANAGER_LOGOUT_FORCE = 2;
|
||||
|
||||
const EndSessionDialogIface = {
|
||||
name: 'org.gnome.SessionManager.EndSessionDialog',
|
||||
methods: [{ name: 'Open',
|
||||
inSignature: 'uuuao',
|
||||
outSignature: ''
|
||||
}
|
||||
],
|
||||
signals: [{ name: 'Canceled',
|
||||
outSignature: '',
|
||||
}],
|
||||
properties: []
|
||||
};
|
||||
|
||||
const logoutDialogContent = {
|
||||
subjectWithUser: _("Log Out %s"),
|
||||
subject: _("Log Out"),
|
||||
inhibitedDescription: _("Click Log Out to quit these applications and log out of the system."),
|
||||
uninhibitedDescriptionWithUser: _("%s will be logged out automatically in %d seconds."),
|
||||
uninhibitedDescription: _("You will be logged out automatically in %d seconds."),
|
||||
endDescription: _("Logging out of the system."),
|
||||
confirmButtonText: _("Log Out"),
|
||||
iconStyleClass: 'end-session-dialog-logout-icon'
|
||||
};
|
||||
|
||||
const shutdownDialogContent = {
|
||||
subject: _("Shut Down"),
|
||||
inhibitedDescription: _("Click Shut Down to quit these applications and shut down the system."),
|
||||
uninhibitedDescription: _("The system will shut down automatically in %d seconds."),
|
||||
endDescription: _("Shutting down the system."),
|
||||
confirmButtonText: _("Shut Down"),
|
||||
iconName: 'system-shutdown',
|
||||
iconStyleClass: 'end-session-dialog-shutdown-icon'
|
||||
};
|
||||
|
||||
const restartDialogContent = {
|
||||
subject: _("Restart"),
|
||||
inhibitedDescription: _("Click Restart to quit these applications and restart the system."),
|
||||
uninhibitedDescription: _("The system will restart automatically in %d seconds."),
|
||||
endDescription: _("Restarting the system."),
|
||||
confirmButtonText: _("Restart"),
|
||||
iconName: 'system-shutdown',
|
||||
iconStyleClass: 'end-session-dialog-shutdown-icon'
|
||||
};
|
||||
|
||||
const DialogContent = {
|
||||
0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */: logoutDialogContent,
|
||||
1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */: shutdownDialogContent,
|
||||
2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */: restartDialogContent
|
||||
};
|
||||
|
||||
function findAppFromInhibitor(inhibitor) {
|
||||
let desktopFile = inhibitor.app_id;
|
||||
|
||||
if (!GLib.str_has_suffix(desktopFile, '.desktop'))
|
||||
desktopFile += '.desktop';
|
||||
|
||||
let candidateDesktopFiles = [];
|
||||
|
||||
candidateDesktopFiles.push(desktopFile);
|
||||
candidateDesktopFiles.push('gnome-' + desktopFile);
|
||||
|
||||
let appSystem = Shell.AppSystem.get_default();
|
||||
let app = null;
|
||||
for (let i = 0; i < candidateDesktopFiles.length; i++) {
|
||||
try {
|
||||
app = appSystem.get_app(candidateDesktopFiles[i]);
|
||||
|
||||
if (app)
|
||||
break;
|
||||
} catch(e) {
|
||||
// ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
function ListItem(app, reason) {
|
||||
this._init(app, reason);
|
||||
}
|
||||
|
||||
ListItem.prototype = {
|
||||
_init: function(app, reason) {
|
||||
this._app = app;
|
||||
this._reason = reason;
|
||||
|
||||
if (this._reason == null)
|
||||
this._reason = '';
|
||||
|
||||
let layout = new St.BoxLayout({ vertical: false});
|
||||
|
||||
this.actor = new St.Clickable({ style_class: 'end-session-dialog-app-list-item',
|
||||
can_focus: true,
|
||||
child: layout,
|
||||
reactive: true,
|
||||
x_align: St.Align.START,
|
||||
x_fill: true });
|
||||
|
||||
this._icon = this._app.create_icon_texture(_ITEM_ICON_SIZE);
|
||||
|
||||
let iconBin = new St.Bin({ style_class: 'end-session-dialog-app-list-item-icon',
|
||||
child: this._icon });
|
||||
layout.add(iconBin);
|
||||
|
||||
let textLayout = new St.BoxLayout({ style_class: 'end-session-dialog-app-list-item-text-box',
|
||||
vertical: true });
|
||||
layout.add(textLayout);
|
||||
|
||||
this._nameLabel = new St.Label({ text: this._app.get_name(),
|
||||
style_class: 'end-session-dialog-app-list-item-name' });
|
||||
textLayout.add(this._nameLabel,
|
||||
{ expand: false,
|
||||
x_fill: true });
|
||||
|
||||
this._descriptionLabel = new St.Label({ text: this._reason,
|
||||
style_class: 'end-session-dialog-app-list-item-description' });
|
||||
textLayout.add(this._descriptionLabel,
|
||||
{ expand: true,
|
||||
x_fill: true });
|
||||
|
||||
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
|
||||
},
|
||||
|
||||
_onClicked: function() {
|
||||
this.emit('activate');
|
||||
this._app.activate(-1);
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(ListItem.prototype);
|
||||
|
||||
// The logout timer only shows updates every 10 seconds
|
||||
// until the last 10 seconds, then it shows updates every
|
||||
// second. This function takes a given time and returns
|
||||
// what we should show to the user for that time.
|
||||
function _roundSecondsToInterval(totalSeconds, secondsLeft, interval) {
|
||||
let time;
|
||||
|
||||
time = Math.ceil(secondsLeft);
|
||||
|
||||
// Final count down is in decrements of 1
|
||||
if (time <= interval)
|
||||
return time;
|
||||
|
||||
// Round up higher than last displayable time interval
|
||||
time += interval - 1;
|
||||
|
||||
// Then round down to that time interval
|
||||
if (time > totalSeconds)
|
||||
time = Math.ceil(totalSeconds);
|
||||
else
|
||||
time -= time % interval;
|
||||
|
||||
return time;
|
||||
}
|
||||
|
||||
function _setLabelText(label, text) {
|
||||
if (text) {
|
||||
label.set_text(text);
|
||||
label.show();
|
||||
} else {
|
||||
label.set_text('');
|
||||
label.hide();
|
||||
}
|
||||
}
|
||||
|
||||
function EndSessionDialog() {
|
||||
if (_endSessionDialog == null) {
|
||||
this._init();
|
||||
DBus.session.exportObject('/org/gnome/SessionManager/EndSessionDialog',
|
||||
this);
|
||||
_endSessionDialog = this;
|
||||
}
|
||||
|
||||
return _endSessionDialog;
|
||||
}
|
||||
|
||||
function init() {
|
||||
// This always returns the same singleton object
|
||||
// By instantiating it initially, we register the
|
||||
// bus object, etc.
|
||||
let dialog = new EndSessionDialog();
|
||||
}
|
||||
|
||||
EndSessionDialog.prototype = {
|
||||
__proto__: ModalDialog.ModalDialog.prototype,
|
||||
|
||||
_init: function() {
|
||||
ModalDialog.ModalDialog.prototype._init.call(this);
|
||||
|
||||
this._user = Gdm.UserManager.ref_default().get_user(GLib.get_user_name());
|
||||
|
||||
this._secondsLeft = 0;
|
||||
this._totalSecondsToStayOpen = 0;
|
||||
this._inhibitors = [];
|
||||
|
||||
this.connect('destroy',
|
||||
Lang.bind(this, this._onDestroy));
|
||||
this.connect('opened',
|
||||
Lang.bind(this, this._onOpened));
|
||||
|
||||
this._userLoadedId = this._user.connect('notify::is_loaded',
|
||||
Lang.bind(this, this._updateContent));
|
||||
|
||||
this._userChangedId = this._user.connect('changed',
|
||||
Lang.bind(this, this._updateContent));
|
||||
|
||||
let mainContentLayout = new St.BoxLayout({ vertical: false });
|
||||
this.contentLayout.add(mainContentLayout,
|
||||
{ x_fill: true,
|
||||
y_fill: false });
|
||||
|
||||
this._iconBin = new St.Bin();
|
||||
mainContentLayout.add(this._iconBin,
|
||||
{ x_fill: true,
|
||||
y_fill: false,
|
||||
x_align: St.Align.END,
|
||||
y_align: St.Align.START });
|
||||
|
||||
let messageLayout = new St.BoxLayout({ vertical: true });
|
||||
mainContentLayout.add(messageLayout,
|
||||
{ y_align: St.Align.START });
|
||||
|
||||
this._subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject' });
|
||||
|
||||
messageLayout.add(this._subjectLabel,
|
||||
{ y_fill: false,
|
||||
y_align: St.Align.START });
|
||||
|
||||
this._descriptionLabel = new St.Label({ style_class: 'end-session-dialog-description' });
|
||||
this._descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
this._descriptionLabel.clutter_text.line_wrap = true;
|
||||
|
||||
messageLayout.add(this._descriptionLabel,
|
||||
{ y_fill: true,
|
||||
y_align: St.Align.START });
|
||||
|
||||
let scrollView = new St.ScrollView({ style_class: 'end-session-dialog-app-list'});
|
||||
scrollView.set_policy(Gtk.PolicyType.NEVER,
|
||||
Gtk.PolicyType.AUTOMATIC);
|
||||
this.contentLayout.add(scrollView,
|
||||
{ x_fill: true,
|
||||
y_fill: true });
|
||||
this._applicationList = new St.BoxLayout({ vertical: true });
|
||||
scrollView.add_actor(this._applicationList,
|
||||
{ x_fill: true,
|
||||
y_fill: true,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.MIDDLE });
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
this._user.disconnect(this._userLoadedId);
|
||||
this._user.disconnect(this._userChangedId);
|
||||
},
|
||||
|
||||
_setIconFromFile: function(iconFile, styleClass) {
|
||||
if (styleClass)
|
||||
this._iconBin.set_style_class_name(styleClass);
|
||||
this._iconBin.set_style(null);
|
||||
|
||||
this._iconBin.child = null;
|
||||
if (iconFile) {
|
||||
this._iconBin.show();
|
||||
this._iconBin.set_style('background-image: url("' + iconFile + '");');
|
||||
} else {
|
||||
this._iconBin.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_setIconFromName: function(iconName, styleClass) {
|
||||
if (styleClass)
|
||||
this._iconBin.set_style_class_name(styleClass);
|
||||
this._iconBin.set_style(null);
|
||||
|
||||
if (iconName != null) {
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
let icon = textureCache.load_icon_name(this._iconBin.get_theme_node(),
|
||||
iconName,
|
||||
St.IconType.SYMBOLIC,
|
||||
_DIALOG_ICON_SIZE);
|
||||
|
||||
this._iconBin.child = icon;
|
||||
this._iconBin.show();
|
||||
} else {
|
||||
this._iconBin.child = null;
|
||||
this._iconBin.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_updateContent: function() {
|
||||
if (this.state != ModalDialog.State.OPENING &&
|
||||
this.state != ModalDialog.State.OPENED)
|
||||
return;
|
||||
|
||||
let dialogContent = DialogContent[this._type];
|
||||
|
||||
let subject = dialogContent.subject;
|
||||
let description;
|
||||
|
||||
if (this._user.is_loaded && !dialogContent.iconName) {
|
||||
let iconFile = this._user.get_icon_file();
|
||||
|
||||
this._setIconFromFile(iconFile, dialogContent.iconStyleClass);
|
||||
} else if (dialogContent.iconName) {
|
||||
this._setIconFromName(dialogContent.iconName,
|
||||
dialogContent.iconStyleClass);
|
||||
}
|
||||
|
||||
if (this._inhibitors.length > 0) {
|
||||
this._stopTimer();
|
||||
description = dialogContent.inhibitedDescription;
|
||||
} else if (this._secondsLeft > 0 && this._inhibitors.length == 0) {
|
||||
let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen,
|
||||
this._secondsLeft,
|
||||
10);
|
||||
|
||||
if (this._user.is_loaded) {
|
||||
let realName = this._user.get_real_name();
|
||||
|
||||
if (realName != null) {
|
||||
if (dialogContent.subjectWithUser)
|
||||
subject = dialogContent.subjectWithUser.format(realName);
|
||||
|
||||
if (dialogContent.uninhibitedDescriptionWithUser)
|
||||
description = dialogContent.uninhibitedDescriptionWithUser.format(realName, displayTime);
|
||||
else
|
||||
description = dialogContent.uninhibitedDescription.format(displayTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (!description)
|
||||
description = dialogContent.uninhibitedDescription.format(displayTime);
|
||||
} else {
|
||||
description = dialogContent.endDescription;
|
||||
}
|
||||
|
||||
_setLabelText(this._subjectLabel, subject);
|
||||
_setLabelText(this._descriptionLabel, description);
|
||||
},
|
||||
|
||||
_updateButtons: function() {
|
||||
if (this.state != ModalDialog.State.OPENING &&
|
||||
this.state != ModalDialog.State.OPENED)
|
||||
return;
|
||||
|
||||
let dialogContent = DialogContent[this._type];
|
||||
let confirmButtonText = _("Confirm");
|
||||
|
||||
if (dialogContent.confirmButtonText)
|
||||
confirmButtonText = dialogContent.confirmButtonText;
|
||||
|
||||
this.setButtons([{ label: _("Cancel"),
|
||||
action: Lang.bind(this, this.cancel),
|
||||
key: Clutter.Escape
|
||||
},
|
||||
{ label: confirmButtonText,
|
||||
action: Lang.bind(this, this._confirm)
|
||||
}]);
|
||||
},
|
||||
|
||||
cancel: function() {
|
||||
this._stopTimer();
|
||||
DBus.session.emit_signal('/org/gnome/SessionManager/EndSessionDialog',
|
||||
'org.gnome.SessionManager.EndSessionDialog',
|
||||
'Canceled', '', []);
|
||||
this.close(global.get_current_time());
|
||||
},
|
||||
|
||||
_confirm: function() {
|
||||
this._fadeOutDialog();
|
||||
this._stopTimer();
|
||||
DBus.session.emit_signal('/org/gnome/SessionManager/EndSessionDialog',
|
||||
'org.gnome.SessionManager.EndSessionDialog',
|
||||
'Confirmed', '', []);
|
||||
},
|
||||
|
||||
_onOpened: function() {
|
||||
if (this._inhibitors.length == 0)
|
||||
this._startTimer();
|
||||
},
|
||||
|
||||
_startTimer: function() {
|
||||
this._secondsLeft = this._totalSecondsToStayOpen;
|
||||
Tweener.addTween(this,
|
||||
{ _secondsLeft: 0,
|
||||
time: this._secondsLeft,
|
||||
transition: 'linear',
|
||||
onUpdate: Lang.bind(this, this._updateContent),
|
||||
onComplete: Lang.bind(this, this._confirm),
|
||||
});
|
||||
},
|
||||
|
||||
_stopTimer: function() {
|
||||
Tweener.removeTweens(this);
|
||||
this._secondsLeft = 0;
|
||||
},
|
||||
|
||||
_onInhibitorLoaded: function(inhibitor) {
|
||||
if (this._inhibitors.indexOf(inhibitor) < 0) {
|
||||
// Stale inhibitor
|
||||
return;
|
||||
}
|
||||
|
||||
let app = findAppFromInhibitor(inhibitor);
|
||||
|
||||
if (app) {
|
||||
let item = new ListItem(app, inhibitor.reason);
|
||||
item.connect('activate',
|
||||
Lang.bind(this, function() {
|
||||
this.close(global.get_current_time());
|
||||
}));
|
||||
this._applicationList.add(item.actor, { x_fill: true });
|
||||
this._stopTimer();
|
||||
} else {
|
||||
// inhibiting app is a service, not an application
|
||||
this._inhibitors.splice(this._inhibitors.indexOf(inhibitor), 1);
|
||||
}
|
||||
|
||||
this._updateContent();
|
||||
},
|
||||
|
||||
OpenAsync: function(type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths, callback) {
|
||||
this._totalSecondsToStayOpen = totalSecondsToStayOpen;
|
||||
this._inhibitors = [];
|
||||
this._applicationList.destroy_children();
|
||||
this._type = type;
|
||||
|
||||
if (!(this._type in DialogContent))
|
||||
throw new DBus.DBusError('org.gnome.Shell.ModalDialog.TypeError',
|
||||
"Unknown dialog type requested");
|
||||
|
||||
for (let i = 0; i < inhibitorObjectPaths.length; i++) {
|
||||
let inhibitor = new GnomeSession.Inhibitor(inhibitorObjectPaths[i]);
|
||||
|
||||
inhibitor.connect('is-loaded',
|
||||
Lang.bind(this, function() {
|
||||
this._onInhibitorLoaded(inhibitor);
|
||||
}));
|
||||
this._inhibitors.push(inhibitor);
|
||||
}
|
||||
|
||||
if (!this.open(timestamp))
|
||||
throw new DBus.DBusError('org.gnome.Shell.ModalDialog.GrabError',
|
||||
"Cannot grab pointer and keyboard");
|
||||
|
||||
this._updateButtons();
|
||||
this._updateContent();
|
||||
|
||||
let signalId = this.connect('opened',
|
||||
Lang.bind(this, function() {
|
||||
callback();
|
||||
this.disconnect(signalId);
|
||||
}));
|
||||
}
|
||||
};
|
||||
DBus.conformExport(EndSessionDialog.prototype, EndSessionDialogIface);
|
@ -1,7 +1,10 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Gettext_gtk20 = imports.gettext.domain('gtk20');
|
||||
const Gettext_gtk30 = imports.gettext.domain('gtk30');
|
||||
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
@ -28,15 +31,69 @@ function _patchContainerClass(containerClass) {
|
||||
};
|
||||
}
|
||||
|
||||
_patchContainerClass(St.BoxLayout);
|
||||
_patchContainerClass(St.Table);
|
||||
// Replace @method with something that throws an error instead
|
||||
function _blockMethod(method, replacement, reason) {
|
||||
let match = method.match(/^(.+)\.([^.]+)$/);
|
||||
if (!match)
|
||||
throw new Error('Bad method name "' + method + '"');
|
||||
let proto = 'imports.gi.' + match[1] + '.prototype';
|
||||
let property = match[2];
|
||||
|
||||
if (!global.set_property_mutable(proto, property, true))
|
||||
throw new Error('Bad method name "' + method + '"');
|
||||
|
||||
// eval() is evil in general, but we know it's safe here since
|
||||
// set_property_mutable() would have failed if proto was
|
||||
// malformed.
|
||||
let node = eval(proto);
|
||||
|
||||
let msg = 'Do not use "' + method + '".';
|
||||
if (replacement)
|
||||
msg += ' Use "' + replacement + '" instead.';
|
||||
if (reason)
|
||||
msg += ' (' + reason + ')';
|
||||
|
||||
node[property] = function() {
|
||||
throw new Error(msg);
|
||||
};
|
||||
|
||||
global.set_property_mutable(proto, property, false);
|
||||
}
|
||||
|
||||
function init() {
|
||||
Tweener.init();
|
||||
String.prototype.format = Format.format;
|
||||
|
||||
// Set the default direction for St widgets (this needs to be done before any use of St)
|
||||
if (Gettext_gtk20.gettext("default:LTR") == "default:RTL") {
|
||||
if (Gettext_gtk30.gettext('default:LTR') == 'default:RTL') {
|
||||
St.Widget.set_default_direction(St.TextDirection.RTL);
|
||||
}
|
||||
|
||||
let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR');
|
||||
if (slowdownEnv) {
|
||||
let factor = parseFloat(slowdownEnv);
|
||||
if (!isNaN(factor) && factor > 0.0)
|
||||
St.set_slow_down_factor(factor);
|
||||
}
|
||||
|
||||
_patchContainerClass(St.BoxLayout);
|
||||
_patchContainerClass(St.Table);
|
||||
|
||||
Clutter.Actor.prototype.toString = function() {
|
||||
return St.describe_actor(this);
|
||||
};
|
||||
|
||||
if (window.global === undefined) // test environment
|
||||
return;
|
||||
|
||||
_blockMethod('Clutter.Event.get_state', 'Shell.get_event_state',
|
||||
'gjs\'s handling of Clutter.ModifierType is broken. See bug 597292.');
|
||||
_blockMethod('Gdk.Window.get_device_position', 'global.get_pointer',
|
||||
'gjs\'s handling of Gdk.ModifierType is broken. See bug 597292.');
|
||||
|
||||
// Now close the back door to prevent extensions from trying to
|
||||
// abuse it. We can't actually delete it since
|
||||
// Shell.Global.prototype itself is read-only.
|
||||
global.set_property_mutable('imports.gi.Shell.Global.prototype', 'set_property_mutable', true);
|
||||
Shell.Global.prototype.set_property_mutable = undefined;
|
||||
}
|
||||
|
@ -3,7 +3,8 @@
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
|
||||
const ExtensionState = {
|
||||
ENABLED: 1,
|
||||
@ -26,6 +27,36 @@ var disabledExtensions;
|
||||
// GFile for user extensions
|
||||
var userExtensionsDir = null;
|
||||
|
||||
/**
|
||||
* versionCheck:
|
||||
* @required: an array of versions we're compatible with
|
||||
* @current: the version we have
|
||||
*
|
||||
* Check if a component is compatible for an extension.
|
||||
* @required is an array, and at least one version must match.
|
||||
* @current must be in the format <major>.<minor>.<point>.<micro>
|
||||
* <micro> is always ignored
|
||||
* <point> is ignored if <minor> is even (so you can target the
|
||||
* whole stable release)
|
||||
* <minor> and <major> must match
|
||||
* Each target version must be at least <major> and <minor>
|
||||
*/
|
||||
function versionCheck(required, current) {
|
||||
let currentArray = current.split('.');
|
||||
let major = currentArray[0];
|
||||
let minor = currentArray[1];
|
||||
let point = currentArray[2];
|
||||
for (let i = 0; i < required.length; i++) {
|
||||
let requiredArray = required[i].split('.');
|
||||
if (requiredArray[0] == major &&
|
||||
requiredArray[1] == minor &&
|
||||
(requiredArray[2] == point ||
|
||||
(requiredArray[2] == undefined && parseInt(minor) % 2 == 0)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function loadExtension(dir, enabled, type) {
|
||||
let info;
|
||||
let baseErrorString = 'While loading extension from "' + dir.get_parse_name() + '": ';
|
||||
@ -44,14 +75,20 @@ function loadExtension(dir, enabled, type) {
|
||||
global.logError(baseErrorString + 'Failed to parse metadata.json: ' + e);
|
||||
return;
|
||||
}
|
||||
let requiredProperties = ['uuid', 'name', 'description'];
|
||||
for (let i = 0; i < requiredProperties; i++) {
|
||||
let requiredProperties = ['uuid', 'name', 'description', 'shell-version'];
|
||||
for (let i = 0; i < requiredProperties.length; i++) {
|
||||
let prop = requiredProperties[i];
|
||||
if (!meta[prop]) {
|
||||
global.logError(baseErrorString + 'missing "' + prop + '" property in metadata.json');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (extensions[meta.uuid] != undefined) {
|
||||
global.logError(baseErrorString + "extension already loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
// Encourage people to add this
|
||||
if (!meta['url']) {
|
||||
global.log(baseErrorString + 'Warning: Missing "url" property in metadata.json');
|
||||
@ -63,6 +100,12 @@ function loadExtension(dir, enabled, type) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!versionCheck(meta['shell-version'], Config.PACKAGE_VERSION) ||
|
||||
(meta['js-version'] && !versionCheck(meta['js-version'], Config.GJS_VERSION))) {
|
||||
global.logError(baseErrorString + 'extension is not compatible with current GNOME Shell and/or GJS version');
|
||||
return;
|
||||
}
|
||||
|
||||
extensionMeta[meta.uuid] = meta;
|
||||
extensionMeta[meta.uuid].type = type;
|
||||
extensionMeta[meta.uuid].path = dir.get_path();
|
||||
@ -119,16 +162,15 @@ function loadExtension(dir, enabled, type) {
|
||||
}
|
||||
|
||||
function init() {
|
||||
let userConfigPath = GLib.get_user_config_dir();
|
||||
let userExtensionsPath = GLib.build_filenamev([userConfigPath, 'gnome-shell', 'extensions']);
|
||||
let userExtensionsPath = GLib.build_filenamev([global.userdatadir, 'extensions']);
|
||||
userExtensionsDir = Gio.file_new_for_path(userExtensionsPath);
|
||||
try {
|
||||
userExtensionsDir.make_directory_with_parents(null);
|
||||
} catch (e) {
|
||||
global.logError(""+e);
|
||||
global.logError('' + e);
|
||||
}
|
||||
|
||||
disabledExtensions = Shell.GConf.get_default().get_string_list('disabled_extensions');
|
||||
disabledExtensions = global.settings.get_strv('disabled-extensions', -1);
|
||||
}
|
||||
|
||||
function _loadExtensionsIn(dir, type) {
|
||||
|
@ -1,724 +0,0 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Pango = imports.gi.Pango;
|
||||
const Signals = imports.signals;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const RedisplayFlags = { NONE: 0,
|
||||
FULL: 1 << 1,
|
||||
SUBSEARCH: 1 << 2,
|
||||
IMMEDIATE: 1 << 3 };
|
||||
|
||||
const ITEM_DISPLAY_DESCRIPTION_COLOR = new Clutter.Color();
|
||||
ITEM_DISPLAY_DESCRIPTION_COLOR.from_pixel(0xffffffbb);
|
||||
const DISPLAY_CONTROL_SELECTED_COLOR = new Clutter.Color();
|
||||
DISPLAY_CONTROL_SELECTED_COLOR.from_pixel(0x112288ff);
|
||||
const PREVIEW_BOX_BACKGROUND_COLOR = new Clutter.Color();
|
||||
PREVIEW_BOX_BACKGROUND_COLOR.from_pixel(0xADADADf0);
|
||||
|
||||
const DEFAULT_PADDING = 4;
|
||||
|
||||
const ITEM_DISPLAY_ICON_SIZE = 48;
|
||||
const DEFAULT_COLUMN_GAP = 6;
|
||||
|
||||
const PREVIEW_ICON_SIZE = 96;
|
||||
const PREVIEW_BOX_PADDING = 6;
|
||||
const PREVIEW_BOX_SPACING = DEFAULT_PADDING;
|
||||
const PREVIEW_BOX_CORNER_RADIUS = 10;
|
||||
// how far relative to the full item width the preview box should be placed
|
||||
const PREVIEW_PLACING = 3/4;
|
||||
const PREVIEW_DETAILS_MIN_WIDTH = PREVIEW_ICON_SIZE * 2;
|
||||
|
||||
/* This is a virtual class that represents a single display item containing
|
||||
* a name, a description, and an icon. It allows selecting an item and represents
|
||||
* it by highlighting it with a different background color than the default.
|
||||
*/
|
||||
function GenericDisplayItem() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
GenericDisplayItem.prototype = {
|
||||
_init: function() {
|
||||
this.actor = new St.BoxLayout({ style_class: "generic-display-item",
|
||||
reactive: true });
|
||||
|
||||
this.actor._delegate = this;
|
||||
this.actor.connect('button-release-event',
|
||||
Lang.bind(this,
|
||||
function() {
|
||||
// Activates the item by launching it
|
||||
this.emit('activate');
|
||||
return true;
|
||||
}));
|
||||
|
||||
let draggable = DND.makeDraggable(this.actor);
|
||||
|
||||
this._iconBin = new St.Bin();
|
||||
this.actor.add(this._iconBin);
|
||||
|
||||
this._infoText = new St.BoxLayout({ style_class: 'generic-display-item-text',
|
||||
vertical: true });
|
||||
this.actor.add(this._infoText, { expand: true, y_fill: false });
|
||||
|
||||
this._name = null;
|
||||
this._description = null;
|
||||
this._icon = null;
|
||||
|
||||
this._initialLoadComplete = false;
|
||||
|
||||
// An array of details description actors that we create over time for the item.
|
||||
// It is used for updating the description text inside the details actor when
|
||||
// the description text for the item is updated.
|
||||
this._detailsDescriptions = [];
|
||||
},
|
||||
|
||||
//// Draggable object interface ////
|
||||
|
||||
// Returns a cloned texture of the item's icon to represent the item as it
|
||||
// is being dragged.
|
||||
getDragActor: function(stageX, stageY) {
|
||||
return this._createIcon();
|
||||
},
|
||||
|
||||
// Returns the item icon, a separate copy of which is used to
|
||||
// represent the item as it is being dragged. This is used to
|
||||
// determine a snap-back location for the drag icon if it does
|
||||
// not get accepted by any drop target.
|
||||
getDragActorSource: function() {
|
||||
return this._icon;
|
||||
},
|
||||
|
||||
//// Public methods ////
|
||||
|
||||
// Highlights the item by setting a different background color than the default
|
||||
// if isSelected is true, removes the highlighting otherwise.
|
||||
markSelected: function(isSelected) {
|
||||
this.actor.set_style_pseudo_class(isSelected ? "selected" : null);
|
||||
},
|
||||
|
||||
/*
|
||||
* Returns an actor containing item details. In the future details can have more information than what
|
||||
* the preview pop-up has and be item-type specific.
|
||||
*/
|
||||
createDetailsActor: function() {
|
||||
|
||||
let details = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
spacing: PREVIEW_BOX_SPACING });
|
||||
|
||||
let mainDetails = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: PREVIEW_BOX_SPACING });
|
||||
|
||||
// Inner box with name and description
|
||||
let textDetails = new St.BoxLayout({ style_class: 'generic-display-details',
|
||||
vertical: true });
|
||||
let detailsName = new St.Label({ style_class: 'generic-display-details-name',
|
||||
text: this._name.text });
|
||||
textDetails.add(detailsName);
|
||||
|
||||
let detailsDescription = new St.Label({ text: this._description.text });
|
||||
textDetails.add(detailsDescription);
|
||||
|
||||
this._detailsDescriptions.push(detailsDescription);
|
||||
|
||||
mainDetails.append(textDetails, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
let previewIcon = this._createPreviewIcon();
|
||||
let largePreviewIcon = this._createLargePreviewIcon();
|
||||
|
||||
if (previewIcon != null && largePreviewIcon == null) {
|
||||
mainDetails.prepend(previewIcon, Big.BoxPackFlags.NONE);
|
||||
}
|
||||
|
||||
details.append(mainDetails, Big.BoxPackFlags.NONE);
|
||||
|
||||
if (largePreviewIcon != null) {
|
||||
let largePreview = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
|
||||
largePreview.append(largePreviewIcon, Big.BoxPackFlags.NONE);
|
||||
details.append(largePreview, Big.BoxPackFlags.NONE);
|
||||
}
|
||||
|
||||
return details;
|
||||
},
|
||||
|
||||
// Destroys the item.
|
||||
destroy: function() {
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
//// Pure virtual public methods ////
|
||||
|
||||
// Performes an action associated with launching this item, such as opening a file or an application.
|
||||
launch: function() {
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
//// Protected methods ////
|
||||
|
||||
/*
|
||||
* Creates the graphical elements for the item based on the item information.
|
||||
*
|
||||
* nameText - name of the item
|
||||
* descriptionText - short description of the item
|
||||
*/
|
||||
_setItemInfo: function(nameText, descriptionText) {
|
||||
if (this._name != null) {
|
||||
// this also removes this._name from the parent container,
|
||||
// so we don't need to call this.actor.remove_actor(this._name) directly
|
||||
this._name.destroy();
|
||||
this._name = null;
|
||||
}
|
||||
if (this._description != null) {
|
||||
this._description.destroy();
|
||||
this._description = null;
|
||||
}
|
||||
if (this._icon != null) {
|
||||
// though we get the icon from elsewhere, we assume its ownership here,
|
||||
// and therefore should be responsible for distroying it
|
||||
this._icon.destroy();
|
||||
this._icon = null;
|
||||
}
|
||||
|
||||
this._icon = this._createIcon();
|
||||
this._iconBin.set_child(this._icon);
|
||||
|
||||
this._name = new St.Label({ style_class: "generic-display-item-name",
|
||||
text: nameText });
|
||||
this._infoText.add(this._name);
|
||||
|
||||
this._description = new St.Label({ style_class: "generic-display-item-description",
|
||||
text: descriptionText ? descriptionText : "" });
|
||||
this._infoText.add(this._description);
|
||||
},
|
||||
|
||||
// Sets the description text for the item, including the description text
|
||||
// in the details actors that have been created for the item.
|
||||
_setDescriptionText: function(text) {
|
||||
this._description.text = text;
|
||||
for (let i = 0; i < this._detailsDescriptions.length; i++) {
|
||||
let detailsDescription = this._detailsDescriptions[i];
|
||||
if (detailsDescription != null) {
|
||||
detailsDescription.text = text;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//// Virtual protected methods ////
|
||||
|
||||
// Creates and returns a large preview icon, but only if we have a detailed image.
|
||||
_createLargePreviewIcon : function() {
|
||||
return null;
|
||||
},
|
||||
|
||||
//// Pure virtual protected methods ////
|
||||
|
||||
// Returns an icon for the item.
|
||||
_createIcon: function() {
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
// Returns a preview icon for the item.
|
||||
_createPreviewIcon: function() {
|
||||
throw new Error("Not implemented");
|
||||
}
|
||||
|
||||
//// Private methods ////
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(GenericDisplayItem.prototype);
|
||||
|
||||
const GenericDisplayFlags = {
|
||||
DISABLE_VSCROLLING: 1 << 0
|
||||
}
|
||||
|
||||
/* This is a virtual class that represents a display containing a collection of items
|
||||
* that can be filtered with a search string.
|
||||
*/
|
||||
function GenericDisplay(flags) {
|
||||
this._init(flags);
|
||||
}
|
||||
|
||||
GenericDisplay.prototype = {
|
||||
_init : function(flags) {
|
||||
let disableVScrolling = (flags & GenericDisplayFlags.DISABLE_VSCROLLING) != 0;
|
||||
this._search = '';
|
||||
this._expanded = false;
|
||||
|
||||
if (disableVScrolling) {
|
||||
this.actor = this._list = new Shell.OverflowList({ spacing: 6,
|
||||
item_height: 50 });
|
||||
} else {
|
||||
this.actor = new St.ScrollView({ x_fill: true, y_fill: true });
|
||||
this.actor.get_hscroll_bar().hide();
|
||||
this._list = new St.BoxLayout({ style_class: 'generic-display-container',
|
||||
vertical: true });
|
||||
this.actor.add_actor(this._list);
|
||||
}
|
||||
|
||||
this._pendingRedisplay = RedisplayFlags.NONE;
|
||||
this.actor.connect('notify::mapped', Lang.bind(this, this._onMappedNotify));
|
||||
|
||||
// map<itemId, Object> where Object represents the item info
|
||||
this._allItems = {};
|
||||
// set<itemId>
|
||||
this._matchedItems = {};
|
||||
// sorted array of items matched by search
|
||||
this._matchedItemKeys = [];
|
||||
// map<itemId, GenericDisplayItem>
|
||||
this._displayedItems = {};
|
||||
this._openDetailIndex = -1;
|
||||
this._selectedIndex = -1;
|
||||
},
|
||||
|
||||
//// Public methods ////
|
||||
|
||||
// Sets the search string and displays the matching items.
|
||||
setSearch: function(text) {
|
||||
let lowertext = text.toLowerCase();
|
||||
if (lowertext == this._search) {
|
||||
return;
|
||||
}
|
||||
let flags = RedisplayFlags.IMMEDIATE;
|
||||
if (this._search != '') {
|
||||
// Because we combine search terms with OR, we have to be sure that no new term
|
||||
// was introduced before deciding that the new search results will be a subset of
|
||||
// the existing search results.
|
||||
if (lowertext.indexOf(this._search) == 0 &&
|
||||
lowertext.split(/\s+/).length == this._search.split(/\s+/).length) {
|
||||
flags |= RedisplayFlags.SUBSEARCH;
|
||||
}
|
||||
}
|
||||
this._search = lowertext;
|
||||
this._redisplay(flags);
|
||||
},
|
||||
|
||||
// Launches the item that is currently selected, closing the Overview
|
||||
activateSelected: function() {
|
||||
if (this._selectedIndex != -1) {
|
||||
let selected = this._findDisplayedByIndex(this._selectedIndex);
|
||||
selected.launch();
|
||||
this.unsetSelected();
|
||||
Main.overview.hide();
|
||||
}
|
||||
},
|
||||
|
||||
// Moves the selection one up. If the selection was already on the top item, it's moved
|
||||
// to the bottom one. Returns true if the selection actually moved up, false if it wrapped
|
||||
// around to the bottom.
|
||||
selectUp: function() {
|
||||
let count = this._getVisibleCount();
|
||||
let selectedUp = true;
|
||||
let prev = this._selectedIndex - 1;
|
||||
if (this._selectedIndex <= 0) {
|
||||
prev = count - 1;
|
||||
selectedUp = false;
|
||||
}
|
||||
this._selectIndex(prev);
|
||||
return selectedUp;
|
||||
},
|
||||
|
||||
// Moves the selection one down. If the selection was already on the bottom item, it's moved
|
||||
// to the top one. Returns true if the selection actually moved down, false if it wrapped
|
||||
// around to the top.
|
||||
selectDown: function() {
|
||||
let count = this._getVisibleCount();
|
||||
let selectedDown = true;
|
||||
let next = this._selectedIndex + 1;
|
||||
if (this._selectedIndex == count - 1) {
|
||||
next = 0;
|
||||
selectedDown = false;
|
||||
}
|
||||
this._selectIndex(next);
|
||||
return selectedDown;
|
||||
},
|
||||
|
||||
// Selects the first item among the displayed items.
|
||||
selectFirstItem: function() {
|
||||
if (this.hasItems())
|
||||
this._selectIndex(0);
|
||||
},
|
||||
|
||||
// Selects the last item among the displayed items.
|
||||
selectLastItem: function() {
|
||||
let count = this._getVisibleCount();
|
||||
if (this.hasItems())
|
||||
this._selectIndex(count - 1);
|
||||
},
|
||||
|
||||
// Returns true if the display has some item selected.
|
||||
hasSelected: function() {
|
||||
return this._selectedIndex != -1;
|
||||
},
|
||||
|
||||
// Removes selection if some display item is selected.
|
||||
unsetSelected: function() {
|
||||
this._selectIndex(-1);
|
||||
},
|
||||
|
||||
// Returns true if the display has any displayed items.
|
||||
hasItems: function() {
|
||||
// TODO: figure out why this._list.displayedCount is returning a
|
||||
// positive number when this._mathedItems.length is 0
|
||||
// This can be triggered if a search string is entered for which there are no matches.
|
||||
// log("this._mathedItems.length: " + this._matchedItems.length + " this._list.displayedCount " + this._list.displayedCount);
|
||||
return this._matchedItemKeys.length > 0;
|
||||
},
|
||||
|
||||
getMatchedItemsCount: function() {
|
||||
return this._matchedItemKeys.length;
|
||||
},
|
||||
|
||||
// Load the initial state
|
||||
load: function() {
|
||||
this._redisplay(RedisplayFlags.FULL);
|
||||
},
|
||||
|
||||
// Should be called when the display is closed
|
||||
resetState: function() {
|
||||
this._filterReset();
|
||||
this._openDetailIndex = -1;
|
||||
if (!(this.actor instanceof Shell.OverflowList))
|
||||
this.actor.get_vscroll_bar().get_adjustment().value = 0;
|
||||
},
|
||||
|
||||
// Returns an actor which acts as a sidebar; this is used for
|
||||
// the applications category view
|
||||
getNavigationArea: function () {
|
||||
return null;
|
||||
},
|
||||
|
||||
createDetailsForIndex: function(index) {
|
||||
let item = this._findDisplayedByIndex(index);
|
||||
return item.createDetailsActor();
|
||||
},
|
||||
|
||||
//// Protected methods ////
|
||||
|
||||
_recreateDisplayItems: function() {
|
||||
this._removeAllDisplayItems();
|
||||
this._setDefaultList();
|
||||
for (let itemId in this._allItems) {
|
||||
this._addDisplayItem(itemId);
|
||||
}
|
||||
},
|
||||
|
||||
// Creates a display item based on the information associated with itemId
|
||||
// and adds it to the list of displayed items, but does not yet display it.
|
||||
_addDisplayItem : function(itemId) {
|
||||
if (this._displayedItems.hasOwnProperty(itemId)) {
|
||||
log("Tried adding a display item for " + itemId + ", but an item with this item id is already among displayed items.");
|
||||
return;
|
||||
}
|
||||
|
||||
let itemInfo = this._allItems[itemId];
|
||||
let displayItem = this._createDisplayItem(itemInfo);
|
||||
|
||||
displayItem.connect('activate',
|
||||
Lang.bind(this,
|
||||
function() {
|
||||
// update the selection
|
||||
this._selectIndex(this._list.get_children().indexOf(displayItem.actor));
|
||||
this.activateSelected();
|
||||
}));
|
||||
|
||||
displayItem.connect('show-details',
|
||||
Lang.bind(this,
|
||||
function() {
|
||||
let index = this._list.get_children().indexOf(displayItem.actor);
|
||||
/* Close the details pane if already open */
|
||||
if (index == this._openDetailIndex) {
|
||||
this._openDetailIndex = -1;
|
||||
this.emit('show-details', -1);
|
||||
} else {
|
||||
this._openDetailIndex = index;
|
||||
this.emit('show-details', index);
|
||||
}
|
||||
}));
|
||||
this._displayedItems[itemId] = displayItem;
|
||||
},
|
||||
|
||||
// Removes an item identifed by the itemId from the displayed items.
|
||||
_removeDisplayItem: function(itemId) {
|
||||
let children = this._list.get_children();
|
||||
let count = children.length;
|
||||
let displayItem = this._displayedItems[itemId];
|
||||
let displayItemIndex = children.indexOf(displayItem.actor);
|
||||
|
||||
if (this.hasSelected() && count == 1) {
|
||||
this.unsetSelected();
|
||||
} else if (this.hasSelected() && displayItemIndex < this._selectedIndex) {
|
||||
this.selectUp();
|
||||
}
|
||||
|
||||
displayItem.destroy();
|
||||
|
||||
delete this._displayedItems[itemId];
|
||||
},
|
||||
|
||||
// Removes all displayed items.
|
||||
_removeAllDisplayItems: function() {
|
||||
this.unsetSelected();
|
||||
for (itemId in this._displayedItems)
|
||||
this._removeDisplayItem(itemId);
|
||||
},
|
||||
|
||||
// Return true if there's an active search or other constraint
|
||||
// on the list
|
||||
_filterActive: function() {
|
||||
return this._search != "";
|
||||
},
|
||||
|
||||
// Called when we are resetting state
|
||||
_filterReset: function() {
|
||||
this.unsetSelected();
|
||||
},
|
||||
|
||||
_compareSearchMatch: function(a, b) {
|
||||
let countA = this._matchedItems[a];
|
||||
let countB = this._matchedItems[b];
|
||||
if (countA > countB)
|
||||
return -1;
|
||||
else if (countA < countB)
|
||||
return 1;
|
||||
else
|
||||
return this._compareItems(a, b);
|
||||
},
|
||||
|
||||
_setMatches: function(matches) {
|
||||
this._matchedItems = matches;
|
||||
this._matchedItemKeys = [];
|
||||
for (let itemId in this._matchedItems) {
|
||||
this._matchedItemKeys.push(itemId);
|
||||
}
|
||||
this._matchedItemKeys.sort(Lang.bind(this, this._compareSearchMatch));
|
||||
},
|
||||
|
||||
/**
|
||||
* _redisplaySubSearch:
|
||||
* A somewhat more optimized function called when we know
|
||||
* that we're going to be displaying a subset of the items
|
||||
* we already had, in the same order. In that case, we can
|
||||
* just hide the actors that shouldn't be shown.
|
||||
*/
|
||||
_redisplaySubSearch: function() {
|
||||
let matches = this._getSearchMatchedItems(true);
|
||||
|
||||
// Just hide all from the old set,
|
||||
// we'll show the ones we want below
|
||||
for (let itemId in this._displayedItems) {
|
||||
let item = this._displayedItems[itemId];
|
||||
item.actor.hide();
|
||||
}
|
||||
|
||||
this._setMatches(matches);
|
||||
|
||||
for (let itemId in matches) {
|
||||
let item = this._displayedItems[itemId];
|
||||
item.actor.show();
|
||||
}
|
||||
this._list.queue_relayout();
|
||||
},
|
||||
|
||||
_redisplayReordering: function() {
|
||||
if (!this._filterActive()) {
|
||||
this._setDefaultList();
|
||||
} else {
|
||||
this._setMatches(this._getSearchMatchedItems(false));
|
||||
}
|
||||
this._list.remove_all();
|
||||
for (let i = 0; i < this._matchedItemKeys.length; i++) {
|
||||
let itemId = this._matchedItemKeys[i];
|
||||
let item = this._displayedItems[itemId];
|
||||
item.actor.show();
|
||||
this._list.add_actor(item.actor);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Updates the displayed items, applying the search string if one exists.
|
||||
* @flags: Flags controlling redisplay behavior as follows:
|
||||
* SUBSEARCH - Indicates that the current _search is a superstring of the previous
|
||||
* one, which implies we only need to re-search through previous results.
|
||||
* FULL - Indicates that we need recreate all displayed items.
|
||||
* IMMEDIATE - Do the full redisplay even if we're not mapped. This is useful
|
||||
* if you want to get the number of matched items and show/hide a section based on
|
||||
* that number.
|
||||
*/
|
||||
_redisplay: function(flags) {
|
||||
let immediate = (flags & RedisplayFlags.IMMEDIATE) != 0;
|
||||
if (!immediate && !this.actor.mapped) {
|
||||
this._pendingRedisplay |= flags;
|
||||
return;
|
||||
}
|
||||
|
||||
let isSubSearch = (flags & RedisplayFlags.SUBSEARCH) != 0;
|
||||
let fullReload = (flags & RedisplayFlags.FULL) != 0;
|
||||
|
||||
let hadSelected = this.hasSelected();
|
||||
this.unsetSelected();
|
||||
|
||||
if (!this._initialLoadComplete)
|
||||
fullReload = true;
|
||||
|
||||
if (!this._refreshCache())
|
||||
fullReload = true;
|
||||
|
||||
if (fullReload) {
|
||||
this._recreateDisplayItems();
|
||||
this._initialLoadComplete = true;
|
||||
}
|
||||
|
||||
if (isSubSearch) {
|
||||
this._redisplaySubSearch();
|
||||
} else {
|
||||
this._redisplayReordering();
|
||||
}
|
||||
|
||||
if (hadSelected) {
|
||||
this._selectedIndex = -1;
|
||||
this.selectFirstItem();
|
||||
}
|
||||
|
||||
this.emit('redisplayed');
|
||||
},
|
||||
|
||||
//// Pure virtual protected methods ////
|
||||
|
||||
// Performs the steps needed to have the latest information about the items.
|
||||
// Implementation should return %true if we are up to date, and %false
|
||||
// if a full reload occurred.
|
||||
_refreshCache: function() {
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
// Sets the list of the displayed items based on the default sorting order.
|
||||
// The default sorting order is specific to each implementing class.
|
||||
_setDefaultList: function() {
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
// Compares items associated with the item ids based on the order in which the
|
||||
// items should be displayed.
|
||||
// Intended to be used as a compareFunction for array.sort().
|
||||
// Returns an integer value indicating the result of the comparison.
|
||||
_compareItems: function(itemIdA, itemIdB) {
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
// Checks if the item info can be a match for the search string.
|
||||
// Returns a boolean flag indicating if that's the case.
|
||||
_isInfoMatching: function(itemInfo, search) {
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
// Creates a display item based on itemInfo.
|
||||
_createDisplayItem: function(itemInfo) {
|
||||
throw new Error("Not implemented");
|
||||
},
|
||||
|
||||
//// Private methods ////
|
||||
|
||||
_getItemSearchScore: function(itemId, terms) {
|
||||
let item = this._allItems[itemId];
|
||||
let score = 0;
|
||||
for (let i = 0; i < terms.length; i++) {
|
||||
let term = terms[i];
|
||||
if (this._isInfoMatching(item, term)) {
|
||||
score++;
|
||||
}
|
||||
}
|
||||
return score;
|
||||
},
|
||||
|
||||
_getSearchMatchedItems: function(isSubSearch) {
|
||||
// Break the search up into terms, and search for each
|
||||
// individual term. Keep track of the number of terms
|
||||
// each item matched.
|
||||
let terms = this._search.split(/\s+/);
|
||||
let matchScores = {};
|
||||
|
||||
if (isSubSearch) {
|
||||
for (let i = 0; i < this._matchedItemKeys.length; i++) {
|
||||
let itemId = this._matchedItemKeys[i];
|
||||
let score = this._getItemSearchScore(itemId, terms);
|
||||
if (score > 0)
|
||||
matchScores[itemId] = score;
|
||||
}
|
||||
} else {
|
||||
for (let itemId in this._displayedItems) {
|
||||
let score = this._getItemSearchScore(itemId, terms);
|
||||
if (score > 0)
|
||||
matchScores[itemId] = score;
|
||||
}
|
||||
}
|
||||
return matchScores;
|
||||
},
|
||||
|
||||
// Returns a display item based on its index in the ordering of the
|
||||
// display children.
|
||||
_findDisplayedByIndex: function(index) {
|
||||
let actor;
|
||||
if (this.actor instanceof Shell.OverflowList)
|
||||
actor = this.actor.get_displayed_actor(index);
|
||||
else
|
||||
actor = this._list.get_children()[index];
|
||||
return this._findDisplayedByActor(actor);
|
||||
},
|
||||
|
||||
// Returns a display item based on the actor that represents it in
|
||||
// the display.
|
||||
_findDisplayedByActor: function(actor) {
|
||||
for (itemId in this._displayedItems) {
|
||||
let item = this._displayedItems[itemId];
|
||||
if (item.actor == actor) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// Selects (e.g. highlights) a display item at the provided index,
|
||||
// updates this.selectedItemDetails actor, and emits 'selected' signal.
|
||||
_selectIndex: function(index) {
|
||||
// Cleanup from the previous item
|
||||
if (this.hasSelected()) {
|
||||
this._findDisplayedByIndex(this._selectedIndex).markSelected(false);
|
||||
}
|
||||
|
||||
this._selectedIndex = index;
|
||||
if (index < 0)
|
||||
return
|
||||
|
||||
// Mark the new item as selected and create its details pane
|
||||
let item = this._findDisplayedByIndex(index);
|
||||
item.markSelected(true);
|
||||
this.emit('selected');
|
||||
},
|
||||
|
||||
_getVisibleCount: function() {
|
||||
if (this.actor instanceof Shell.OverflowList)
|
||||
return this._list.displayed_count;
|
||||
return this._list.get_n_children();
|
||||
},
|
||||
|
||||
_onMappedNotify: function () {
|
||||
let mapped = this.actor.mapped;
|
||||
if (mapped && this._pendingRedisplay > RedisplayFlags.NONE)
|
||||
this._redisplay(this._pendingRedisplay);
|
||||
|
||||
this._pendingRedisplay = RedisplayFlags.NONE;
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(GenericDisplay.prototype);
|
298
js/ui/iconGrid.js
Normal file
@ -0,0 +1,298 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Params = imports.misc.params;
|
||||
|
||||
const ICON_SIZE = 48;
|
||||
|
||||
|
||||
function BaseIcon(label, createIcon) {
|
||||
this._init(label, createIcon);
|
||||
}
|
||||
|
||||
BaseIcon.prototype = {
|
||||
_init : function(label, params) {
|
||||
params = Params.parse(params, { createIcon: null,
|
||||
setSizeManually: false });
|
||||
this.actor = new St.Bin({ style_class: 'overview-icon',
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
this.actor._delegate = this;
|
||||
this.actor.connect('style-changed',
|
||||
Lang.bind(this, this._onStyleChanged));
|
||||
|
||||
this._spacing = 0;
|
||||
|
||||
let box = new Shell.GenericContainer();
|
||||
box.connect('allocate', Lang.bind(this, this._allocate));
|
||||
box.connect('get-preferred-width',
|
||||
Lang.bind(this, this._getPreferredWidth));
|
||||
box.connect('get-preferred-height',
|
||||
Lang.bind(this, this._getPreferredHeight));
|
||||
this.actor.set_child(box);
|
||||
|
||||
this.iconSize = ICON_SIZE;
|
||||
this._iconBin = new St.Bin();
|
||||
|
||||
box.add_actor(this._iconBin);
|
||||
|
||||
this._name = new St.Label({ text: label });
|
||||
box.add_actor(this._name);
|
||||
|
||||
if (params.createIcon)
|
||||
this.createIcon = params.createIcon;
|
||||
this._setSizeManually = params.setSizeManually;
|
||||
|
||||
this.icon = this.createIcon(this.iconSize);
|
||||
this._iconBin.set_child(this.icon);
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
let availWidth = box.x2 - box.x1;
|
||||
let availHeight = box.y2 - box.y1;
|
||||
|
||||
let [labelMinHeight, labelNatHeight] = this._name.get_preferred_height(-1);
|
||||
let [iconMinHeight, iconNatHeight] = this._iconBin.get_preferred_height(-1);
|
||||
let preferredHeight = labelNatHeight + this._spacing + iconNatHeight;
|
||||
let labelHeight = availHeight >= preferredHeight ? labelNatHeight
|
||||
: labelMinHeight;
|
||||
let iconSize = availHeight - this._spacing - labelHeight;
|
||||
let iconPadding = (availWidth - iconSize) / 2;
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
|
||||
childBox.x1 = iconPadding;
|
||||
childBox.y1 = 0;
|
||||
childBox.x2 = availWidth - iconPadding;
|
||||
childBox.y2 = iconSize;
|
||||
this._iconBin.allocate(childBox, flags);
|
||||
|
||||
childBox.x1 = 0;
|
||||
childBox.x2 = availWidth;
|
||||
childBox.y1 = iconSize + this._spacing;
|
||||
childBox.y2 = childBox.y1 + labelHeight;
|
||||
this._name.allocate(childBox, flags);
|
||||
},
|
||||
|
||||
_getPreferredWidth: function(actor, forHeight, alloc) {
|
||||
this._getPreferredHeight(actor, -1, alloc);
|
||||
},
|
||||
|
||||
_getPreferredHeight: function(actor, forWidth, alloc) {
|
||||
let [iconMinHeight, iconNatHeight] = this._iconBin.get_preferred_height(forWidth);
|
||||
let [labelMinHeight, labelNatHeight] = this._name.get_preferred_height(forWidth);
|
||||
alloc.min_size = iconMinHeight + this._spacing + labelMinHeight;
|
||||
alloc.natural_size = iconNatHeight + this._spacing + labelNatHeight;
|
||||
},
|
||||
|
||||
// This can be overridden by a subclass, or by the createIcon
|
||||
// parameter to _init()
|
||||
createIcon: function(size) {
|
||||
throw new Error('no implementation of createIcon in ' + this);
|
||||
},
|
||||
|
||||
setIconSize: function(size) {
|
||||
if (!this._setSizeManually)
|
||||
throw new Error('setSizeManually has to be set to use setIconsize');
|
||||
|
||||
this._setIconSize(size);
|
||||
},
|
||||
|
||||
_setIconSize: function(size) {
|
||||
if (size == this.iconSize)
|
||||
return;
|
||||
|
||||
this.icon.destroy();
|
||||
this.iconSize = size;
|
||||
this.icon = this.createIcon(this.iconSize);
|
||||
this._iconBin.child = this.icon;
|
||||
},
|
||||
|
||||
_onStyleChanged: function() {
|
||||
let node = this.actor.get_theme_node();
|
||||
this._spacing = node.get_length('spacing');
|
||||
|
||||
if (this._setSizeManually)
|
||||
return;
|
||||
|
||||
let len = node.get_length('icon-size');
|
||||
if (len > 0)
|
||||
this._setIconSize(len);
|
||||
}
|
||||
};
|
||||
|
||||
function IconGrid(params) {
|
||||
this._init(params);
|
||||
}
|
||||
|
||||
IconGrid.prototype = {
|
||||
_init: function(params) {
|
||||
params = Params.parse(params, { rowLimit: null,
|
||||
columnLimit: null,
|
||||
xAlign: St.Align.MIDDLE });
|
||||
this._rowLimit = params.rowLimit;
|
||||
this._colLimit = params.columnLimit;
|
||||
this._xAlign = params.xAlign;
|
||||
|
||||
this.actor = new St.BoxLayout({ style_class: 'icon-grid',
|
||||
vertical: true });
|
||||
// Pulled from CSS, but hardcode some defaults here
|
||||
this._spacing = 0;
|
||||
this._item_size = ICON_SIZE;
|
||||
this._grid = new Shell.GenericContainer();
|
||||
this.actor.add(this._grid, { expand: true, y_align: St.Align.START });
|
||||
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
||||
|
||||
this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||
this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||
this._grid.connect('allocate', Lang.bind(this, this._allocate));
|
||||
},
|
||||
|
||||
_getPreferredWidth: function (grid, forHeight, alloc) {
|
||||
let children = this._grid.get_children();
|
||||
let nColumns = this._colLimit ? Math.min(this._colLimit,
|
||||
children.length)
|
||||
: children.length;
|
||||
let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
|
||||
// Kind of a lie, but not really an issue right now. If
|
||||
// we wanted to support some sort of hidden/overflow that would
|
||||
// need higher level design
|
||||
alloc.min_size = this._item_size;
|
||||
alloc.natural_size = nColumns * this._item_size + totalSpacing;
|
||||
},
|
||||
|
||||
_getVisibleChildren: function() {
|
||||
let children = this._grid.get_children();
|
||||
children = children.filter(function(actor) {
|
||||
return actor.visible;
|
||||
});
|
||||
return children;
|
||||
},
|
||||
|
||||
_getPreferredHeight: function (grid, forWidth, alloc) {
|
||||
let children = this._getVisibleChildren();
|
||||
let [nColumns, usedWidth] = this._computeLayout(forWidth);
|
||||
let nRows;
|
||||
if (nColumns > 0)
|
||||
nRows = Math.ceil(children.length / nColumns);
|
||||
else
|
||||
nRows = 0;
|
||||
if (this._rowLimit)
|
||||
nRows = Math.min(nRows, this._rowLimit);
|
||||
let totalSpacing = Math.max(0, nRows - 1) * this._spacing;
|
||||
let height = nRows * this._item_size + totalSpacing;
|
||||
alloc.min_size = height;
|
||||
alloc.natural_size = height;
|
||||
},
|
||||
|
||||
_allocate: function (grid, box, flags) {
|
||||
let children = this._getVisibleChildren();
|
||||
let availWidth = box.x2 - box.x1;
|
||||
let availHeight = box.y2 - box.y1;
|
||||
|
||||
let [nColumns, usedWidth] = this._computeLayout(availWidth);
|
||||
|
||||
let leftPadding;
|
||||
switch(this._xAlign) {
|
||||
case St.Align.START:
|
||||
leftPadding = 0;
|
||||
break;
|
||||
case St.Align.MIDDLE:
|
||||
leftPadding = Math.floor((availWidth - usedWidth) / 2);
|
||||
break;
|
||||
case St.Align.END:
|
||||
leftPadding = availWidth - usedWidth;
|
||||
}
|
||||
|
||||
let x = box.x1 + leftPadding;
|
||||
let y = box.y1;
|
||||
let columnIndex = 0;
|
||||
let rowIndex = 0;
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
|
||||
= children[i].get_preferred_size();
|
||||
|
||||
/* Center the item in its allocation horizontally */
|
||||
let width = Math.min(this._item_size, childNaturalWidth);
|
||||
let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
|
||||
let height = Math.min(this._item_size, childNaturalHeight);
|
||||
let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
if (St.Widget.get_default_direction() == St.TextDirection.RTL) {
|
||||
let _x = box.x2 - (x + width);
|
||||
childBox.x1 = Math.floor(_x - childXSpacing);
|
||||
} else {
|
||||
childBox.x1 = Math.floor(x + childXSpacing);
|
||||
}
|
||||
childBox.y1 = Math.floor(y + childYSpacing);
|
||||
childBox.x2 = childBox.x1 + width;
|
||||
childBox.y2 = childBox.y1 + height;
|
||||
|
||||
if (this._rowLimit && rowIndex >= this._rowLimit) {
|
||||
this._grid.set_skip_paint(children[i], true);
|
||||
} else {
|
||||
children[i].allocate(childBox, flags);
|
||||
this._grid.set_skip_paint(children[i], false);
|
||||
}
|
||||
|
||||
columnIndex++;
|
||||
if (columnIndex == nColumns) {
|
||||
columnIndex = 0;
|
||||
rowIndex++;
|
||||
}
|
||||
|
||||
if (columnIndex == 0) {
|
||||
y += this._item_size + this._spacing;
|
||||
x = box.x1 + leftPadding;
|
||||
} else {
|
||||
x += this._item_size + this._spacing;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_computeLayout: function (forWidth) {
|
||||
let children = this._grid.get_children();
|
||||
let nColumns = 0;
|
||||
let usedWidth = 0;
|
||||
while ((this._colLimit == null || nColumns < this._colLimit) &&
|
||||
(usedWidth + this._item_size <= forWidth)) {
|
||||
usedWidth += this._item_size + this._spacing;
|
||||
nColumns += 1;
|
||||
}
|
||||
|
||||
if (nColumns > 0)
|
||||
usedWidth -= this._spacing;
|
||||
|
||||
return [nColumns, usedWidth];
|
||||
},
|
||||
|
||||
_onStyleChanged: function() {
|
||||
let themeNode = this.actor.get_theme_node();
|
||||
this._spacing = themeNode.get_length('spacing');
|
||||
this._item_size = themeNode.get_length('-shell-grid-item-size');
|
||||
this._grid.queue_relayout();
|
||||
},
|
||||
|
||||
removeAll: function () {
|
||||
this._grid.get_children().forEach(Lang.bind(this, function (child) {
|
||||
child.destroy();
|
||||
}));
|
||||
},
|
||||
|
||||
addItem: function(actor) {
|
||||
this._grid.add_actor(actor);
|
||||
},
|
||||
|
||||
getItemAtIndex: function(index) {
|
||||
return this._grid.get_children()[index];
|
||||
},
|
||||
|
||||
visibleItemsCount: function() {
|
||||
return this._grid.get_children().length - this._grid.get_n_skip_paint();
|
||||
}
|
||||
};
|
@ -1,19 +1,20 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const SHADE_COLOR = new Clutter.Color();
|
||||
SHADE_COLOR.from_pixel(0x00000044);
|
||||
|
||||
/**
|
||||
* Lightbox:
|
||||
* @container: parent Clutter.Container
|
||||
* @width: (optional) shade actor width
|
||||
* @height: (optional) shade actor height
|
||||
* @params: (optional) additional parameters:
|
||||
* - inhibitEvents: whether to inhibit events for @container
|
||||
* - width: shade actor width
|
||||
* - height: shade actor height
|
||||
* - fadeTime: seconds used to fade in/out
|
||||
*
|
||||
* Lightbox creates a dark translucent "shade" actor to hide the
|
||||
* contents of @container, and allows you to specify particular actors
|
||||
@ -27,30 +28,37 @@ SHADE_COLOR.from_pixel(0x00000044);
|
||||
*
|
||||
* By default, the shade window will have the height and width of
|
||||
* @container and will track any changes in its size. You can override
|
||||
* this by passing an explicit width and height
|
||||
* this by passing an explicit width and height in @params.
|
||||
*/
|
||||
function Lightbox(container, width, height) {
|
||||
this._init(container, width, height);
|
||||
function Lightbox(container, params) {
|
||||
this._init(container, params);
|
||||
}
|
||||
|
||||
Lightbox.prototype = {
|
||||
_init : function(container, width, height) {
|
||||
_init : function(container, params) {
|
||||
params = Params.parse(params, { inhibitEvents: false,
|
||||
width: null,
|
||||
height: null,
|
||||
fadeTime: null
|
||||
});
|
||||
|
||||
this._container = container;
|
||||
this._children = container.get_children();
|
||||
this.actor = new Clutter.Rectangle({ color: SHADE_COLOR,
|
||||
x: 0,
|
||||
y: 0,
|
||||
border_width: 0,
|
||||
reactive: true });
|
||||
this._fadeTime = params.fadeTime;
|
||||
this.actor = new St.Bin({ x: 0,
|
||||
y: 0,
|
||||
style_class: 'lightbox',
|
||||
reactive: params.inhibitEvents });
|
||||
|
||||
container.add_actor(this.actor);
|
||||
this.actor.raise_top();
|
||||
this.actor.hide();
|
||||
|
||||
this._destroySignalId = this.actor.connect('destroy', Lang.bind(this, this.destroy));
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
|
||||
if (width && height) {
|
||||
this.actor.width = width;
|
||||
this.actor.height = height;
|
||||
if (params.width && params.height) {
|
||||
this.actor.width = params.width;
|
||||
this.actor.height = params.height;
|
||||
this._allocationChangedSignalId = 0;
|
||||
} else {
|
||||
this.actor.width = container.width;
|
||||
@ -65,8 +73,13 @@ Lightbox.prototype = {
|
||||
},
|
||||
|
||||
_allocationChanged : function(container, box, flags) {
|
||||
this.actor.width = this._container.width;
|
||||
this.actor.height = this._container.height;
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() {
|
||||
this.actor.width = this.width;
|
||||
this.actor.height = this.height;
|
||||
return false;
|
||||
}));
|
||||
this.width = this._container.width;
|
||||
this.height = this._container.height;
|
||||
},
|
||||
|
||||
_actorAdded : function(container, newChild) {
|
||||
@ -91,6 +104,35 @@ Lightbox.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if (this._fadeTime) {
|
||||
this.actor.opacity = 0;
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 255,
|
||||
time: this._fadeTime,
|
||||
transition: 'easeOutQuad'
|
||||
});
|
||||
} else {
|
||||
this.actor.opacity = 255;
|
||||
}
|
||||
this.actor.show();
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
if (this._fadeTime) {
|
||||
Tweener.addTween(this.actor,
|
||||
{ opacity: 0,
|
||||
time: this._fadeTime,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this, function() {
|
||||
this.actor.hide();
|
||||
})
|
||||
});
|
||||
} else {
|
||||
this.actor.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_actorRemoved : function(container, child) {
|
||||
let index = this._children.indexOf(child);
|
||||
if (index != -1) // paranoia
|
||||
@ -134,18 +176,24 @@ Lightbox.prototype = {
|
||||
/**
|
||||
* destroy:
|
||||
*
|
||||
* Destroys the lightbox. This is called automatically if the
|
||||
* lightbox's container is destroyed.
|
||||
* Destroys the lightbox.
|
||||
*/
|
||||
destroy : function() {
|
||||
this.actor.destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
* _onDestroy:
|
||||
*
|
||||
* This is called when the lightbox' actor is destroyed, either
|
||||
* by destroying its container or by explicitly calling this.destroy().
|
||||
*/
|
||||
_onDestroy: function() {
|
||||
if (this._allocationChangedSignalId != 0)
|
||||
this._container.disconnect(this._allocationChangedSignalId);
|
||||
this._container.disconnect(this._actorAddedSignalId);
|
||||
this._container.disconnect(this._actorRemovedSignalId);
|
||||
|
||||
this.actor.disconnect(this._destroySignalId);
|
||||
|
||||
this.highlight(null);
|
||||
this.actor.destroy();
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
@ -12,6 +11,7 @@ function Link(props) {
|
||||
Link.prototype = {
|
||||
_init : function(props) {
|
||||
let realProps = { reactive: true,
|
||||
track_hover: true,
|
||||
style_class: 'shell-link' };
|
||||
// The user can pass in reactive: false to override the above and get
|
||||
// a non-reactive link (a link to the current page, perhaps)
|
||||
|
@ -1,7 +1,8 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GConf = imports.gi.GConf;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Pango = imports.gi.Pango;
|
||||
const St = imports.gi.St;
|
||||
@ -18,22 +19,22 @@ const Tweener = imports.ui.tweener;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
/* Imports...feel free to add here as needed */
|
||||
var commandHeader = "const Clutter = imports.gi.Clutter; " +
|
||||
"const GLib = imports.gi.GLib; " +
|
||||
"const Gtk = imports.gi.Gtk; " +
|
||||
"const Mainloop = imports.mainloop; " +
|
||||
"const Meta = imports.gi.Meta; " +
|
||||
"const Shell = imports.gi.Shell; " +
|
||||
"const Main = imports.ui.main; " +
|
||||
"const Lang = imports.lang; " +
|
||||
"const Tweener = imports.ui.tweener; " +
|
||||
var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
|
||||
'const GLib = imports.gi.GLib; ' +
|
||||
'const Gtk = imports.gi.Gtk; ' +
|
||||
'const Mainloop = imports.mainloop; ' +
|
||||
'const Meta = imports.gi.Meta; ' +
|
||||
'const Shell = imports.gi.Shell; ' +
|
||||
'const Main = imports.ui.main; ' +
|
||||
'const Lang = imports.lang; ' +
|
||||
'const Tweener = imports.ui.tweener; ' +
|
||||
/* Utility functions...we should probably be able to use these
|
||||
* in the shell core code too. */
|
||||
"const stage = global.stage; " +
|
||||
"const color = function(pixel) { let c= new Clutter.Color(); c.from_pixel(pixel); return c; }; " +
|
||||
'const stage = global.stage; ' +
|
||||
'const color = function(pixel) { let c= new Clutter.Color(); c.from_pixel(pixel); return c; }; ' +
|
||||
/* Special lookingGlass functions */
|
||||
"const it = Main.lookingGlass.getIt(); " +
|
||||
"const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ";
|
||||
'const it = Main.lookingGlass.getIt(); ' +
|
||||
'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ';
|
||||
|
||||
function Notebook() {
|
||||
this._init();
|
||||
@ -43,14 +44,16 @@ Notebook.prototype = {
|
||||
_init: function() {
|
||||
this.actor = new St.BoxLayout({ vertical: true });
|
||||
|
||||
this.tabControls = new St.BoxLayout({ style_class: "labels" });
|
||||
this.tabControls = new St.BoxLayout({ style_class: 'labels' });
|
||||
|
||||
this._selectedIndex = -1;
|
||||
this._tabs = [];
|
||||
},
|
||||
|
||||
appendPage: function(name, child) {
|
||||
let labelBox = new St.BoxLayout({ style_class: "notebook-tab" });
|
||||
let labelBox = new St.BoxLayout({ style_class: 'notebook-tab',
|
||||
reactive: true,
|
||||
track_hover: true });
|
||||
let label = new St.Button({ label: name });
|
||||
label.connect('clicked', Lang.bind(this, function () {
|
||||
this.selectChild(child);
|
||||
@ -84,7 +87,7 @@ Notebook.prototype = {
|
||||
if (this._selectedIndex < 0)
|
||||
return;
|
||||
let tabData = this._tabs[this._selectedIndex];
|
||||
tabData.labelBox.set_style_pseudo_class(null);
|
||||
tabData.labelBox.remove_style_pseudo_class('selected');
|
||||
tabData.scrollView.hide();
|
||||
this._selectedIndex = -1;
|
||||
},
|
||||
@ -98,7 +101,7 @@ Notebook.prototype = {
|
||||
return;
|
||||
}
|
||||
let tabData = this._tabs[index];
|
||||
tabData.labelBox.set_style_pseudo_class('selected');
|
||||
tabData.labelBox.add_style_pseudo_class('selected');
|
||||
tabData.scrollView.show();
|
||||
this._selectedIndex = index;
|
||||
this.emit('selection', tabData.child);
|
||||
@ -136,9 +139,43 @@ Notebook.prototype = {
|
||||
let vAdjust = tabData.scrollView.vscroll.adjustment;
|
||||
vAdjust.value = vAdjust.upper - vAdjust.page_size;
|
||||
}
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(Notebook.prototype);
|
||||
|
||||
function objectToString(o) {
|
||||
if (typeof(o) == typeof(objectToString)) {
|
||||
// special case this since the default is way, way too verbose
|
||||
return "<js function>";
|
||||
} else {
|
||||
return "" + o;
|
||||
}
|
||||
}
|
||||
|
||||
function ObjLink(o, title) {
|
||||
this._init(o, title);
|
||||
}
|
||||
|
||||
ObjLink.prototype = {
|
||||
__proto__: Link.Link,
|
||||
|
||||
_init: function(o, title) {
|
||||
let text;
|
||||
if (title)
|
||||
text = title;
|
||||
else
|
||||
text = objectToString(o);
|
||||
text = GLib.markup_escape_text(text, -1);
|
||||
this._obj = o;
|
||||
Link.Link.prototype._init.call(this, { label: text });
|
||||
this.actor.get_child().single_line_mode = true;
|
||||
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
|
||||
},
|
||||
|
||||
_onClicked: function (link) {
|
||||
Main.lookingGlass.inspectObject(this._obj, this.actor);
|
||||
}
|
||||
};
|
||||
|
||||
function Result(command, o, index) {
|
||||
this._init(command, o, index);
|
||||
}
|
||||
@ -148,163 +185,321 @@ Result.prototype = {
|
||||
this.index = index;
|
||||
this.o = o;
|
||||
|
||||
this.actor = new Big.Box();
|
||||
this.actor = new St.BoxLayout({ vertical: true });
|
||||
|
||||
let cmdTxt = new St.Label({ text: command });
|
||||
cmdTxt.ellipsize = Pango.EllipsizeMode.END;
|
||||
|
||||
this.actor.append(cmdTxt, Big.BoxPackFlags.NONE);
|
||||
let resultTxt = new St.Label({ text: "r(" + index + ") = " + o });
|
||||
resultTxt.ellipsize = Pango.EllipsizeMode.END;
|
||||
|
||||
this.actor.append(resultTxt, Big.BoxPackFlags.NONE);
|
||||
let line = new Clutter.Rectangle({ name: "Separator",
|
||||
height: 1 });
|
||||
let padBin = new St.Bin({ name: "Separator", x_fill: true, y_fill: true });
|
||||
cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
||||
this.actor.add(cmdTxt);
|
||||
let box = new St.BoxLayout({});
|
||||
this.actor.add(box);
|
||||
let resultTxt = new St.Label({ text: 'r(' + index + ') = ' });
|
||||
resultTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
||||
box.add(resultTxt);
|
||||
let objLink = new ObjLink(o);
|
||||
box.add(objLink.actor);
|
||||
let line = new Clutter.Rectangle({ name: 'Separator' });
|
||||
let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true });
|
||||
padBin.add_actor(line);
|
||||
this.actor.append(padBin, Big.BoxPackFlags.NONE);
|
||||
this.actor.add(padBin);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function ActorHierarchy() {
|
||||
function WindowList() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
ActorHierarchy.prototype = {
|
||||
WindowList.prototype = {
|
||||
_init : function () {
|
||||
this._previousTarget = null;
|
||||
this._target = null;
|
||||
|
||||
this._parentList = [];
|
||||
|
||||
this.actor = new St.BoxLayout({ name: "ActorHierarchy", vertical: true });
|
||||
this.actor = new St.BoxLayout({ name: 'Windows', vertical: true, style: 'spacing: 8px' });
|
||||
let display = global.screen.get_display();
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
this._updateId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._updateWindowList));
|
||||
display.connect('window-created', Lang.bind(this, this._updateWindowList));
|
||||
tracker.connect('tracked-windows-changed', Lang.bind(this, this._updateWindowList));
|
||||
},
|
||||
|
||||
setTarget: function(actor) {
|
||||
this._previousTarget = this._target;
|
||||
this.target = actor;
|
||||
|
||||
this.actor.get_children().forEach(function (child) { child.destroy(); });
|
||||
|
||||
if (!(actor instanceof Clutter.Actor))
|
||||
return;
|
||||
|
||||
if (this.target == null)
|
||||
return;
|
||||
|
||||
this._parentList = [];
|
||||
let parent = actor;
|
||||
while ((parent = parent.get_parent()) != null) {
|
||||
this._parentList.push(parent);
|
||||
|
||||
let link = new St.Label({ reactive: true,
|
||||
text: "" + parent });
|
||||
this.actor.add_actor(link);
|
||||
let parentTarget = parent;
|
||||
link.connect('button-press-event', Lang.bind(this, function () {
|
||||
this._selectByActor(parentTarget);
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
this.emit('selection', actor);
|
||||
},
|
||||
|
||||
_selectByActor: function(actor) {
|
||||
let idx = this._parentList.indexOf(actor);
|
||||
let children = this.actor.get_children();
|
||||
let link = children[idx];
|
||||
this.emit('selection', actor);
|
||||
}
|
||||
}
|
||||
Signals.addSignalMethods(ActorHierarchy.prototype);
|
||||
|
||||
function PropertyInspector() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
PropertyInspector.prototype = {
|
||||
_init : function () {
|
||||
this._target = null;
|
||||
|
||||
this._parentList = [];
|
||||
|
||||
this.actor = new St.BoxLayout({ name: "PropertyInspector", vertical: true });
|
||||
},
|
||||
|
||||
setTarget: function(actor) {
|
||||
this.target = actor;
|
||||
|
||||
this.actor.get_children().forEach(function (child) { child.destroy(); });
|
||||
|
||||
for (let propName in actor) {
|
||||
let valueStr;
|
||||
try {
|
||||
valueStr = "" + actor[propName];
|
||||
} catch (e) {
|
||||
valueStr = '<error>';
|
||||
_updateWindowList: function() {
|
||||
this.actor.get_children().forEach(function (actor) { actor.destroy(); });
|
||||
let windows = global.get_window_actors();
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let metaWindow = windows[i].metaWindow;
|
||||
// Avoid multiple connections
|
||||
if (!metaWindow._lookingGlassManaged) {
|
||||
metaWindow.connect('unmanaged', Lang.bind(this, this._updateWindowList));
|
||||
metaWindow._lookingGlassManaged = true;
|
||||
}
|
||||
let box = new St.BoxLayout({ vertical: true });
|
||||
this.actor.add(box);
|
||||
let windowLink = new ObjLink(metaWindow, metaWindow.title);
|
||||
box.add(windowLink.actor, { x_align: St.Align.START, x_fill: false });
|
||||
let propsBox = new St.BoxLayout({ vertical: true, style: 'padding-left: 6px;' });
|
||||
box.add(propsBox);
|
||||
propsBox.add(new St.Label({ text: 'wmclass: ' + metaWindow.get_wm_class() }));
|
||||
let app = tracker.get_window_app(metaWindow);
|
||||
if (app != null && !app.is_transient()) {
|
||||
let icon = app.create_icon_texture(22);
|
||||
let propBox = new St.BoxLayout({ style: 'spacing: 6px; ' });
|
||||
propsBox.add(propBox);
|
||||
propBox.add(new St.Label({ text: 'app: ' }), { y_fill: false });
|
||||
let appLink = new ObjLink(app, app.get_id());
|
||||
propBox.add(appLink.actor, { y_fill: false });
|
||||
propBox.add(icon, { y_fill: false });
|
||||
} else {
|
||||
propsBox.add(new St.Label({ text: '<untracked>' }));
|
||||
}
|
||||
let propText = propName + ": " + valueStr;
|
||||
let propDisplay = new St.Label({ reactive: true,
|
||||
text: propText });
|
||||
this.actor.add_actor(propDisplay);
|
||||
}
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(WindowList.prototype);
|
||||
|
||||
function ObjInspector() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
ObjInspector.prototype = {
|
||||
_init : function () {
|
||||
this._obj = null;
|
||||
this._previousObj = null;
|
||||
|
||||
this._parentList = [];
|
||||
|
||||
this.actor = new St.ScrollView({ x_fill: true, y_fill: true });
|
||||
this.actor.get_hscroll_bar().hide();
|
||||
this._container = new St.BoxLayout({ name: 'LookingGlassPropertyInspector',
|
||||
style_class: 'lg-dialog',
|
||||
vertical: true });
|
||||
this.actor.add_actor(this._container);
|
||||
},
|
||||
|
||||
selectObject: function(obj, skipPrevious) {
|
||||
if (!skipPrevious)
|
||||
this._previousObj = this._obj;
|
||||
else
|
||||
this._previousObj = null;
|
||||
this._obj = obj;
|
||||
|
||||
this._container.get_children().forEach(function (child) { child.destroy(); });
|
||||
|
||||
let hbox = new St.BoxLayout({ style_class: 'lg-obj-inspector-title' });
|
||||
this._container.add_actor(hbox);
|
||||
let label = new St.Label({ text: 'Inspecting: %s: %s'.format(typeof(obj),
|
||||
objectToString(obj)) });
|
||||
label.single_line_mode = true;
|
||||
hbox.add(label, { expand: true, y_fill: false });
|
||||
let button = new St.Button({ label: 'Insert', style_class: 'lg-obj-inspector-button' });
|
||||
button.connect('clicked', Lang.bind(this, this._onInsert));
|
||||
hbox.add(button);
|
||||
|
||||
if (this._previousObj != null) {
|
||||
button = new St.Button({ label: 'Back', style_class: 'lg-obj-inspector-button' });
|
||||
button.connect('clicked', Lang.bind(this, this._onBack));
|
||||
hbox.add(button);
|
||||
}
|
||||
|
||||
button = new St.Button({ style_class: 'window-close' });
|
||||
button.connect('clicked', Lang.bind(this, this.close));
|
||||
hbox.add(button);
|
||||
if (typeof(obj) == typeof({})) {
|
||||
for (let propName in obj) {
|
||||
let valueStr;
|
||||
let link;
|
||||
try {
|
||||
let prop = obj[propName];
|
||||
link = new ObjLink(prop).actor;
|
||||
} catch (e) {
|
||||
link = new St.Label({ text: '<error>' });
|
||||
}
|
||||
let hbox = new St.BoxLayout();
|
||||
let propText = propName + ": " + valueStr;
|
||||
hbox.add(new St.Label({ text: propName + ': ' }));
|
||||
hbox.add(link);
|
||||
this._container.add_actor(hbox);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
open: function(sourceActor) {
|
||||
if (this._open)
|
||||
return;
|
||||
this._previousObj = null;
|
||||
this._open = true;
|
||||
this.actor.show();
|
||||
if (sourceActor) {
|
||||
this.actor.set_scale(0, 0);
|
||||
let [sourceX, sourceY] = sourceActor.get_transformed_position();
|
||||
let [sourceWidth, sourceHeight] = sourceActor.get_transformed_size();
|
||||
this.actor.move_anchor_point(Math.floor(sourceX + sourceWidth / 2),
|
||||
Math.floor(sourceY + sourceHeight / 2));
|
||||
Tweener.addTween(this.actor, { scale_x: 1, scale_y: 1,
|
||||
transition: "easeOutQuad",
|
||||
time: 0.2 });
|
||||
} else {
|
||||
this.actor.set_scale(1, 1);
|
||||
}
|
||||
},
|
||||
|
||||
close: function() {
|
||||
if (!this._open)
|
||||
return;
|
||||
this._open = false;
|
||||
this.actor.hide();
|
||||
this._previousObj = null;
|
||||
this._obj = null;
|
||||
},
|
||||
|
||||
_onInsert: function() {
|
||||
let obj = this._obj;
|
||||
this.close();
|
||||
Main.lookingGlass.insertObject(obj);
|
||||
},
|
||||
|
||||
_onBack: function() {
|
||||
this.selectObject(this._previousObj, true);
|
||||
}
|
||||
};
|
||||
|
||||
function Inspector() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
Inspector.prototype = {
|
||||
_init: function() {
|
||||
let width = 150;
|
||||
let primary = global.get_primary_monitor();
|
||||
let eventHandler = new St.BoxLayout({ name: "LookingGlassDialog",
|
||||
let container = new Shell.GenericContainer({ width: 0,
|
||||
height: 0 });
|
||||
container.connect('allocate', Lang.bind(this, this._allocate));
|
||||
Main.uiGroup.add_actor(container);
|
||||
|
||||
let eventHandler = new St.BoxLayout({ name: 'LookingGlassDialog',
|
||||
vertical: false,
|
||||
y: primary.y + Math.floor(primary.height / 2),
|
||||
reactive: true });
|
||||
eventHandler.connect('notify::allocation', Lang.bind(this, function () {
|
||||
eventHandler.x = primary.x + Math.floor((primary.width - eventHandler.width) / 2);
|
||||
}));
|
||||
global.stage.add_actor(eventHandler);
|
||||
let displayText = new St.Label();
|
||||
eventHandler.add(displayText, { expand: true });
|
||||
this._eventHandler = eventHandler;
|
||||
container.add_actor(eventHandler);
|
||||
this._displayText = new St.Label();
|
||||
eventHandler.add(this._displayText, { expand: true });
|
||||
|
||||
let borderPaintTarget = null;
|
||||
let borderPaintId = null;
|
||||
eventHandler.connect('destroy', Lang.bind(this, function() {
|
||||
if (borderPaintTarget != null)
|
||||
borderPaintTarget.disconnect(borderPaintId);
|
||||
}));
|
||||
|
||||
eventHandler.connect('button-press-event', Lang.bind(this, function (actor, event) {
|
||||
Clutter.ungrab_pointer(eventHandler);
|
||||
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
let target = global.stage.get_actor_at_pos(Clutter.PickMode.ALL,
|
||||
stageX,
|
||||
stageY);
|
||||
this.emit('target', target, stageX, stageY);
|
||||
eventHandler.destroy();
|
||||
this.emit('closed');
|
||||
return true;
|
||||
}));
|
||||
|
||||
eventHandler.connect('motion-event', Lang.bind(this, function (actor, event) {
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
let target = global.stage.get_actor_at_pos(Clutter.PickMode.ALL,
|
||||
stageX,
|
||||
stageY);
|
||||
displayText.text = '<inspect x: ' + stageX + ' y: ' + stageY + '> ' + target;
|
||||
if (borderPaintTarget != null)
|
||||
borderPaintTarget.disconnect(borderPaintId);
|
||||
borderPaintTarget = target;
|
||||
borderPaintId = Shell.add_hook_paint_red_border(target);
|
||||
return true;
|
||||
}));
|
||||
this._borderPaintTarget = null;
|
||||
this._borderPaintId = null;
|
||||
eventHandler.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
eventHandler.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
|
||||
eventHandler.connect('button-press-event', Lang.bind(this, this._onButtonPressEvent));
|
||||
eventHandler.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
||||
eventHandler.connect('motion-event', Lang.bind(this, this._onMotionEvent));
|
||||
Clutter.grab_pointer(eventHandler);
|
||||
Clutter.grab_keyboard(eventHandler);
|
||||
|
||||
// this._target is the actor currently shown by the inspector.
|
||||
// this._pointerTarget is the actor directly under the pointer.
|
||||
// Normally these are the same, but if you use the scroll wheel
|
||||
// to drill down, they'll diverge until you either scroll back
|
||||
// out, or move the pointer outside of _pointerTarget.
|
||||
this._target = null;
|
||||
this._pointerTarget = null;
|
||||
},
|
||||
|
||||
_allocate: function(actor, box, flags) {
|
||||
if (!this._eventHandler)
|
||||
return;
|
||||
|
||||
let primary = global.get_primary_monitor();
|
||||
|
||||
let [minWidth, minHeight, natWidth, natHeight] =
|
||||
this._eventHandler.get_preferred_size();
|
||||
|
||||
let childBox = new Clutter.ActorBox();
|
||||
childBox.x1 = primary.x + Math.floor((primary.width - natWidth) / 2);
|
||||
childBox.x2 = childBox.x1 + natWidth;
|
||||
childBox.y1 = primary.y + Math.floor((primary.height - natHeight) / 2);
|
||||
childBox.y2 = childBox.y1 + natHeight;
|
||||
this._eventHandler.allocate(childBox, flags);
|
||||
},
|
||||
|
||||
_close: function() {
|
||||
Clutter.ungrab_pointer(this._eventHandler);
|
||||
Clutter.ungrab_keyboard(this._eventHandler);
|
||||
this._eventHandler.destroy();
|
||||
this._eventHandler = null;
|
||||
this.emit('closed');
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
if (this._borderPaintTarget != null)
|
||||
this._borderPaintTarget.disconnect(this._borderPaintId);
|
||||
},
|
||||
|
||||
_onKeyPressEvent: function (actor, event) {
|
||||
if (event.get_key_symbol() == Clutter.Escape)
|
||||
this._close();
|
||||
return true;
|
||||
},
|
||||
|
||||
_onButtonPressEvent: function (actor, event) {
|
||||
if (this._target) {
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
this.emit('target', this._target, stageX, stageY);
|
||||
}
|
||||
this._close();
|
||||
return true;
|
||||
},
|
||||
|
||||
_onScrollEvent: function (actor, event) {
|
||||
switch (event.get_scroll_direction()) {
|
||||
case Clutter.ScrollDirection.UP:
|
||||
// select parent
|
||||
let parent = this._target.get_parent();
|
||||
if (parent != null) {
|
||||
this._target = parent;
|
||||
this._update(event);
|
||||
}
|
||||
break;
|
||||
|
||||
case Clutter.ScrollDirection.DOWN:
|
||||
// select child
|
||||
if (this._target != this._pointerTarget) {
|
||||
let child = this._pointerTarget;
|
||||
while (child) {
|
||||
let parent = child.get_parent();
|
||||
if (parent == this._target)
|
||||
break;
|
||||
child = parent;
|
||||
}
|
||||
if (child) {
|
||||
this._target = child;
|
||||
this._update(event);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
_onMotionEvent: function (actor, event) {
|
||||
this._update(event);
|
||||
return true;
|
||||
},
|
||||
|
||||
_update: function(event) {
|
||||
let [stageX, stageY] = event.get_coords();
|
||||
let target = global.stage.get_actor_at_pos(Clutter.PickMode.ALL,
|
||||
stageX,
|
||||
stageY);
|
||||
|
||||
if (target != this._pointerTarget)
|
||||
this._target = target;
|
||||
this._pointerTarget = target;
|
||||
|
||||
let position = '[inspect x: ' + stageX + ' y: ' + stageY + ']';
|
||||
this._displayText.text = '';
|
||||
this._displayText.text = position + ' ' + this._target;
|
||||
if (this._borderPaintTarget != null)
|
||||
this._borderPaintTarget.disconnect(this._borderPaintId);
|
||||
this._borderPaintTarget = this._target;
|
||||
this._borderPaintId = Shell.add_hook_paint_red_border(this._target);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(Inspector.prototype);
|
||||
|
||||
@ -317,18 +512,23 @@ ErrorLog.prototype = {
|
||||
this.actor = new St.BoxLayout();
|
||||
this.text = new St.Label();
|
||||
this.actor.add(this.text);
|
||||
// We need to override StLabel's default ellipsization when
|
||||
// using line_wrap; otherwise ClutterText's layout is going
|
||||
// to constrain both the width and height, which prevents
|
||||
// scrolling.
|
||||
this.text.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
||||
this.text.clutter_text.line_wrap = true;
|
||||
this.actor.connect('notify::mapped', Lang.bind(this, this._renderText));
|
||||
},
|
||||
|
||||
_formatTime: function(d){
|
||||
function pad(n) { return n < 10 ? '0' + n : n };
|
||||
function pad(n) { return n < 10 ? '0' + n : n; }
|
||||
return d.getUTCFullYear()+'-'
|
||||
+ pad(d.getUTCMonth()+1)+'-'
|
||||
+ pad(d.getUTCDate())+'T'
|
||||
+ pad(d.getUTCHours())+':'
|
||||
+ pad(d.getUTCMinutes())+':'
|
||||
+ pad(d.getUTCSeconds())+'Z'
|
||||
+ pad(d.getUTCSeconds())+'Z';
|
||||
},
|
||||
|
||||
_renderText: function() {
|
||||
@ -338,11 +538,11 @@ ErrorLog.prototype = {
|
||||
let stack = Main._getAndClearErrorStack();
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
let logItem = stack[i];
|
||||
text += logItem.category + " t=" + this._formatTime(new Date(logItem.timestamp)) + " " + logItem.message + "\n";
|
||||
text += logItem.category + ' t=' + this._formatTime(new Date(logItem.timestamp)) + ' ' + logItem.message + '\n';
|
||||
}
|
||||
this.text.text = text;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function Extensions() {
|
||||
this._init();
|
||||
@ -398,7 +598,7 @@ Extensions.prototype = {
|
||||
case ExtensionSystem.ExtensionState.OUT_OF_DATE:
|
||||
return _("Out of date");
|
||||
}
|
||||
return "Unknown"; // Not translated, shouldn't appear
|
||||
return 'Unknown'; // Not translated, shouldn't appear
|
||||
},
|
||||
|
||||
_createExtensionDisplay: function(meta) {
|
||||
@ -444,7 +644,7 @@ function LookingGlass() {
|
||||
LookingGlass.prototype = {
|
||||
_init : function() {
|
||||
this._idleHistorySaveId = 0;
|
||||
let historyPath = global.configdir + "/lookingglass-history.txt";
|
||||
let historyPath = global.userdatadir + '/lookingglass-history.txt';
|
||||
this._historyFile = Gio.file_new_for_path(historyPath);
|
||||
this._savedText = null;
|
||||
this._historyNavIndex = -1;
|
||||
@ -463,22 +663,27 @@ LookingGlass.prototype = {
|
||||
// Sort of magic, but...eh.
|
||||
this._maxItems = 150;
|
||||
|
||||
this.actor = new St.BoxLayout({ name: "LookingGlassDialog",
|
||||
this.actor = new St.BoxLayout({ name: 'LookingGlassDialog',
|
||||
style_class: 'lg-dialog',
|
||||
vertical: true,
|
||||
visible: false });
|
||||
this.actor.connect('key-press-event', Lang.bind(this, this._globalKeyPressEvent));
|
||||
|
||||
let gconf = Shell.GConf.get_default();
|
||||
gconf.watch_directory("/desktop/gnome/interface");
|
||||
gconf.connect("changed::/desktop/gnome/interface/monospace_font_name",
|
||||
Lang.bind(this, this._updateFont));
|
||||
this._interfaceSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
|
||||
this._interfaceSettings.connect('changed::monospace-font-name',
|
||||
Lang.bind(this, this._updateFont));
|
||||
this._updateFont();
|
||||
|
||||
global.stage.add_actor(this.actor);
|
||||
Main.uiGroup.add_actor(this.actor);
|
||||
|
||||
let toolbar = new St.BoxLayout({ name: "Toolbar" });
|
||||
this._objInspector = new ObjInspector();
|
||||
Main.uiGroup.add_actor(this._objInspector.actor);
|
||||
this._objInspector.actor.hide();
|
||||
|
||||
let toolbar = new St.BoxLayout({ name: 'Toolbar' });
|
||||
this.actor.add_actor(toolbar);
|
||||
let inspectIcon = Shell.TextureCache.get_default().load_gicon(new Gio.ThemedIcon({ name: 'gtk-color-picker' }),
|
||||
24);
|
||||
let inspectIcon = new St.Icon({ icon_name: 'gtk-color-picker',
|
||||
icon_size: 24 });
|
||||
toolbar.add_actor(inspectIcon);
|
||||
inspectIcon.reactive = true;
|
||||
inspectIcon.connect('button-press-event', Lang.bind(this, function () {
|
||||
@ -486,7 +691,6 @@ LookingGlass.prototype = {
|
||||
inspector.connect('target', Lang.bind(this, function(i, target, stageX, stageY) {
|
||||
this._pushResult('<inspect x:' + stageX + ' y:' + stageY + '>',
|
||||
target);
|
||||
this._hierarchy.setTarget(target);
|
||||
}));
|
||||
inspector.connect('closed', Lang.bind(this, function() {
|
||||
this.actor.show();
|
||||
@ -504,18 +708,17 @@ LookingGlass.prototype = {
|
||||
toolbar.add(emptyBox, { expand: true });
|
||||
toolbar.add_actor(notebook.tabControls);
|
||||
|
||||
this._evalBox = new St.BoxLayout({ name: "EvalBox", vertical: true });
|
||||
this._evalBox = new St.BoxLayout({ name: 'EvalBox', vertical: true });
|
||||
notebook.appendPage('Evaluator', this._evalBox);
|
||||
|
||||
this._resultsArea = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
spacing: 4 });
|
||||
this._resultsArea = new St.BoxLayout({ name: 'ResultsArea', vertical: true });
|
||||
this._evalBox.add(this._resultsArea, { expand: true });
|
||||
|
||||
let entryArea = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL });
|
||||
let entryArea = new St.BoxLayout({ name: 'EntryArea' });
|
||||
this._evalBox.add_actor(entryArea);
|
||||
|
||||
let label = new St.Label({ text: 'js>>> ' });
|
||||
entryArea.append(label, Big.BoxPackFlags.NONE);
|
||||
entryArea.add(label);
|
||||
|
||||
this._entry = new St.Entry();
|
||||
/* unmapping the edit box will un-focus it, undo that */
|
||||
@ -523,17 +726,14 @@ LookingGlass.prototype = {
|
||||
if (child == this._evalBox)
|
||||
global.stage.set_key_focus(this._entry);
|
||||
}));
|
||||
entryArea.append(this._entry, Big.BoxPackFlags.EXPAND);
|
||||
entryArea.add(this._entry, { expand: true });
|
||||
|
||||
this._hierarchy = new ActorHierarchy();
|
||||
notebook.appendPage('Hierarchy', this._hierarchy.actor);
|
||||
|
||||
this._propInspector = new PropertyInspector();
|
||||
notebook.appendPage('Properties', this._propInspector.actor);
|
||||
this._hierarchy.connect('selection', Lang.bind(this, function (h, actor) {
|
||||
this._pushResult('<parent selection>', actor);
|
||||
this._windowList = new WindowList();
|
||||
this._windowList.connect('selected', Lang.bind(this, function(list, window) {
|
||||
notebook.selectIndex(0);
|
||||
this._pushResult('<window selection>', window);
|
||||
}));
|
||||
notebook.appendPage('Windows', this._windowList.actor);
|
||||
|
||||
this._errorLog = new ErrorLog();
|
||||
notebook.appendPage('Errors', this._errorLog.actor);
|
||||
@ -547,7 +747,7 @@ LookingGlass.prototype = {
|
||||
// newline-separated.
|
||||
text.replace('\n', ' ');
|
||||
// Strip leading and trailing whitespace
|
||||
text = text.replace(/^\s+/g, "").replace(/\s+$/g, "");
|
||||
text = text.replace(/^\s+/g, '').replace(/\s+$/g, '');
|
||||
if (text == '')
|
||||
return true;
|
||||
this._evaluate(text);
|
||||
@ -582,11 +782,10 @@ LookingGlass.prototype = {
|
||||
},
|
||||
|
||||
_updateFont: function() {
|
||||
let gconf = Shell.GConf.get_default();
|
||||
let fontName = gconf.get_string("/desktop/gnome/interface/monospace_font_name");
|
||||
let fontName = this._interfaceSettings.get_string('monospace-font-name');
|
||||
// This is mishandled by the scanner - should by Pango.FontDescription_from_string(fontName);
|
||||
// https://bugzilla.gnome.org/show_bug.cgi?id=595889
|
||||
let fontDesc = Pango.Font.description_from_string(fontName);
|
||||
let fontDesc = Pango.font_description_from_string(fontName);
|
||||
// We ignore everything but size and style; you'd be crazy to set your system-wide
|
||||
// monospace font to be bold/oblique/etc. Could easily be added here.
|
||||
this.actor.style =
|
||||
@ -621,8 +820,7 @@ LookingGlass.prototype = {
|
||||
let index = this._results.length + this._offset;
|
||||
let result = new Result('>>> ' + command, obj, index);
|
||||
this._results.push(result);
|
||||
this._resultsArea.append(result.actor, Big.BoxPackFlags.NONE);
|
||||
this._propInspector.setTarget(obj);
|
||||
this._resultsArea.add(result.actor);
|
||||
if (this._borderPaintTarget != null) {
|
||||
this._borderPaintTarget.disconnect(this._borderPaintId);
|
||||
this._borderPaintTarget = null;
|
||||
@ -657,11 +855,10 @@ LookingGlass.prototype = {
|
||||
try {
|
||||
resultObj = eval(fullCmd);
|
||||
} catch (e) {
|
||||
resultObj = "<exception " + e + ">";
|
||||
resultObj = '<exception ' + e + '>';
|
||||
}
|
||||
|
||||
this._pushResult(command, resultObj);
|
||||
this._hierarchy.setTarget(null);
|
||||
this._entry.text = '';
|
||||
},
|
||||
|
||||
@ -691,6 +888,9 @@ LookingGlass.prototype = {
|
||||
this.actor.y = this._hiddenY;
|
||||
this.actor.width = myWidth;
|
||||
this.actor.height = myHeight;
|
||||
this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8));
|
||||
this._objInspector.actor.set_position(this.actor.x + Math.floor(myWidth * 0.1),
|
||||
this._targetY + Math.floor(myHeight * 0.1));
|
||||
},
|
||||
|
||||
slaveTo: function(actor) {
|
||||
@ -701,11 +901,24 @@ LookingGlass.prototype = {
|
||||
this._resizeTo(actor);
|
||||
},
|
||||
|
||||
insertObject: function(obj) {
|
||||
this._pushResult('<insert>', obj);
|
||||
},
|
||||
|
||||
inspectObject: function(obj, sourceActor) {
|
||||
this._objInspector.open(sourceActor);
|
||||
this._objInspector.selectObject(obj);
|
||||
},
|
||||
|
||||
// Handle key events which are relevant for all tabs of the LookingGlass
|
||||
_globalKeyPressEvent : function(actor, event) {
|
||||
let symbol = event.get_key_symbol();
|
||||
if (symbol == Clutter.Escape) {
|
||||
this.close();
|
||||
if (this._objInspector.actor.visible) {
|
||||
this._objInspector.close();
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -715,22 +928,19 @@ LookingGlass.prototype = {
|
||||
if (this._open)
|
||||
return;
|
||||
|
||||
if (!Main.pushModal(this.actor))
|
||||
if (!Main.pushModal(this._entry))
|
||||
return;
|
||||
|
||||
this._keyPressEventId = global.stage.connect('key-press-event',
|
||||
Lang.bind(this, this._globalKeyPressEvent));
|
||||
|
||||
this.actor.show();
|
||||
this.actor.lower(Main.chrome.actor);
|
||||
this._open = true;
|
||||
|
||||
Tweener.removeTweens(this.actor);
|
||||
|
||||
global.stage.set_key_focus(this._entry);
|
||||
|
||||
Tweener.addTween(this.actor, { time: 0.5,
|
||||
transition: "easeOutQuad",
|
||||
// We inverse compensate for the slow-down so you can change the factor
|
||||
// through LookingGlass without long waits.
|
||||
Tweener.addTween(this.actor, { time: 0.5 / St.get_slow_down_factor(),
|
||||
transition: 'easeOutQuad',
|
||||
y: this._targetY
|
||||
});
|
||||
},
|
||||
@ -739,8 +949,7 @@ LookingGlass.prototype = {
|
||||
if (!this._open)
|
||||
return;
|
||||
|
||||
if (this._keyPressEventId)
|
||||
global.stage.disconnect(this._keyPressEventId);
|
||||
this._objInspector.actor.hide();
|
||||
|
||||
this._historyNavIndex = -1;
|
||||
this._open = false;
|
||||
@ -752,10 +961,10 @@ LookingGlass.prototype = {
|
||||
this._borderPaintTarget = null;
|
||||
}
|
||||
|
||||
Main.popModal(this.actor);
|
||||
Main.popModal(this._entry);
|
||||
|
||||
Tweener.addTween(this.actor, { time: 0.5,
|
||||
transition: "easeOutQuad",
|
||||
Tweener.addTween(this.actor, { time: 0.5 / St.get_slow_down_factor(),
|
||||
transition: 'easeOutQuad',
|
||||
y: this._hiddenY,
|
||||
onComplete: Lang.bind(this, function () {
|
||||
this.actor.hide();
|
||||
|
1425
js/ui/magnifier.js
Normal file
383
js/ui/magnifierDBus.js
Normal file
@ -0,0 +1,383 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const MAG_SERVICE_NAME = 'org.gnome.Magnifier';
|
||||
const MAG_SERVICE_PATH = '/org/gnome/Magnifier';
|
||||
const ZOOM_SERVICE_NAME = 'org.gnome.Magnifier.ZoomRegion';
|
||||
const ZOOM_SERVICE_PATH = '/org/gnome/Magnifier/ZoomRegion';
|
||||
|
||||
// Subset of gnome-mag's Magnifier dbus interface -- to be expanded. See:
|
||||
// http://git.gnome.org/browse/gnome-mag/tree/xml/...Magnifier.xml
|
||||
const MagnifierIface = {
|
||||
name: MAG_SERVICE_NAME,
|
||||
methods: [
|
||||
{ name: 'setActive', inSignature: 'b', outSignature: '' },
|
||||
{ name: 'isActive', inSignature: '', outSignature: 'b' },
|
||||
{ name: 'showCursor', inSignature: '', outSignature: '' },
|
||||
{ name: 'hideCursor', inSignature: '', outSignature: '' },
|
||||
{ name: 'createZoomRegion', inSignature: 'ddaiai', outSignature: 'o' },
|
||||
{ name: 'addZoomRegion', inSignature: 'o', outSignature: 'b' },
|
||||
{ name: 'getZoomRegions', inSignature: '', outSignature: 'ao' },
|
||||
{ name: 'clearAllZoomRegions', inSignature: '', outSignature: '' },
|
||||
{ name: 'fullScreenCapable', inSignature: '', outSignature: 'b' },
|
||||
|
||||
{ name: 'setCrosswireSize', inSignature: 'i', outSignature: '' },
|
||||
{ name: 'getCrosswireSize', inSignature: '', outSignature: 'i' },
|
||||
{ name: 'setCrosswireLength', inSignature: 'i', outSignature: '' },
|
||||
{ name: 'getCrosswireLength', inSignature: '', outSignature: 'i' },
|
||||
{ name: 'setCrosswireClip', inSignature: 'b', outSignature: '' },
|
||||
{ name: 'getCrosswireClip', inSignature: '', outSignature: 'b' },
|
||||
{ name: 'setCrosswireColor', inSignature: 'u', outSignature: '' },
|
||||
{ name: 'getCrosswireColor', inSignature: '', outSignature: 'u' }
|
||||
],
|
||||
signals: [],
|
||||
properties: []
|
||||
};
|
||||
|
||||
// Subset of gnome-mag's ZoomRegion dbus interface -- to be expanded. See:
|
||||
// http://git.gnome.org/browse/gnome-mag/tree/xml/...ZoomRegion.xml
|
||||
const ZoomRegionIface = {
|
||||
name: ZOOM_SERVICE_NAME,
|
||||
methods: [
|
||||
{ name: 'setMagFactor', inSignature: 'dd', outSignature: ''},
|
||||
{ name: 'getMagFactor', inSignature: '', outSignature: 'dd' },
|
||||
{ name: 'setRoi', inSignature: 'ai', outSignature: '' },
|
||||
{ name: 'getRoi', inSignature: '', outSignature: 'ai' },
|
||||
{ name: 'shiftContentsTo', inSignature: 'ii', outSignature: 'b' },
|
||||
{ name: 'moveResize', inSignature: 'ai', outSignature: '' }
|
||||
],
|
||||
signals: [],
|
||||
properties: []
|
||||
};
|
||||
|
||||
// For making unique ZoomRegion DBus proxy object paths of the form:
|
||||
// '/org/gnome/Magnifier/ZoomRegion/zoomer0',
|
||||
// '/org/gnome/Magnifier/ZoomRegion/zoomer1', etc.
|
||||
let _zoomRegionInstanceCount = 0;
|
||||
|
||||
function ShellMagnifier() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
ShellMagnifier.prototype = {
|
||||
_init: function() {
|
||||
this._zoomers = {};
|
||||
DBus.session.exportObject(MAG_SERVICE_PATH, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* setActive:
|
||||
* @activate: Boolean to activate or de-activate the magnifier.
|
||||
*/
|
||||
setActive: function(activate) {
|
||||
Main.magnifier.setActive(activate);
|
||||
},
|
||||
|
||||
/**
|
||||
* isActive:
|
||||
* @return Whether the magnifier is active (boolean).
|
||||
*/
|
||||
isActive: function() {
|
||||
return Main.magnifier.isActive();
|
||||
},
|
||||
|
||||
/**
|
||||
* showCursor:
|
||||
* Show the system mouse pointer.
|
||||
*/
|
||||
showCursor: function() {
|
||||
Main.magnifier.showSystemCursor();
|
||||
},
|
||||
|
||||
/**
|
||||
* hideCursor:
|
||||
* Hide the system mouse pointer.
|
||||
*/
|
||||
hideCursor: function() {
|
||||
Main.magnifier.hideSystemCursor();
|
||||
},
|
||||
|
||||
/**
|
||||
* createZoomRegion:
|
||||
* Create a new ZoomRegion and return its object path.
|
||||
* @xMagFactor: The power to set horizontal magnification of the
|
||||
* ZoomRegion. A value of 1.0 means no magnification. A
|
||||
* value of 2.0 doubles the size.
|
||||
* @yMagFactor: The power to set the vertical magnification of the
|
||||
* ZoomRegion.
|
||||
* @roi Array of integers defining the region of the
|
||||
* screen/desktop to magnify. The array has the form
|
||||
* [left, top, right, bottom].
|
||||
* @viewPort Array of integers, [left, top, right, bottom] that defines
|
||||
* the position of the ZoomRegion on screen.
|
||||
*
|
||||
* FIXME: The arguments here are redundant, since the width and height of
|
||||
* the ROI are determined by the viewport and magnification factors.
|
||||
* We ignore the passed in width and height.
|
||||
*
|
||||
* @return The newly created ZoomRegion.
|
||||
*/
|
||||
createZoomRegion: function(xMagFactor, yMagFactor, roi, viewPort) {
|
||||
let ROI = { x: roi[0], y: roi[1], width: roi[2] - roi[0], height: roi[3] - roi[1] };
|
||||
let viewBox = { x: viewPort[0], y: viewPort[1], width: viewPort[2] - viewPort[0], height: viewPort[3] - viewPort[1] };
|
||||
let realZoomRegion = Main.magnifier.createZoomRegion(xMagFactor, yMagFactor, ROI, viewBox);
|
||||
let objectPath = ZOOM_SERVICE_PATH + '/zoomer' + _zoomRegionInstanceCount;
|
||||
_zoomRegionInstanceCount++;
|
||||
|
||||
let zoomRegionProxy = new ShellMagnifierZoomRegion(objectPath, realZoomRegion);
|
||||
let proxyAndZoomRegion = {};
|
||||
proxyAndZoomRegion.proxy = zoomRegionProxy;
|
||||
proxyAndZoomRegion.zoomRegion = realZoomRegion;
|
||||
this._zoomers[objectPath] = proxyAndZoomRegion;
|
||||
return objectPath;
|
||||
},
|
||||
|
||||
/**
|
||||
* addZoomRegion:
|
||||
* Append the given ZoomRegion to the magnifier's list of ZoomRegions.
|
||||
* @zoomerObjectPath: The object path for the zoom region proxy.
|
||||
*/
|
||||
addZoomRegion: function(zoomerObjectPath) {
|
||||
let proxyAndZoomRegion = this._zoomers[zoomerObjectPath];
|
||||
if (proxyAndZoomRegion && proxyAndZoomRegion.zoomRegion) {
|
||||
Main.magnifier.addZoomRegion(proxyAndZoomRegion.zoomRegion);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* getZoomRegions:
|
||||
* Return a list of ZoomRegion object paths for this Magnifier.
|
||||
* @return: The Magnifier's zoom region list as an array of DBus object
|
||||
* paths.
|
||||
*/
|
||||
getZoomRegions: function() {
|
||||
// There may be more ZoomRegions in the magnifier itself than have
|
||||
// been added through dbus. Make sure all of them are associated with
|
||||
// an object path and proxy.
|
||||
let zoomRegions = Main.magnifier.getZoomRegions();
|
||||
let objectPaths = [];
|
||||
let thoseZoomers = this._zoomers;
|
||||
zoomRegions.forEach (function(aZoomRegion, index, array) {
|
||||
let found = false;
|
||||
for (let objectPath in thoseZoomers) {
|
||||
let proxyAndZoomRegion = thoseZoomers[objectPath];
|
||||
if (proxyAndZoomRegion.zoomRegion === aZoomRegion) {
|
||||
objectPaths.push(objectPath);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// Got a ZoomRegion with no DBus proxy, make one.
|
||||
let newPath = ZOOM_SERVICE_PATH + '/zoomer' + _zoomRegionInstanceCount;
|
||||
_zoomRegionInstanceCount++;
|
||||
let zoomRegionProxy = new ShellMagnifierZoomRegion(newPath, aZoomRegion);
|
||||
let proxyAndZoomer = {};
|
||||
proxyAndZoomer.proxy = zoomRegionProxy;
|
||||
proxyAndZoomer.zoomRegion = aZoomRegion;
|
||||
thoseZoomers[newPath] = proxyAndZoomer;
|
||||
objectPaths.push(newPath);
|
||||
}
|
||||
});
|
||||
return objectPaths;
|
||||
},
|
||||
|
||||
/**
|
||||
* clearAllZoomRegions:
|
||||
* Remove all the zoom regions from this Magnfier's ZoomRegion list.
|
||||
*/
|
||||
clearAllZoomRegions: function() {
|
||||
Main.magnifier.clearAllZoomRegions();
|
||||
for (let objectPath in this._zoomers) {
|
||||
let proxyAndZoomer = this._zoomers[objectPath];
|
||||
proxyAndZoomer.proxy = null;
|
||||
proxyAndZoomer.zoomRegion = null;
|
||||
delete this._zoomers[objectPath];
|
||||
DBus.session.unexportObject(proxyAndZoomer);
|
||||
}
|
||||
this._zoomers = {};
|
||||
},
|
||||
|
||||
/**
|
||||
* fullScreenCapable:
|
||||
* Consult if the Magnifier can magnify in full-screen mode.
|
||||
* @return Always return true.
|
||||
*/
|
||||
fullScreenCapable: function() {
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* setCrosswireSize:
|
||||
* Set the crosswire size of all ZoomRegions.
|
||||
* @size: The thickness of each line in the cross wire.
|
||||
*/
|
||||
setCrosswireSize: function(size) {
|
||||
Main.magnifier.setCrosshairsThickness(size);
|
||||
},
|
||||
|
||||
/**
|
||||
* getCrosswireSize:
|
||||
* Get the crosswire size of all ZoomRegions.
|
||||
* @return: The thickness of each line in the cross wire.
|
||||
*/
|
||||
getCrosswireSize: function() {
|
||||
return Main.magnifier.getCrosshairsThickness();
|
||||
},
|
||||
|
||||
/**
|
||||
* setCrosswireLength:
|
||||
* Set the crosswire length of all zoom-regions..
|
||||
* @size: The length of each line in the cross wire.
|
||||
*/
|
||||
setCrosswireLength: function(length) {
|
||||
Main.magnifier.setCrosshairsLength(length);
|
||||
},
|
||||
|
||||
/**
|
||||
* setCrosswireSize:
|
||||
* Set the crosswire size of all zoom-regions.
|
||||
* @size: The thickness of each line in the cross wire.
|
||||
*/
|
||||
getCrosswireLength: function() {
|
||||
return Main.magnifier.getCrosshairsLength();
|
||||
},
|
||||
|
||||
/**
|
||||
* setCrosswireClip:
|
||||
* Set if the crosswire will be clipped by the cursor image..
|
||||
* @clip: Flag to indicate whether to clip the crosswire.
|
||||
*/
|
||||
setCrosswireClip: function(clip) {
|
||||
Main.magnifier.setCrosshairsClip(clip);
|
||||
},
|
||||
|
||||
/**
|
||||
* getCrosswireClip:
|
||||
* Get the crosswire clip value.
|
||||
* @return: Whether the crosswire is clipped by the cursor image.
|
||||
*/
|
||||
getCrosswireClip: function() {
|
||||
return Main.magnifier.getCrosshairsClip();
|
||||
},
|
||||
|
||||
/**
|
||||
* setCrosswireColor:
|
||||
* Set the crosswire color of all ZoomRegions.
|
||||
* @color: Unsigned int of the form rrggbbaa.
|
||||
*/
|
||||
setCrosswireColor: function(color) {
|
||||
Main.magnifier.setCrosshairsColor('#%08x'.format(color));
|
||||
},
|
||||
|
||||
/**
|
||||
* getCrosswireClip:
|
||||
* Get the crosswire color of all ZoomRegions.
|
||||
* @return: The crosswire color as an unsigned int in the form rrggbbaa.
|
||||
*/
|
||||
getCrosswireColor: function() {
|
||||
let colorString = Main.magnifier.getCrosshairsColor();
|
||||
// Drop the leading '#'.
|
||||
return parseInt(colorString.slice(1), 16);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* ShellMagnifierZoomRegion:
|
||||
* Object that implements the DBus ZoomRegion interface.
|
||||
* @zoomerObjectPath: String that is the path to a DBus ZoomRegion.
|
||||
* @zoomRegion: The actual zoom region associated with the object path.
|
||||
*/
|
||||
function ShellMagnifierZoomRegion(zoomerObjectPath, zoomRegion) {
|
||||
this._init(zoomerObjectPath, zoomRegion);
|
||||
}
|
||||
|
||||
ShellMagnifierZoomRegion.prototype = {
|
||||
_init: function(zoomerObjectPath, zoomRegion) {
|
||||
this._zoomRegion = zoomRegion;
|
||||
DBus.session.proxifyObject(this, ZOOM_SERVICE_NAME, zoomerObjectPath);
|
||||
DBus.session.exportObject(zoomerObjectPath, this);
|
||||
},
|
||||
|
||||
/**
|
||||
* setMagFactor:
|
||||
* @xMagFactor: The power to set the horizontal magnification factor to
|
||||
* of the magnified view. A value of 1.0 means no
|
||||
* magnification. A value of 2.0 doubles the size.
|
||||
* @yMagFactor: The power to set the vertical magnification factor to
|
||||
* of the magnified view.
|
||||
*/
|
||||
setMagFactor: function(xMagFactor, yMagFactor) {
|
||||
this._zoomRegion.setMagFactor(xMagFactor, yMagFactor);
|
||||
},
|
||||
|
||||
/**
|
||||
* getMagFactor:
|
||||
* @return an array, [xMagFactor, yMagFactor], containing the horizontal
|
||||
* and vertical magnification powers. A value of 1.0 means no
|
||||
* magnification. A value of 2.0 means the contents are doubled
|
||||
* in size, and so on.
|
||||
*/
|
||||
getMagFactor: function() {
|
||||
return this._zoomRegion.getMagFactor();
|
||||
},
|
||||
|
||||
/**
|
||||
* setRoi:
|
||||
* Sets the "region of interest" that the ZoomRegion is magnifying.
|
||||
* @roi Array, [left, top, right, bottom], defining the region of the
|
||||
* screen to magnify. The values are in screen (unmagnified)
|
||||
* coordinate space.
|
||||
*/
|
||||
setRoi: function(roi) {
|
||||
let roiObject = { x: roi[0], y: roi[1], width: roi[2] - roi[0], height: roi[3] - roi[1] };
|
||||
this._zoomRegion.setROI(roiObject);
|
||||
},
|
||||
|
||||
/**
|
||||
* getRoi:
|
||||
* Retrieves the "region of interest" -- the rectangular bounds of that part
|
||||
* of the desktop that the magnified view is showing (x, y, width, height).
|
||||
* The bounds are given in non-magnified coordinates.
|
||||
* @return an array, [left, top, right, bottom], representing the bounding
|
||||
* rectangle of what is shown in the magnified view.
|
||||
*/
|
||||
getRoi: function() {
|
||||
let roi = this._zoomRegion.getROI();
|
||||
roi[2] += roi[0];
|
||||
roi[3] += roi[1];
|
||||
return roi;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the "region of interest" by centering the given screen coordinate
|
||||
* within the zoom region.
|
||||
* @x The x-coord of the point to place at the center of the zoom region.
|
||||
* @y The y-coord.
|
||||
* @return Whether the shift was successful (for GS-mag, this is always
|
||||
* true).
|
||||
*/
|
||||
shiftContentsTo: function(x, y) {
|
||||
this._zoomRegion.scrollContentsTo(x, y);
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* moveResize
|
||||
* Sets the position and size of the ZoomRegion on screen.
|
||||
* @viewPort Array, [left, top, right, bottom], defining the position and
|
||||
* size on screen to place the zoom region.
|
||||
*/
|
||||
moveResize: function(viewPort) {
|
||||
let viewRect = { x: viewPort[0], y: viewPort[1], width: viewPort[2] - viewPort[0], height: viewPort[3] - viewPort[1] };
|
||||
this._zoomRegion.setViewPort(viewRect);
|
||||
}
|
||||
};
|
||||
|
||||
DBus.conformExport(ShellMagnifier.prototype, MagnifierIface);
|
||||
DBus.conformExport(ShellMagnifierZoomRegion.prototype, ZoomRegionIface);
|
426
js/ui/main.js
@ -1,18 +1,28 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
imports.gi.versions.Clutter = '1.0';
|
||||
imports.gi.versions.Gio = '2.0';
|
||||
imports.gi.versions.Gdk = '3.0';
|
||||
imports.gi.versions.GdkPixbuf = '2.0';
|
||||
imports.gi.versions.Gtk = '3.0';
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const DBus = imports.dbus;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const GConf = imports.gi.GConf;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Chrome = imports.ui.chrome;
|
||||
const CtrlAltTab = imports.ui.ctrlAltTab;
|
||||
const EndSessionDialog = imports.ui.endSessionDialog;
|
||||
const Environment = imports.ui.environment;
|
||||
const ExtensionSystem = imports.ui.extensionSystem;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
@ -22,35 +32,50 @@ const PlaceDisplay = imports.ui.placeDisplay;
|
||||
const RunDialog = imports.ui.runDialog;
|
||||
const LookingGlass = imports.ui.lookingGlass;
|
||||
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
||||
const Scripting = imports.ui.scripting;
|
||||
const ShellDBus = imports.ui.shellDBus;
|
||||
const Sidebar = imports.ui.sidebar;
|
||||
const TelepathyClient = imports.ui.telepathyClient;
|
||||
const WindowManager = imports.ui.windowManager;
|
||||
const Magnifier = imports.ui.magnifier;
|
||||
const XdndHandler = imports.ui.xdndHandler;
|
||||
const StatusIconDispatcher = imports.ui.statusIconDispatcher;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const DEFAULT_BACKGROUND_COLOR = new Clutter.Color();
|
||||
DEFAULT_BACKGROUND_COLOR.from_pixel(0x2266bbff);
|
||||
|
||||
let chrome = null;
|
||||
let panel = null;
|
||||
let sidebar = null;
|
||||
let placesManager = null;
|
||||
let overview = null;
|
||||
let runDialog = null;
|
||||
let lookingGlass = null;
|
||||
let wm = null;
|
||||
let notificationDaemon = null;
|
||||
let notificationPopup = null;
|
||||
let messageTray = null;
|
||||
let notificationDaemon = null;
|
||||
let windowAttentionHandler = null;
|
||||
let telepathyClient = null;
|
||||
let ctrlAltTabManager = null;
|
||||
let recorder = null;
|
||||
let shellDBusService = null;
|
||||
let modalCount = 0;
|
||||
let modalActorFocusStack = [];
|
||||
let uiGroup = null;
|
||||
let magnifier = null;
|
||||
let xdndHandler = null;
|
||||
let statusIconDispatcher = null;
|
||||
let _errorLogStack = [];
|
||||
let _startDate;
|
||||
let _defaultCssStylesheet = null;
|
||||
let _cssStylesheet = null;
|
||||
|
||||
let background = null;
|
||||
|
||||
function start() {
|
||||
// Add a binding for "global" in the global JS namespace; (gjs
|
||||
// Add a binding for 'global' in the global JS namespace; (gjs
|
||||
// keeps the web browser convention of having that namespace be
|
||||
// called "window".)
|
||||
// called 'window'.)
|
||||
window.global = Shell.Global.get();
|
||||
|
||||
// Now monkey patch utility functions into the global proxy;
|
||||
@ -59,7 +84,7 @@ function start() {
|
||||
global.logError = _logError;
|
||||
global.log = _logDebug;
|
||||
|
||||
Gio.DesktopAppInfo.set_desktop_env("GNOME");
|
||||
Gio.DesktopAppInfo.set_desktop_env('GNOME');
|
||||
|
||||
global.grab_dbus_service();
|
||||
shellDBusService = new ShellDBus.GnomeShell();
|
||||
@ -82,48 +107,52 @@ function start() {
|
||||
Shell.WindowTracker.get_default();
|
||||
Shell.AppUsage.get_default();
|
||||
|
||||
// The background color really only matters if there is no desktop
|
||||
// window (say, nautilus) running. We set it mostly so things look good
|
||||
// when we are running inside Xephyr.
|
||||
// The stage is always covered so Clutter doesn't need to clear it; however
|
||||
// the color is used as the default contents for the Mutter root background
|
||||
// actor so set it anyways.
|
||||
global.stage.color = DEFAULT_BACKGROUND_COLOR;
|
||||
global.stage.no_clear_hint = true;
|
||||
|
||||
// Mutter currently hardcodes putting "Yessir. The compositor is running""
|
||||
// in the Overview. Clear that out.
|
||||
let children = global.overlay_group.get_children();
|
||||
for (let i = 0; i < children.length; i++)
|
||||
children[i].destroy();
|
||||
_defaultCssStylesheet = global.datadir + '/theme/gnome-shell.css';
|
||||
loadTheme();
|
||||
|
||||
let themeContext = St.ThemeContext.get_for_stage (global.stage);
|
||||
let stylesheetPath = global.datadir + "/theme/gnome-shell.css";
|
||||
let theme = new St.Theme ({ application_stylesheet: stylesheetPath });
|
||||
themeContext.set_theme (theme);
|
||||
|
||||
global.connect('panel-run-dialog', function(panel) {
|
||||
// Make sure not more than one run dialog is shown.
|
||||
getRunDialog().open();
|
||||
});
|
||||
let shellwm = global.window_manager;
|
||||
shellwm.takeover_keybinding("panel_main_menu");
|
||||
shellwm.connect("keybinding::panel_main_menu", function () {
|
||||
shellwm.takeover_keybinding('panel_main_menu');
|
||||
shellwm.connect('keybinding::panel_main_menu', function () {
|
||||
overview.toggle();
|
||||
});
|
||||
shellwm.takeover_keybinding("panel_run_dialog");
|
||||
shellwm.connect("keybinding::panel_run_dialog", function () {
|
||||
shellwm.takeover_keybinding('panel_run_dialog');
|
||||
shellwm.connect('keybinding::panel_run_dialog', function () {
|
||||
getRunDialog().open();
|
||||
});
|
||||
|
||||
// Set up stage hierarchy to group all UI actors under one container.
|
||||
uiGroup = new Clutter.Group();
|
||||
global.window_group.reparent(uiGroup);
|
||||
global.overlay_group.reparent(uiGroup);
|
||||
global.stage.add_actor(uiGroup);
|
||||
|
||||
placesManager = new PlaceDisplay.PlacesManager();
|
||||
xdndHandler = new XdndHandler.XdndHandler();
|
||||
overview = new Overview.Overview();
|
||||
chrome = new Chrome.Chrome();
|
||||
magnifier = new Magnifier.Magnifier();
|
||||
statusIconDispatcher = new StatusIconDispatcher.StatusIconDispatcher();
|
||||
panel = new Panel.Panel();
|
||||
sidebar = new Sidebar.Sidebar();
|
||||
wm = new WindowManager.WindowManager();
|
||||
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
||||
notificationPopup = new MessageTray.Notification();
|
||||
messageTray = new MessageTray.MessageTray();
|
||||
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
||||
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
|
||||
telepathyClient = new TelepathyClient.Client();
|
||||
panel.startStatusArea();
|
||||
|
||||
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
|
||||
ctrlAltTabManager.addGroup(panel.actor, _("Panel"), 'gnome-panel');
|
||||
|
||||
_startDate = new Date();
|
||||
|
||||
let recorderSettings = new Gio.Settings({ schema: 'org.gnome.shell.recorder' });
|
||||
|
||||
global.screen.connect('toggle-recording', function() {
|
||||
if (recorder == null) {
|
||||
recorder = new Shell.Recorder({ stage: global.stage });
|
||||
@ -132,11 +161,27 @@ function start() {
|
||||
if (recorder.is_recording()) {
|
||||
recorder.pause();
|
||||
} else {
|
||||
// read the parameters from GSettings always in case they have changed
|
||||
recorder.set_framerate(recorderSettings.get_int('framerate'));
|
||||
recorder.set_filename('shell-%d%u-%c.' + recorderSettings.get_string('file-extension'));
|
||||
let pipeline = recorderSettings.get_string('pipeline');
|
||||
|
||||
if (!pipeline.match(/^\s*$/))
|
||||
recorder.set_pipeline(pipeline);
|
||||
else
|
||||
recorder.set_pipeline(null);
|
||||
|
||||
recorder.record();
|
||||
}
|
||||
});
|
||||
|
||||
_relayout();
|
||||
global.screen.override_workspace_layout(Meta.ScreenCorner.TOPLEFT, false, -1, 1);
|
||||
|
||||
// Provide the bus object for gnome-session to
|
||||
// initiate logouts.
|
||||
EndSessionDialog.init();
|
||||
|
||||
global.gdk_screen.connect('monitors-changed', _relayout);
|
||||
|
||||
ExtensionSystem.init();
|
||||
ExtensionSystem.loadExtensions();
|
||||
@ -145,13 +190,153 @@ function start() {
|
||||
|
||||
let display = global.screen.get_display();
|
||||
display.connect('overlay-key', Lang.bind(overview, overview.toggle));
|
||||
global.connect('panel-main-menu', Lang.bind(overview, overview.toggle));
|
||||
|
||||
global.stage.connect('captured-event', _globalKeyPressHandler);
|
||||
|
||||
_log('info', 'loaded at ' + _startDate);
|
||||
// Perform initial relayout here
|
||||
_relayout();
|
||||
|
||||
Mainloop.idle_add(_removeUnusedWorkspaces);
|
||||
_log('info', 'loaded at ' + _startDate);
|
||||
log('GNOME Shell started at ' + _startDate);
|
||||
|
||||
let perfModuleName = GLib.getenv("SHELL_PERF_MODULE");
|
||||
if (perfModuleName) {
|
||||
let perfOutput = GLib.getenv("SHELL_PERF_OUTPUT");
|
||||
let module = eval('imports.perf.' + perfModuleName + ';');
|
||||
Scripting.runPerfScript(module, perfOutput);
|
||||
}
|
||||
|
||||
global.screen.connect('notify::n-workspaces', _nWorkspacesChanged);
|
||||
Mainloop.idle_add(_nWorkspacesChanged);
|
||||
}
|
||||
|
||||
let _workspaces = [];
|
||||
let _checkWorkspacesId = 0;
|
||||
|
||||
function _checkWorkspaces() {
|
||||
let i;
|
||||
let emptyWorkspaces = [];
|
||||
|
||||
for (i = 0; i < _workspaces.length; i++)
|
||||
emptyWorkspaces[i] = true;
|
||||
|
||||
let windows = global.get_window_actors();
|
||||
for (i = 0; i < windows.length; i++) {
|
||||
let win = windows[i];
|
||||
|
||||
if (win.get_meta_window().is_on_all_workspaces())
|
||||
continue;
|
||||
|
||||
let workspaceIndex = win.get_workspace();
|
||||
emptyWorkspaces[workspaceIndex] = false;
|
||||
}
|
||||
|
||||
// If we don't have an empty workspace at the end, add one
|
||||
if (!emptyWorkspaces[emptyWorkspaces.length -1]) {
|
||||
global.screen.append_new_workspace(false, global.get_current_time());
|
||||
emptyWorkspaces.push(false);
|
||||
}
|
||||
|
||||
// Delete other empty workspaces; do it from the end to avoid index changes
|
||||
for (i = emptyWorkspaces.length - 2; i >= 0; i--) {
|
||||
if (emptyWorkspaces[i])
|
||||
global.screen.remove_workspace(_workspaces[i], global.get_current_time());
|
||||
}
|
||||
|
||||
_checkWorkspacesId = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
function _queueCheckWorkspaces() {
|
||||
if (_checkWorkspacesId == 0)
|
||||
_checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, _checkWorkspaces);
|
||||
}
|
||||
|
||||
function _nWorkspacesChanged() {
|
||||
let oldNumWorkspaces = _workspaces.length;
|
||||
let newNumWorkspaces = global.screen.n_workspaces;
|
||||
|
||||
if (oldNumWorkspaces == newNumWorkspaces)
|
||||
return false;
|
||||
|
||||
let lostWorkspaces = [];
|
||||
if (newNumWorkspaces > oldNumWorkspaces) {
|
||||
let w;
|
||||
|
||||
// Assume workspaces are only added at the end
|
||||
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++)
|
||||
_workspaces[w] = global.screen.get_workspace_by_index(w);
|
||||
|
||||
for (w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
|
||||
let workspace = _workspaces[w];
|
||||
workspace._windowAddedId = workspace.connect('window-added', _queueCheckWorkspaces);
|
||||
workspace._windowRemovedId = workspace.connect('window-removed', _queueCheckWorkspaces);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Assume workspaces are only removed sequentially
|
||||
// (e.g. 2,3,4 - not 2,4,7)
|
||||
let removedIndex;
|
||||
let removedNum = oldNumWorkspaces - newNumWorkspaces;
|
||||
for (let w = 0; w < oldNumWorkspaces; w++) {
|
||||
let workspace = global.screen.get_workspace_by_index(w);
|
||||
if (_workspaces[w] != workspace) {
|
||||
removedIndex = w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let lostWorkspaces = _workspaces.splice(removedIndex, removedNum);
|
||||
lostWorkspaces.forEach(function(workspace) {
|
||||
workspace.disconnect(workspace._windowAddedId);
|
||||
workspace.disconnect(workspace._windowRemovedId);
|
||||
});
|
||||
}
|
||||
|
||||
_queueCheckWorkspaces();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* getThemeStylesheet:
|
||||
*
|
||||
* Get the theme CSS file that the shell will load
|
||||
*
|
||||
* Returns: A file path that contains the theme CSS,
|
||||
* null if using the default
|
||||
*/
|
||||
function getThemeStylesheet()
|
||||
{
|
||||
return _cssStylesheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* setThemeStylesheet:
|
||||
* @cssStylesheet: A file path that contains the theme CSS,
|
||||
* set it to null to use the default
|
||||
*
|
||||
* Set the theme CSS file that the shell will load
|
||||
*/
|
||||
function setThemeStylesheet(cssStylesheet)
|
||||
{
|
||||
_cssStylesheet = cssStylesheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* loadTheme:
|
||||
*
|
||||
* Reloads the theme CSS file
|
||||
*/
|
||||
function loadTheme() {
|
||||
let themeContext = St.ThemeContext.get_for_stage (global.stage);
|
||||
|
||||
let cssStylesheet = _defaultCssStylesheet;
|
||||
if (_cssStylesheet != null)
|
||||
cssStylesheet = _cssStylesheet;
|
||||
|
||||
let theme = new St.Theme ({ application_stylesheet: cssStylesheet });
|
||||
themeContext.set_theme (theme);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,7 +357,7 @@ function _log(category, msg) {
|
||||
for (let i = 2; i < arguments.length; i++) {
|
||||
text += JSON.stringify(arguments[i]);
|
||||
if (i < arguments.length - 1)
|
||||
text += " ";
|
||||
text += ' ';
|
||||
}
|
||||
}
|
||||
_errorLogStack.push({timestamp: new Date().getTime(),
|
||||
@ -200,34 +385,11 @@ function _relayout() {
|
||||
panel.actor.set_position(primary.x, primary.y);
|
||||
panel.actor.set_size(primary.width, Panel.PANEL_HEIGHT);
|
||||
overview.relayout();
|
||||
}
|
||||
|
||||
// metacity-clutter currently uses the same prefs as plain metacity,
|
||||
// which probably means we'll be starting out with multiple workspaces;
|
||||
// remove any unused ones. (We do this from an idle handler, because
|
||||
// global.get_windows() still returns NULL at the point when start()
|
||||
// is called.)
|
||||
function _removeUnusedWorkspaces() {
|
||||
|
||||
let windows = global.get_windows();
|
||||
let maxWorkspace = 0;
|
||||
for (let i = 0; i < windows.length; i++) {
|
||||
let win = windows[i];
|
||||
|
||||
if (!win.get_meta_window().is_on_all_workspaces() &&
|
||||
win.get_workspace() > maxWorkspace) {
|
||||
maxWorkspace = win.get_workspace();
|
||||
}
|
||||
}
|
||||
let screen = global.screen;
|
||||
if (screen.n_workspaces > maxWorkspace) {
|
||||
for (let w = screen.n_workspaces - 1; w > maxWorkspace; w--) {
|
||||
let workspace = screen.get_workspace_by_index(w);
|
||||
screen.remove_workspace(workspace, 0);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
// To avoid updating the position and size of the workspaces
|
||||
// in the overview, we just hide the overview. The positions
|
||||
// will be updated when it is next shown.
|
||||
overview.hide();
|
||||
}
|
||||
|
||||
// This function encapsulates hacks to make certain global keybindings
|
||||
@ -235,44 +397,64 @@ function _removeUnusedWorkspaces() {
|
||||
// are disabled with a global grab. (When there is a global grab, then
|
||||
// all key events will be delivered to the stage, so ::captured-event
|
||||
// on the stage can be used for global keybindings.)
|
||||
//
|
||||
// We expect to need to conditionally enable just a few keybindings
|
||||
// depending on circumstance; the main hackiness here is that we are
|
||||
// assuming that keybindings have their default values; really we
|
||||
// should be asking Mutter to resolve the key into an action and then
|
||||
// base our handling based on the action.
|
||||
function _globalKeyPressHandler(actor, event) {
|
||||
if (modalCount == 0)
|
||||
return false;
|
||||
if (event.type() != Clutter.EventType.KEY_PRESS)
|
||||
return false;
|
||||
|
||||
let type = event.type();
|
||||
let symbol = event.get_key_symbol();
|
||||
let keyCode = event.get_key_code();
|
||||
let modifierState = Shell.get_event_state(event);
|
||||
|
||||
if (type == Clutter.EventType.KEY_PRESS) {
|
||||
let symbol = event.get_key_symbol();
|
||||
if (symbol == Clutter.Print) {
|
||||
// We want to be able to take screenshots of the shell at all times
|
||||
let gconf = Shell.GConf.get_default();
|
||||
let command = gconf.get_string("/apps/metacity/keybinding_commands/command_screenshot");
|
||||
if (command != null && command != "") {
|
||||
let [ok, len, args] = GLib.shell_parse_argv(command);
|
||||
let p = new Shell.Process({'args' : args});
|
||||
p.run();
|
||||
}
|
||||
let display = global.screen.get_display();
|
||||
// This relies on the fact that Clutter.ModifierType is the same as Gdk.ModifierType
|
||||
let action = display.get_keybinding_action(keyCode, modifierState);
|
||||
|
||||
// The screenshot action should always be available (even if a
|
||||
// modal dialog is present)
|
||||
if (action == Meta.KeyBindingAction.COMMAND_SCREENSHOT) {
|
||||
let gconf = GConf.Client.get_default();
|
||||
let command = gconf.get_string('/apps/metacity/keybinding_commands/command_screenshot');
|
||||
if (command != null && command != '')
|
||||
Util.spawnCommandLine(command);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Other bindings are only available when the overview is up and
|
||||
// no modal dialog is present.
|
||||
if (!overview.visible || modalCount > 1)
|
||||
return false;
|
||||
|
||||
// This isn't a Meta.KeyBindingAction yet
|
||||
if (symbol == Clutter.Super_L || symbol == Clutter.Super_R) {
|
||||
overview.hide();
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case Meta.KeyBindingAction.WORKSPACE_LEFT:
|
||||
wm.actionMoveWorkspaceLeft();
|
||||
return true;
|
||||
}
|
||||
} else if (type == Clutter.EventType.KEY_RELEASE) {
|
||||
let symbol = event.get_key_symbol();
|
||||
if (symbol == Clutter.Super_L || symbol == Clutter.Super_R) {
|
||||
// The super key is the default for triggering the overview, and should
|
||||
// get us out of the overview when we are already in it.
|
||||
if (overview.visible)
|
||||
overview.hide();
|
||||
|
||||
case Meta.KeyBindingAction.WORKSPACE_RIGHT:
|
||||
wm.actionMoveWorkspaceRight();
|
||||
return true;
|
||||
} else if (symbol == Clutter.F2 && (Shell.get_event_state(event) & Clutter.ModifierType.MOD1_MASK)) {
|
||||
case Meta.KeyBindingAction.WORKSPACE_UP:
|
||||
wm.actionMoveWorkspaceUp();
|
||||
return true;
|
||||
case Meta.KeyBindingAction.WORKSPACE_DOWN:
|
||||
wm.actionMoveWorkspaceDown();
|
||||
return true;
|
||||
case Meta.KeyBindingAction.PANEL_RUN_DIALOG:
|
||||
case Meta.KeyBindingAction.COMMAND_2:
|
||||
getRunDialog().open();
|
||||
}
|
||||
return true;
|
||||
case Meta.KeyBindingAction.PANEL_MAIN_MENU:
|
||||
overview.hide();
|
||||
return true;
|
||||
case Meta.KeyBindingAction.SWITCH_PANELS:
|
||||
ctrlAltTabManager.popup(modifierState & Clutter.ModifierType.SHIFT_MASK);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -291,22 +473,31 @@ function _findModal(actor) {
|
||||
/**
|
||||
* pushModal:
|
||||
* @actor: #ClutterActor which will be given keyboard focus
|
||||
* @timestamp: optional timestamp
|
||||
*
|
||||
* Ensure we are in a mode where all keyboard and mouse input goes to
|
||||
* the stage. Multiple calls to this function act in a stacking fashion;
|
||||
* the effect will be undone when an equal number of popModal() invocations
|
||||
* have been made.
|
||||
* the stage, and focus @actor. Multiple calls to this function act in
|
||||
* a stacking fashion; the effect will be undone when an equal number
|
||||
* of popModal() invocations have been made.
|
||||
*
|
||||
* Next, record the current Clutter keyboard focus on a stack. If the modal stack
|
||||
* returns to this actor, reset the focus to the actor which was focused
|
||||
* at the time pushModal() was invoked.
|
||||
* Next, record the current Clutter keyboard focus on a stack. If the
|
||||
* modal stack returns to this actor, reset the focus to the actor
|
||||
* which was focused at the time pushModal() was invoked.
|
||||
*
|
||||
* @timestamp is optionally used to associate the call with a specific user
|
||||
* initiated event. If not provided then the value of
|
||||
* global.get_current_time() is assumed.
|
||||
*
|
||||
* Returns: true iff we successfully acquired a grab or already had one
|
||||
*/
|
||||
function pushModal(actor) {
|
||||
function pushModal(actor, timestamp) {
|
||||
|
||||
if (timestamp == undefined)
|
||||
timestamp = global.get_current_time();
|
||||
|
||||
if (modalCount == 0) {
|
||||
if (!global.begin_modal(global.get_current_time())) {
|
||||
log("pushModal: invocation of begin_modal failed");
|
||||
if (!global.begin_modal(timestamp)) {
|
||||
log('pushModal: invocation of begin_modal failed');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -329,18 +520,28 @@ function pushModal(actor) {
|
||||
}
|
||||
modalActorFocusStack.push([actor, curFocus]);
|
||||
|
||||
global.stage.set_key_focus(actor);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* popModal:
|
||||
* @actor: #ClutterActor passed to original invocation of pushModal().
|
||||
* @timestamp: optional timestamp
|
||||
*
|
||||
* Reverse the effect of pushModal(). If this invocation is undoing
|
||||
* the topmost invocation, then the focus will be restored to the
|
||||
* previous focus at the time when pushModal() was invoked.
|
||||
*
|
||||
* @timestamp is optionally used to associate the call with a specific user
|
||||
* initiated event. If not provided then the value of
|
||||
* global.get_current_time() is assumed.
|
||||
*/
|
||||
function popModal(actor) {
|
||||
function popModal(actor, timestamp) {
|
||||
|
||||
if (timestamp == undefined)
|
||||
timestamp = global.get_current_time();
|
||||
|
||||
modalCount -= 1;
|
||||
let focusIndex = _findModal(actor);
|
||||
if (focusIndex >= 0) {
|
||||
@ -358,7 +559,8 @@ function popModal(actor) {
|
||||
if (modalCount > 0)
|
||||
return;
|
||||
|
||||
global.end_modal(global.get_current_time());
|
||||
global.stage.set_key_focus(null);
|
||||
global.end_modal(timestamp);
|
||||
global.set_stage_input_mode(Shell.StageInputMode.NORMAL);
|
||||
}
|
||||
|
||||
@ -381,12 +583,14 @@ function getRunDialog() {
|
||||
* activateWindow:
|
||||
* @window: the Meta.Window to activate
|
||||
* @time: (optional) current event time
|
||||
* @workspaceNum: (optional) window's workspace number
|
||||
*
|
||||
* Activates @window, switching to its workspace first if necessary
|
||||
* Activates @window, switching to its workspace first if necessary,
|
||||
* and switching out of the overview if it's currently active
|
||||
*/
|
||||
function activateWindow(window, time) {
|
||||
function activateWindow(window, time, workspaceNum) {
|
||||
let activeWorkspaceNum = global.screen.get_active_workspace_index();
|
||||
let windowWorkspaceNum = window.get_workspace().index();
|
||||
let windowWorkspaceNum = (workspaceNum !== undefined) ? workspaceNum : window.get_workspace().index();
|
||||
|
||||
if (!time)
|
||||
time = global.get_current_time();
|
||||
@ -397,6 +601,8 @@ function activateWindow(window, time) {
|
||||
} else {
|
||||
window.activate(time);
|
||||
}
|
||||
|
||||
overview.hide();
|
||||
}
|
||||
|
||||
// TODO - replace this timeout with some system to guess when the user might
|
||||
@ -445,7 +651,7 @@ function _queueBeforeRedraw(workId) {
|
||||
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function () {
|
||||
_runBeforeRedrawQueue();
|
||||
return false;
|
||||
}, null);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,7 +675,7 @@ function _queueBeforeRedraw(workId) {
|
||||
*/
|
||||
function initializeDeferredWork(actor, callback, props) {
|
||||
// Turn into a string so we can use as an object property
|
||||
let workId = "" + (++_deferredWorkSequence);
|
||||
let workId = '' + (++_deferredWorkSequence);
|
||||
_deferredWorkData[workId] = { 'actor': actor,
|
||||
'callback': callback };
|
||||
actor.connect('notify::mapped', function () {
|
||||
@ -499,7 +705,7 @@ function initializeDeferredWork(actor, callback, props) {
|
||||
function queueDeferredWork(workId) {
|
||||
let data = _deferredWorkData[workId];
|
||||
if (!data) {
|
||||
global.logError("invalid work id ", workId);
|
||||
global.logError('invalid work id ', workId);
|
||||
return;
|
||||
}
|
||||
if (_deferredWorkQueue.indexOf(workId) < 0)
|
||||
|
1908
js/ui/messageTray.js
235
js/ui/modalDialog.js
Normal file
@ -0,0 +1,235 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Meta = imports.gi.Meta;
|
||||
const Pango = imports.gi.Pango;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Params = imports.misc.params;
|
||||
|
||||
const Lightbox = imports.ui.lightbox;
|
||||
const Main = imports.ui.main;
|
||||
const Tweener = imports.ui.tweener;
|
||||
|
||||
const OPEN_AND_CLOSE_TIME = 0.1;
|
||||
const FADE_OUT_DIALOG_TIME = 1.0;
|
||||
|
||||
const State = {
|
||||
OPENED: 0,
|
||||
CLOSED: 1,
|
||||
OPENING: 2,
|
||||
CLOSING: 3,
|
||||
FADED_OUT: 4
|
||||
};
|
||||
|
||||
function ModalDialog() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
ModalDialog.prototype = {
|
||||
_init: function(params) {
|
||||
params = Params.parse(params, { styleClass: null });
|
||||
|
||||
this.state = State.CLOSED;
|
||||
|
||||
this._group = new St.Group({ visible: false,
|
||||
x: 0,
|
||||
y: 0 });
|
||||
Main.uiGroup.add_actor(this._group);
|
||||
global.focus_manager.add_group(this._group);
|
||||
this._initialKeyFocus = this._group;
|
||||
|
||||
this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy));
|
||||
|
||||
this._actionKeys = {};
|
||||
this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
|
||||
|
||||
this._lightbox = new Lightbox.Lightbox(this._group,
|
||||
{ inhibitEvents: true });
|
||||
|
||||
this._backgroundBin = new St.Bin();
|
||||
|
||||
this._group.add_actor(this._backgroundBin);
|
||||
this._lightbox.highlight(this._backgroundBin);
|
||||
|
||||
this._dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog',
|
||||
vertical: true });
|
||||
if (params.styleClass != null) {
|
||||
this._dialogLayout.add_style_class_name(params.styleClass);
|
||||
}
|
||||
this._backgroundBin.child = this._dialogLayout;
|
||||
|
||||
this.contentLayout = new St.BoxLayout({ vertical: true });
|
||||
this._dialogLayout.add(this.contentLayout,
|
||||
{ x_fill: true,
|
||||
y_fill: true,
|
||||
x_align: St.Align.MIDDLE,
|
||||
y_align: St.Align.START });
|
||||
|
||||
this._buttonLayout = new St.BoxLayout({ opacity: 220,
|
||||
vertical: false });
|
||||
this._dialogLayout.add(this._buttonLayout,
|
||||
{ expand: true,
|
||||
x_align: St.Align.MIDDLE,
|
||||
y_align: St.Align.END });
|
||||
},
|
||||
|
||||
setButtons: function(buttons) {
|
||||
this._buttonLayout.destroy_children();
|
||||
this._actionKeys = {};
|
||||
|
||||
let i = 0;
|
||||
for (let index in buttons) {
|
||||
let buttonInfo = buttons[index];
|
||||
let label = buttonInfo['label'];
|
||||
let action = buttonInfo['action'];
|
||||
let key = buttonInfo['key'];
|
||||
|
||||
let button = new St.Button({ style_class: 'modal-dialog-button',
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
label: label });
|
||||
|
||||
let x_alignment;
|
||||
if (buttons.length == 1)
|
||||
x_alignment = St.Align.END;
|
||||
else if (i == 0)
|
||||
x_alignment = St.Align.START;
|
||||
else if (i == buttons.length - 1)
|
||||
x_alignment = St.Align.END;
|
||||
else
|
||||
x_alignment = St.Align.MIDDLE;
|
||||
|
||||
this._initialKeyFocus = button;
|
||||
this._buttonLayout.add(button,
|
||||
{ expand: true,
|
||||
x_fill: false,
|
||||
y_fill: false,
|
||||
x_align: x_alignment,
|
||||
y_align: St.Align.MIDDLE });
|
||||
|
||||
button.connect('clicked', action);
|
||||
|
||||
if (key)
|
||||
this._actionKeys[key] = action;
|
||||
i++;
|
||||
}
|
||||
},
|
||||
|
||||
_onKeyPressEvent: function(object, keyPressEvent) {
|
||||
let symbol = keyPressEvent.get_key_symbol();
|
||||
let action = this._actionKeys[symbol];
|
||||
|
||||
if (action)
|
||||
action();
|
||||
},
|
||||
|
||||
_onGroupDestroy: function() {
|
||||
this.emit('destroy');
|
||||
},
|
||||
|
||||
_fadeOpen: function() {
|
||||
let monitor = global.get_focus_monitor();
|
||||
|
||||
this._backgroundBin.set_position(monitor.x, monitor.y);
|
||||
this._backgroundBin.set_size(monitor.width, monitor.height);
|
||||
|
||||
this.state = State.OPENING;
|
||||
|
||||
this._dialogLayout.opacity = 255;
|
||||
this._lightbox.show();
|
||||
this._group.opacity = 0;
|
||||
this._group.show();
|
||||
Tweener.addTween(this._group,
|
||||
{ opacity: 255,
|
||||
time: OPEN_AND_CLOSE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this._initialKeyFocus.grab_key_focus();
|
||||
this.state = State.OPENED;
|
||||
this.emit('opened');
|
||||
}),
|
||||
});
|
||||
},
|
||||
|
||||
open: function(timestamp) {
|
||||
if (this.state == State.OPENED || this.state == State.OPENING)
|
||||
return true;
|
||||
|
||||
if (!Main.pushModal(this._group, timestamp))
|
||||
return false;
|
||||
|
||||
global.stage.set_key_focus(this._group);
|
||||
|
||||
this._fadeOpen();
|
||||
return true;
|
||||
},
|
||||
|
||||
close: function(timestamp) {
|
||||
if (this.state == State.CLOSED || this.state == State.CLOSING)
|
||||
return;
|
||||
|
||||
let needsPopModal;
|
||||
|
||||
if (this.state == State.OPENED || this.state == State.OPENING)
|
||||
needsPopModal = true;
|
||||
else
|
||||
needsPopModal = false;
|
||||
|
||||
this.state = State.CLOSING;
|
||||
|
||||
Tweener.addTween(this._group,
|
||||
{ opacity: 0,
|
||||
time: OPEN_AND_CLOSE_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this.state = State.CLOSED;
|
||||
this._group.hide();
|
||||
|
||||
if (needsPopModal)
|
||||
Main.popModal(this._group, timestamp);
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
// This method is like close, but fades the dialog out much slower,
|
||||
// and leaves the lightbox in place. Once in the faded out state,
|
||||
// the dialog can be brought back by an open call, or the lightbox
|
||||
// can be dismissed by a close call.
|
||||
//
|
||||
// The main point of this method is to give some indication to the user
|
||||
// that the dialog reponse has been acknowledged but will take a few
|
||||
// moments before being processed.
|
||||
// e.g., if a user clicked "Log Out" then the dialog should go away
|
||||
// imediately, but the lightbox should remain until the logout is
|
||||
// complete.
|
||||
_fadeOutDialog: function(timestamp) {
|
||||
if (this.state == State.CLOSED || this.state == State.CLOSING)
|
||||
return;
|
||||
|
||||
if (this.state == State.FADED_OUT)
|
||||
return;
|
||||
|
||||
Tweener.addTween(this._dialogLayout,
|
||||
{ opacity: 0,
|
||||
time: FADE_OUT_DIALOG_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
this.state = State.FADED_OUT;
|
||||
Main.popModal(this._group, timestamp);
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(ModalDialog.prototype);
|
@ -5,13 +5,38 @@ const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Mainloop = imports.mainloop;
|
||||
const St = imports.gi.St;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Config = imports.misc.config;
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const Params = imports.misc.params;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
let nextNotificationId = 1;
|
||||
|
||||
// Should really be defined in dbus.js
|
||||
const BusIface = {
|
||||
name: 'org.freedesktop.DBus',
|
||||
methods: [{ name: 'GetConnectionUnixProcessID',
|
||||
inSignature: 's',
|
||||
outSignature: 'i' }]
|
||||
};
|
||||
|
||||
const Bus = function () {
|
||||
this._init();
|
||||
};
|
||||
|
||||
Bus.prototype = {
|
||||
_init: function() {
|
||||
DBus.session.proxifyObject(this, 'org.freedesktop.DBus', '/org/freedesktop/DBus');
|
||||
}
|
||||
};
|
||||
|
||||
DBus.proxifyPrototype(Bus.prototype, BusIface);
|
||||
|
||||
const NotificationDaemonIface = {
|
||||
name: 'org.freedesktop.Notifications',
|
||||
methods: [{ name: 'Notify',
|
||||
@ -49,6 +74,17 @@ const Urgency = {
|
||||
CRITICAL: 2
|
||||
};
|
||||
|
||||
const rewriteRules = {
|
||||
'XChat': [
|
||||
{ pattern: /^XChat: Private message from: (\S*) \(.*\)$/,
|
||||
replacement: '<$1>' },
|
||||
{ pattern: /^XChat: New public message from: (\S*) \((.*)\)$/,
|
||||
replacement: '$2 <$1>' },
|
||||
{ pattern: /^XChat: Highlighted message from: (\S*) \((.*)\)$/,
|
||||
replacement: '$2 <$1>' }
|
||||
]
|
||||
};
|
||||
|
||||
function NotificationDaemon() {
|
||||
this._init();
|
||||
}
|
||||
@ -66,6 +102,19 @@ NotificationDaemon.prototype = {
|
||||
DBus.MANY_INSTANCES,
|
||||
Lang.bind(this, this._acquiredName),
|
||||
Lang.bind(this, this._lostName));
|
||||
|
||||
this._sources = {};
|
||||
this._senderToPid = {};
|
||||
this._notifications = {};
|
||||
this._busProxy = new Bus();
|
||||
|
||||
Main.statusIconDispatcher.connect('message-icon-added', Lang.bind(this, this._onTrayIconAdded));
|
||||
Main.statusIconDispatcher.connect('message-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
|
||||
|
||||
Shell.WindowTracker.get_default().connect('notify::focus-app',
|
||||
Lang.bind(this, this._onFocusAppChanged));
|
||||
Main.overview.connect('hidden',
|
||||
Lang.bind(this, this._onFocusAppChanged));
|
||||
},
|
||||
|
||||
_acquiredName: function() {
|
||||
@ -79,131 +128,32 @@ NotificationDaemon.prototype = {
|
||||
log('Failed to acquire org.freedesktop.Notifications');
|
||||
else {
|
||||
log('Failed to acquire org.freedesktop.Notifications; trying again');
|
||||
|
||||
// kill the notification-daemon. pkill is more portable
|
||||
// than killall, but on Linux at least it won't match if
|
||||
// you pass more than 15 characters of the process name...
|
||||
// However, if you use the "-f" flag to match the entire
|
||||
// command line, it will work, but we have to be careful
|
||||
// in that case that we don't match "gedit
|
||||
// notification-daemon.c" or whatever...
|
||||
let p = new Shell.Process({ args: ['pkill', '-f',
|
||||
'^([^ ]*/)?(notification-daemon|notify-osd)$']});
|
||||
p.run();
|
||||
Util.killall('notification-daemon');
|
||||
Util.killall('notify-osd');
|
||||
}
|
||||
},
|
||||
|
||||
_sourceId: function(id) {
|
||||
return 'notification-' + id;
|
||||
},
|
||||
_iconForNotificationData: function(icon, hints, size) {
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
|
||||
Notify: function(appName, replacesId, icon, summary, body,
|
||||
actions, hints, timeout) {
|
||||
let id, source = null;
|
||||
|
||||
if (replacesId != 0) {
|
||||
id = replacesId;
|
||||
source = Main.messageTray.getSource(this._sourceId(id));
|
||||
// source may be null if the current source was destroyed
|
||||
// right as the client sent the new notification
|
||||
}
|
||||
|
||||
if (source == null) {
|
||||
id = nextNotificationId++;
|
||||
|
||||
source = new Source(this._sourceId(id), icon, hints);
|
||||
Main.messageTray.add(source);
|
||||
|
||||
source.connect('clicked', Lang.bind(this,
|
||||
function() {
|
||||
source.destroy();
|
||||
this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
|
||||
}));
|
||||
}
|
||||
|
||||
summary = GLib.markup_escape_text(summary, -1);
|
||||
if (body)
|
||||
source.notify('<b>' + summary + '</b>: ' + body);
|
||||
else
|
||||
source.notify('<b>' + summary + '</b>');
|
||||
return id;
|
||||
},
|
||||
|
||||
CloseNotification: function(id) {
|
||||
let source = Main.messageTray.getSource(this._sourceId(id));
|
||||
if (source)
|
||||
source.destroy();
|
||||
this._emitNotificationClosed(id, NotificationClosedReason.APP_CLOSED);
|
||||
},
|
||||
|
||||
GetCapabilities: function() {
|
||||
return [
|
||||
// 'actions',
|
||||
'body',
|
||||
// 'body-hyperlinks',
|
||||
// 'body-images',
|
||||
'body-markup',
|
||||
// 'icon-multi',
|
||||
'icon-static'
|
||||
// 'sound',
|
||||
];
|
||||
},
|
||||
|
||||
GetServerInformation: function() {
|
||||
return [
|
||||
'GNOME Shell',
|
||||
'GNOME',
|
||||
'0.1', // FIXME, get this from somewhere
|
||||
'1.0'
|
||||
];
|
||||
},
|
||||
|
||||
_emitNotificationClosed: function(id, reason) {
|
||||
DBus.session.emit_signal('/org/freedesktop/Notifications',
|
||||
'org.freedesktop.Notifications',
|
||||
'NotificationClosed', 'uu',
|
||||
[id, reason]);
|
||||
}
|
||||
};
|
||||
|
||||
DBus.conformExport(NotificationDaemon.prototype, NotificationDaemonIface);
|
||||
|
||||
function Source(sourceId, icon, hints) {
|
||||
this._init(sourceId, icon, hints);
|
||||
}
|
||||
|
||||
Source.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function(sourceId, icon, hints) {
|
||||
MessageTray.Source.prototype._init.call(this, sourceId);
|
||||
|
||||
hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true);
|
||||
|
||||
this._icon = icon;
|
||||
this._iconData = hints.icon_data;
|
||||
this._urgency = hints.urgency;
|
||||
},
|
||||
|
||||
createIcon: function(size) {
|
||||
let textureCache = Shell.TextureCache.get_default();
|
||||
|
||||
if (this._icon) {
|
||||
if (this._icon.substr(0, 7) == 'file://')
|
||||
return textureCache.load_uri_async(this._icon, size, size);
|
||||
else if (this._icon[0] == '/') {
|
||||
let uri = GLib.filename_to_uri(this._icon, null);
|
||||
if (icon) {
|
||||
if (icon.substr(0, 7) == 'file://')
|
||||
return textureCache.load_uri_async(icon, size, size);
|
||||
else if (icon[0] == '/') {
|
||||
let uri = GLib.filename_to_uri(icon, null);
|
||||
return textureCache.load_uri_async(uri, size, size);
|
||||
} else
|
||||
return textureCache.load_icon_name(this._icon, size);
|
||||
} else if (this._iconData) {
|
||||
return new St.Icon({ icon_name: icon,
|
||||
icon_type: St.IconType.FULLCOLOR,
|
||||
icon_size: size });
|
||||
} else if (hints['image-data']) {
|
||||
let [width, height, rowStride, hasAlpha,
|
||||
bitsPerSample, nChannels, data] = this._iconData;
|
||||
bitsPerSample, nChannels, data] = hints['image-data'];
|
||||
return textureCache.load_from_raw(data, data.length, hasAlpha,
|
||||
width, height, rowStride, size);
|
||||
} else {
|
||||
let stockIcon;
|
||||
switch (this._urgency) {
|
||||
switch (hints.urgency) {
|
||||
case Urgency.LOW:
|
||||
case Urgency.NORMAL:
|
||||
stockIcon = 'gtk-dialog-info';
|
||||
@ -212,7 +162,360 @@ Source.prototype = {
|
||||
stockIcon = 'gtk-dialog-error';
|
||||
break;
|
||||
}
|
||||
return textureCache.load_icon_name(stockIcon, size);
|
||||
return new St.Icon({ icon_name: stockIcon,
|
||||
icon_type: St.IconType.FULLCOLOR,
|
||||
icon_size: size });
|
||||
}
|
||||
},
|
||||
|
||||
// Returns the source associated with ndata.notification if it is set.
|
||||
// Otherwise, returns the source associated with the pid if one is
|
||||
// stored in this._sources and the notification is not transient.
|
||||
// Otherwise, creates a new source as long as pid is provided.
|
||||
//
|
||||
// Either a pid or ndata.notification is needed to retrieve or
|
||||
// create a source.
|
||||
_getSource: function(title, pid, ndata) {
|
||||
if (!pid && !(ndata && ndata.notification))
|
||||
return null;
|
||||
|
||||
// We use notification's source for the notifications we still have
|
||||
// around that are getting replaced because we don't keep sources
|
||||
// for transient notifications in this._sources, but we still want
|
||||
// the notification associated with them to get replaced correctly.
|
||||
if (ndata && ndata.notification)
|
||||
return ndata.notification.source;
|
||||
|
||||
let isForTransientNotification = (ndata && ndata.hints['transient'] == true);
|
||||
|
||||
// We don't want to override a persistent notification
|
||||
// with a transient one from the same sender, so we
|
||||
// always create a new source object for new transient notifications
|
||||
// and never add it to this._sources .
|
||||
if (!isForTransientNotification && this._sources[pid])
|
||||
return this._sources[pid];
|
||||
|
||||
let source = new Source(title, pid);
|
||||
source.setTransient(isForTransientNotification);
|
||||
|
||||
if (!isForTransientNotification) {
|
||||
this._sources[pid] = source;
|
||||
source.connect('destroy', Lang.bind(this,
|
||||
function() {
|
||||
delete this._sources[pid];
|
||||
}));
|
||||
}
|
||||
|
||||
Main.messageTray.add(source);
|
||||
return source;
|
||||
},
|
||||
|
||||
Notify: function(appName, replacesId, icon, summary, body,
|
||||
actions, hints, timeout) {
|
||||
let id;
|
||||
|
||||
// Filter out notifications from Empathy, since we
|
||||
// handle that information from telepathyClient.js
|
||||
if (appName == 'Empathy') {
|
||||
// Ignore replacesId since we already sent back a
|
||||
// NotificationClosed for that id.
|
||||
id = nextNotificationId++;
|
||||
Mainloop.idle_add(Lang.bind(this,
|
||||
function () {
|
||||
this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
|
||||
}));
|
||||
return id;
|
||||
}
|
||||
|
||||
let rewrites = rewriteRules[appName];
|
||||
if (rewrites) {
|
||||
for (let i = 0; i < rewrites.length; i++) {
|
||||
let rule = rewrites[i];
|
||||
if (summary.search(rule.pattern) != -1)
|
||||
summary = summary.replace(rule.pattern, rule.replacement);
|
||||
}
|
||||
}
|
||||
|
||||
hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true);
|
||||
|
||||
// Be compatible with the various hints for image data
|
||||
// 'image-data' is the latest name of this hint, introduced in 1.2
|
||||
if (!hints['image-data']) {
|
||||
if (hints['image_data'])
|
||||
hints['image-data'] = hints['image_data']; // version 1.1 of the spec
|
||||
else if (hints['icon_data'])
|
||||
hints['image-data'] = hints['icon_data']; // previous versions of the spec
|
||||
}
|
||||
|
||||
let ndata = { appName: appName,
|
||||
icon: icon,
|
||||
summary: summary,
|
||||
body: body,
|
||||
actions: actions,
|
||||
hints: hints,
|
||||
timeout: timeout };
|
||||
if (replacesId != 0 && this._notifications[replacesId]) {
|
||||
ndata.id = id = replacesId;
|
||||
ndata.notification = this._notifications[replacesId].notification;
|
||||
} else {
|
||||
replacesId = 0;
|
||||
ndata.id = id = nextNotificationId++;
|
||||
}
|
||||
this._notifications[id] = ndata;
|
||||
|
||||
let sender = DBus.getCurrentMessageContext().sender;
|
||||
let pid = this._senderToPid[sender];
|
||||
|
||||
let source = this._getSource(appName, pid, ndata);
|
||||
|
||||
if (source) {
|
||||
this._notifyForSource(source, ndata);
|
||||
return id;
|
||||
}
|
||||
|
||||
if (replacesId) {
|
||||
// There's already a pending call to GetConnectionUnixProcessID,
|
||||
// which will see the new notification data when it finishes,
|
||||
// so we don't have to do anything.
|
||||
return id;
|
||||
}
|
||||
|
||||
this._busProxy.GetConnectionUnixProcessIDRemote(sender, Lang.bind(this,
|
||||
function (pid, ex) {
|
||||
// The app may have updated or removed the notification
|
||||
ndata = this._notifications[id];
|
||||
if (!ndata)
|
||||
return;
|
||||
|
||||
source = this._getSource(appName, pid, ndata);
|
||||
|
||||
// We only store sender-pid entries for persistent sources.
|
||||
// Removing the entries once the source is destroyed
|
||||
// would result in the entries associated with transient
|
||||
// sources removed once the notification is shown anyway.
|
||||
// However, keeping these pairs would mean that we would
|
||||
// possibly remove an entry associated with a persistent
|
||||
// source when a transient source for the same sender is
|
||||
// distroyed.
|
||||
if (!source.isTransient) {
|
||||
this._senderToPid[sender] = pid;
|
||||
source.connect('destroy', Lang.bind(this,
|
||||
function() {
|
||||
delete this._senderToPid[sender];
|
||||
}));
|
||||
}
|
||||
this._notifyForSource(source, ndata);
|
||||
}));
|
||||
|
||||
return id;
|
||||
},
|
||||
|
||||
_notifyForSource: function(source, ndata) {
|
||||
let [id, icon, summary, body, actions, hints, notification] =
|
||||
[ndata.id, ndata.icon, ndata.summary, ndata.body,
|
||||
ndata.actions, ndata.hints, ndata.notification];
|
||||
|
||||
let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE);
|
||||
|
||||
if (notification == null) {
|
||||
notification = new MessageTray.Notification(source, summary, body,
|
||||
{ icon: iconActor,
|
||||
bannerMarkup: true });
|
||||
ndata.notification = notification;
|
||||
notification.connect('destroy', Lang.bind(this,
|
||||
function(n, reason) {
|
||||
delete this._notifications[id];
|
||||
let notificationClosedReason;
|
||||
switch (reason) {
|
||||
case MessageTray.NotificationDestroyedReason.EXPIRED:
|
||||
notificationClosedReason = NotificationClosedReason.EXPIRED;
|
||||
break;
|
||||
case MessageTray.NotificationDestroyedReason.DISMISSED:
|
||||
notificationClosedReason = NotificationClosedReason.DISMISSED;
|
||||
break;
|
||||
case MessageTray.NotificationDestroyedReason.SOURCE_CLOSED:
|
||||
notificationClosedReason = NotificationClosedReason.APP_CLOSED;
|
||||
break;
|
||||
}
|
||||
this._emitNotificationClosed(id, notificationClosedReason);
|
||||
}));
|
||||
notification.connect('action-invoked', Lang.bind(this,
|
||||
function(n, actionId) {
|
||||
this._emitActionInvoked(id, actionId);
|
||||
}));
|
||||
} else {
|
||||
notification.update(summary, body, { icon: iconActor,
|
||||
bannerMarkup: true,
|
||||
clear: true });
|
||||
}
|
||||
|
||||
if (actions.length) {
|
||||
notification.setUseActionIcons(hints['action-icons'] == true);
|
||||
for (let i = 0; i < actions.length - 1; i += 2)
|
||||
notification.addButton(actions[i], actions[i + 1]);
|
||||
}
|
||||
switch (hints.urgency) {
|
||||
case Urgency.LOW:
|
||||
notification.setUrgency(MessageTray.Urgency.LOW);
|
||||
break;
|
||||
case Urgency.NORMAL:
|
||||
notification.setUrgency(MessageTray.Urgency.NORMAL);
|
||||
break;
|
||||
case Urgency.CRITICAL:
|
||||
notification.setUrgency(MessageTray.Urgency.CRITICAL);
|
||||
break;
|
||||
}
|
||||
notification.setResident(hints.resident == true);
|
||||
// 'transient' is a reserved keyword in JS, so we have to retrieve the value
|
||||
// of the 'transient' hint with hints['transient'] rather than hints.transient
|
||||
notification.setTransient(hints['transient'] == true);
|
||||
|
||||
let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null;
|
||||
source.notify(notification, sourceIconActor);
|
||||
},
|
||||
|
||||
CloseNotification: function(id) {
|
||||
let ndata = this._notifications[id];
|
||||
if (ndata) {
|
||||
if (ndata.notification)
|
||||
ndata.notification.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED);
|
||||
delete this._notifications[id];
|
||||
}
|
||||
},
|
||||
|
||||
GetCapabilities: function() {
|
||||
return [
|
||||
'actions',
|
||||
'action-icons',
|
||||
'body',
|
||||
// 'body-hyperlinks',
|
||||
// 'body-images',
|
||||
'body-markup',
|
||||
// 'icon-multi',
|
||||
'icon-static',
|
||||
'persistence',
|
||||
// 'sound',
|
||||
];
|
||||
},
|
||||
|
||||
GetServerInformation: function() {
|
||||
return [
|
||||
Config.PACKAGE_NAME,
|
||||
'GNOME',
|
||||
Config.PACKAGE_VERSION,
|
||||
'1.2'
|
||||
];
|
||||
},
|
||||
|
||||
_onFocusAppChanged: function() {
|
||||
let tracker = Shell.WindowTracker.get_default();
|
||||
if (!tracker.focus_app)
|
||||
return;
|
||||
|
||||
for (let id in this._sources) {
|
||||
let source = this._sources[id];
|
||||
if (source.app == tracker.focus_app) {
|
||||
if (source.notification && !source.notification.resident)
|
||||
source.notification.destroy();
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_emitNotificationClosed: function(id, reason) {
|
||||
DBus.session.emit_signal('/org/freedesktop/Notifications',
|
||||
'org.freedesktop.Notifications',
|
||||
'NotificationClosed', 'uu',
|
||||
[id, reason]);
|
||||
},
|
||||
|
||||
_emitActionInvoked: function(id, action) {
|
||||
DBus.session.emit_signal('/org/freedesktop/Notifications',
|
||||
'org.freedesktop.Notifications',
|
||||
'ActionInvoked', 'us',
|
||||
[id, action]);
|
||||
},
|
||||
|
||||
_onTrayIconAdded: function(o, icon) {
|
||||
let source = this._getSource(icon.title || icon.wm_class || _("Unknown"), icon.pid, null);
|
||||
source.setTrayIcon(icon);
|
||||
},
|
||||
|
||||
_onTrayIconRemoved: function(o, icon) {
|
||||
let source = this._sources[icon.pid];
|
||||
if (source)
|
||||
source.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
DBus.conformExport(NotificationDaemon.prototype, NotificationDaemonIface);
|
||||
|
||||
function Source(title, pid) {
|
||||
this._init(title, pid);
|
||||
}
|
||||
|
||||
Source.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function(title, pid) {
|
||||
MessageTray.Source.prototype._init.call(this, title);
|
||||
|
||||
this._pid = pid;
|
||||
this._setApp();
|
||||
if (this.app)
|
||||
this.title = this.app.get_name();
|
||||
else
|
||||
this.useNotificationIcon = true;
|
||||
this._isTrayIcon = false;
|
||||
},
|
||||
|
||||
notify: function(notification, icon) {
|
||||
if (!this.app)
|
||||
this._setApp();
|
||||
if (!this.app && icon)
|
||||
this._setSummaryIcon(icon);
|
||||
MessageTray.Source.prototype.notify.call(this, notification);
|
||||
},
|
||||
|
||||
_setApp: function() {
|
||||
if (this.app)
|
||||
return;
|
||||
|
||||
this.app = Shell.WindowTracker.get_default().get_app_from_pid(this._pid);
|
||||
if (!this.app)
|
||||
return;
|
||||
|
||||
// Only override the icon if we were previously using
|
||||
// notification-based icons (ie, not a trayicon) or if it was unset before
|
||||
if (!this._isTrayIcon) {
|
||||
this.useNotificationIcon = false;
|
||||
this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
|
||||
}
|
||||
},
|
||||
|
||||
setTrayIcon: function(icon) {
|
||||
this._setSummaryIcon(icon);
|
||||
this.useNotificationIcon = false;
|
||||
this._isTrayIcon = true;
|
||||
},
|
||||
|
||||
_notificationClicked: function(notification) {
|
||||
this.openApp();
|
||||
},
|
||||
|
||||
_notificationRemoved: function() {
|
||||
if (!this._isTrayIcon)
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
openApp: function() {
|
||||
if (this.app == null)
|
||||
return;
|
||||
|
||||
let windows = this.app.get_windows();
|
||||
if (windows.length > 0) {
|
||||
let mostRecentWindow = windows[0];
|
||||
Main.activateWindow(mostRecentWindow);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
920
js/ui/panel.js
97
js/ui/panelMenu.js
Normal file
@ -0,0 +1,97 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const St = imports.gi.St;
|
||||
const Lang = imports.lang;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
function Button(menuAlignment) {
|
||||
this._init(menuAlignment);
|
||||
}
|
||||
|
||||
Button.prototype = {
|
||||
_init: function(menuAlignment) {
|
||||
this.actor = new St.Bin({ style_class: 'panel-button',
|
||||
reactive: true,
|
||||
can_focus: true,
|
||||
x_fill: true,
|
||||
y_fill: false,
|
||||
track_hover: true });
|
||||
this.actor._delegate = this;
|
||||
this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress));
|
||||
this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPress));
|
||||
this.menu = new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, /* FIXME */ 0);
|
||||
this.menu.connect('open-state-changed', Lang.bind(this, this._onOpenStateChanged));
|
||||
Main.chrome.addActor(this.menu.actor, { visibleInOverview: true,
|
||||
affectsStruts: false });
|
||||
this.menu.actor.hide();
|
||||
},
|
||||
|
||||
_onButtonPress: function(actor, event) {
|
||||
this.menu.toggle();
|
||||
},
|
||||
|
||||
_onKeyPress: function(actor, event) {
|
||||
let symbol = event.get_key_symbol();
|
||||
if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
|
||||
this.menu.toggle();
|
||||
return true;
|
||||
} else if (symbol == Clutter.KEY_Down) {
|
||||
if (!this.menu.isOpen)
|
||||
this.menu.toggle();
|
||||
this.menu.activateFirst();
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
},
|
||||
|
||||
_onOpenStateChanged: function(menu, open) {
|
||||
if (open)
|
||||
this.actor.add_style_pseudo_class('pressed');
|
||||
else
|
||||
this.actor.remove_style_pseudo_class('pressed');
|
||||
}
|
||||
};
|
||||
|
||||
/* SystemStatusButton:
|
||||
*
|
||||
* This class manages one System Status indicator (network, keyboard,
|
||||
* volume, bluetooth...), which is just a PanelMenuButton with an
|
||||
* icon and a tooltip
|
||||
*/
|
||||
function SystemStatusButton() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
SystemStatusButton.prototype = {
|
||||
__proto__: Button.prototype,
|
||||
|
||||
_init: function(iconName,tooltipText) {
|
||||
Button.prototype._init.call(this, St.Align.START);
|
||||
this._iconActor = new St.Icon({ icon_name: iconName,
|
||||
icon_type: St.IconType.SYMBOLIC,
|
||||
style_class: 'system-status-icon' });
|
||||
this.actor.set_child(this._iconActor);
|
||||
this.setTooltip(tooltipText);
|
||||
},
|
||||
|
||||
setIcon: function(iconName) {
|
||||
this._iconActor.icon_name = iconName;
|
||||
},
|
||||
|
||||
setGIcon: function(gicon) {
|
||||
this._iconActor.gicon = gicon;
|
||||
},
|
||||
|
||||
setTooltip: function(text) {
|
||||
if (text != null) {
|
||||
this.tooltip = text;
|
||||
this.actor.has_tooltip = true;
|
||||
this.actor.tooltip_text = text;
|
||||
} else {
|
||||
this.actor.has_tooltip = false;
|
||||
this.tooltip = null;
|
||||
}
|
||||
}
|
||||
};
|
@ -1,8 +1,5 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Pango = imports.gi.Pango;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Shell = imports.gi.Shell;
|
||||
@ -15,12 +12,9 @@ const _ = Gettext.gettext;
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const Main = imports.ui.main;
|
||||
const Params = imports.misc.params;
|
||||
const Search = imports.ui.search;
|
||||
|
||||
const NAUTILUS_PREFS_DIR = '/apps/nautilus/preferences';
|
||||
const DESKTOP_IS_HOME_KEY = NAUTILUS_PREFS_DIR + '/desktop_is_home_dir';
|
||||
|
||||
const PLACES_ICON_SIZE = 16;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
/**
|
||||
* Represents a place object, which is most normally a bookmark entry,
|
||||
@ -48,10 +42,14 @@ PlaceInfo.prototype = {
|
||||
for (let i = 0; i < terms.length; i++) {
|
||||
let term = terms[i];
|
||||
let idx = this._lowerName.indexOf(term);
|
||||
if (idx == 0)
|
||||
return Search.MatchType.PREFIX;
|
||||
else if (idx > 0)
|
||||
mtype = Search.MatchType.SUBSTRING;
|
||||
if (idx == 0) {
|
||||
mtype = Search.MatchType.PREFIX;
|
||||
} else if (idx > 0) {
|
||||
if (mtype == Search.MatchType.NONE)
|
||||
mtype = Search.MatchType.SUBSTRING;
|
||||
} else {
|
||||
return Search.MatchType.NONE;
|
||||
}
|
||||
}
|
||||
return mtype;
|
||||
},
|
||||
@ -59,6 +57,21 @@ PlaceInfo.prototype = {
|
||||
isRemovable: function() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper function to translate launch parameters into a GAppLaunchContext
|
||||
function _makeLaunchContext(params)
|
||||
{
|
||||
params = Params.parse(params, { workspace: null,
|
||||
timestamp: null });
|
||||
|
||||
let launchContext = global.create_app_launch_context();
|
||||
if (params.workspace != null)
|
||||
launchContext.set_desktop(params.workspace.index());
|
||||
if (params.timestamp != null)
|
||||
launchContext.set_timestamp(params.timestamp);
|
||||
|
||||
return launchContext;
|
||||
}
|
||||
|
||||
function PlaceDeviceInfo(mount) {
|
||||
@ -72,17 +85,17 @@ PlaceDeviceInfo.prototype = {
|
||||
this._mount = mount;
|
||||
this.name = mount.get_name();
|
||||
this._lowerName = this.name.toLowerCase();
|
||||
this.id = "mount:" + mount.get_root().get_uri();
|
||||
this.id = 'mount:' + mount.get_root().get_uri();
|
||||
},
|
||||
|
||||
iconFactory: function(size) {
|
||||
let icon = this._mount.get_icon();
|
||||
return Shell.TextureCache.get_default().load_gicon(icon, size);
|
||||
return St.TextureCache.get_default().load_gicon(null, icon, size);
|
||||
},
|
||||
|
||||
launch: function() {
|
||||
launch: function(param) {
|
||||
Gio.app_info_launch_default_for_uri(this._mount.get_root().get_uri(),
|
||||
global.create_app_launch_context());
|
||||
_makeLaunchContex(params));
|
||||
},
|
||||
|
||||
isRemovable: function() {
|
||||
@ -93,14 +106,26 @@ PlaceDeviceInfo.prototype = {
|
||||
if (!this.isRemovable())
|
||||
return;
|
||||
|
||||
this._mount.unmount(0, null, Lang.bind(this, this._removeFinish), null);
|
||||
if (this._mount.can_eject())
|
||||
this._mount.eject(0, null, Lang.bind(this, this._removeFinish));
|
||||
else
|
||||
this._mount.unmount(0, null, Lang.bind(this, this._removeFinish));
|
||||
},
|
||||
|
||||
_removeFinish: function(o, res, data) {
|
||||
this._mount.unmount_finish(res);
|
||||
try {
|
||||
if (this._mount.can_eject())
|
||||
this._mount.eject_finish(res);
|
||||
else
|
||||
this._mount.unmount_finish(res);
|
||||
} catch (e) {
|
||||
let message = _("Failed to unmount '%s'").format(o.get_name());
|
||||
Main.overview.shellInfo.setMessage(message,
|
||||
Lang.bind(this, this.remove),
|
||||
_("Retry"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function PlacesManager() {
|
||||
this._init();
|
||||
@ -108,13 +133,9 @@ function PlacesManager() {
|
||||
|
||||
PlacesManager.prototype = {
|
||||
_init: function() {
|
||||
let gconf = Shell.GConf.get_default();
|
||||
gconf.watch_directory(NAUTILUS_PREFS_DIR);
|
||||
|
||||
this._defaultPlaces = [];
|
||||
this._mounts = [];
|
||||
this._bookmarks = [];
|
||||
this._isDesktopHome = false;
|
||||
|
||||
let homeFile = Gio.file_new_for_path (GLib.get_home_dir());
|
||||
let homeUri = homeFile.get_uri();
|
||||
@ -122,10 +143,10 @@ PlacesManager.prototype = {
|
||||
let homeIcon = Shell.util_get_icon_for_uri (homeUri);
|
||||
this._home = new PlaceInfo('special:home', homeLabel,
|
||||
function(size) {
|
||||
return Shell.TextureCache.get_default().load_gicon(homeIcon, size);
|
||||
return St.TextureCache.get_default().load_gicon(null, homeIcon, size);
|
||||
},
|
||||
function() {
|
||||
Gio.app_info_launch_default_for_uri(homeUri, global.create_app_launch_context());
|
||||
function(params) {
|
||||
Gio.app_info_launch_default_for_uri(homeUri, _makeLaunchContext(params));
|
||||
});
|
||||
|
||||
let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
|
||||
@ -135,18 +156,24 @@ PlacesManager.prototype = {
|
||||
let desktopIcon = Shell.util_get_icon_for_uri (desktopUri);
|
||||
this._desktopMenu = new PlaceInfo('special:desktop', desktopLabel,
|
||||
function(size) {
|
||||
return Shell.TextureCache.get_default().load_gicon(desktopIcon, size);
|
||||
return St.TextureCache.get_default().load_gicon(null, desktopIcon, size);
|
||||
},
|
||||
function() {
|
||||
Gio.app_info_launch_default_for_uri(desktopUri, global.create_app_launch_context());
|
||||
function(params) {
|
||||
Gio.app_info_launch_default_for_uri(desktopUri, _makeLaunchContext(params));
|
||||
});
|
||||
|
||||
this._connect = new PlaceInfo('special:connect', _("Connect to..."),
|
||||
function (size) {
|
||||
return Shell.TextureCache.get_default().load_icon_name("applications-internet", size);
|
||||
return new St.Icon({ icon_name: 'applications-internet',
|
||||
icon_type: St.IconType.FULLCOLOR,
|
||||
icon_size: size });
|
||||
},
|
||||
function () {
|
||||
new Shell.Process({ args: ['nautilus-connect-server'] }).run();
|
||||
function (params) {
|
||||
// BUG: nautilus-connect-server doesn't have a desktop file, so we can'
|
||||
// launch it with the workspace from params. It's probably pretty rare
|
||||
// and odd to drag this place onto a workspace in any case
|
||||
|
||||
Util.spawn(['nautilus-connect-server']);
|
||||
});
|
||||
|
||||
let networkApp = null;
|
||||
@ -156,7 +183,7 @@ PlacesManager.prototype = {
|
||||
try {
|
||||
networkApp = Shell.AppSystem.get_default().load_from_desktop_file('network-scheme.desktop');
|
||||
} catch(e) {
|
||||
log("Cannot create \"Network\" item, .desktop file not found or corrupt.");
|
||||
log('Cannot create "Network" item, .desktop file not found or corrupt.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -165,15 +192,17 @@ PlacesManager.prototype = {
|
||||
function(size) {
|
||||
return networkApp.create_icon_texture(size);
|
||||
},
|
||||
function () {
|
||||
networkApp.launch();
|
||||
function (params) {
|
||||
params = Params.parse(params, { workspace: null,
|
||||
timestamp: 0 });
|
||||
|
||||
networkApp.launch_full(params.timestamp, [],
|
||||
params.workspace ? params.workspace.index() : -1);
|
||||
});
|
||||
}
|
||||
|
||||
this._defaultPlaces.push(this._home);
|
||||
|
||||
if (!this._isDesktopHome)
|
||||
this._defaultPlaces.push(this._desktopMenu);
|
||||
this._defaultPlaces.push(this._desktopMenu);
|
||||
|
||||
if (this._network)
|
||||
this._defaultPlaces.push(this._network);
|
||||
@ -195,7 +224,7 @@ PlacesManager.prototype = {
|
||||
this._volumeMonitor.connect('drive-changed', Lang.bind(this, this._updateDevices));
|
||||
this._updateDevices();
|
||||
|
||||
this._bookmarksPath = GLib.build_filenamev([GLib.get_home_dir(), ".gtk-bookmarks"]);
|
||||
this._bookmarksPath = GLib.build_filenamev([GLib.get_home_dir(), '.gtk-bookmarks']);
|
||||
this._bookmarksFile = Gio.file_new_for_path(this._bookmarksPath);
|
||||
let monitor = this._bookmarksFile.monitor_file(Gio.FileMonitorFlags.NONE, null);
|
||||
this._bookmarkTimeoutId = 0;
|
||||
@ -211,10 +240,6 @@ PlacesManager.prototype = {
|
||||
}));
|
||||
|
||||
this._reloadBookmarks();
|
||||
this._updateDesktopMenuVisibility();
|
||||
|
||||
gconf.connect('changed::' + DESKTOP_IS_HOME_KEY, Lang.bind(this, this._updateDesktopMenuVisibility));
|
||||
|
||||
},
|
||||
|
||||
_updateDevices: function() {
|
||||
@ -310,10 +335,10 @@ PlacesManager.prototype = {
|
||||
|
||||
let item = new PlaceInfo('bookmark:' + bookmark, label,
|
||||
function(size) {
|
||||
return Shell.TextureCache.get_default().load_gicon(icon, size);
|
||||
return St.TextureCache.get_default().load_gicon(null, icon, size);
|
||||
},
|
||||
function() {
|
||||
Gio.app_info_launch_default_for_uri(bookmark, global.create_app_launch_context());
|
||||
function(params) {
|
||||
Gio.app_info_launch_default_for_uri(bookmark, _makeLaunchContext(params));
|
||||
});
|
||||
this._bookmarks.push(item);
|
||||
}
|
||||
@ -323,15 +348,6 @@ PlacesManager.prototype = {
|
||||
this.emit('places-updated');
|
||||
},
|
||||
|
||||
_updateDesktopMenuVisibility: function() {
|
||||
let gconf = Shell.GConf.get_default();
|
||||
this._isDesktopHome = gconf.get_boolean(DESKTOP_IS_HOME_KEY);
|
||||
|
||||
/* See comment in _updateDevices for explanation why there are two signals. */
|
||||
this.emit('defaults-updated');
|
||||
this.emit('places-updated');
|
||||
},
|
||||
|
||||
_addMount: function(mount) {
|
||||
let devItem = new PlaceDeviceInfo(mount);
|
||||
this._mounts.push(devItem);
|
||||
@ -353,13 +369,13 @@ PlacesManager.prototype = {
|
||||
return this._mounts;
|
||||
},
|
||||
|
||||
_lookupById: function(sourceArray, id) {
|
||||
_lookupIndexById: function(sourceArray, id) {
|
||||
for (let i = 0; i < sourceArray.length; i++) {
|
||||
let place = sourceArray[i];
|
||||
if (place.id == id)
|
||||
return place;
|
||||
return i;
|
||||
}
|
||||
return null;
|
||||
return -1;
|
||||
},
|
||||
|
||||
lookupPlaceById: function(id) {
|
||||
@ -372,141 +388,15 @@ PlacesManager.prototype = {
|
||||
sourceArray = this._mounts;
|
||||
else if (type == 'bookmark')
|
||||
sourceArray = this._bookmarks;
|
||||
return this._lookupById(sourceArray, id);
|
||||
return sourceArray[this._lookupIndexById(sourceArray, id)];
|
||||
},
|
||||
|
||||
_removeById: function(sourceArray, id) {
|
||||
sourceArray.splice(this._lookupIndexById(sourceArray, id), 1);
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(PlacesManager.prototype);
|
||||
|
||||
/**
|
||||
* An entry in the places menu.
|
||||
* @info The corresponding PlaceInfo to populate this entry.
|
||||
*/
|
||||
function DashPlaceDisplayItem(info) {
|
||||
this._init(info);
|
||||
}
|
||||
|
||||
DashPlaceDisplayItem.prototype = {
|
||||
_init: function(info) {
|
||||
this.name = info.name;
|
||||
this._info = info;
|
||||
this._icon = info.iconFactory(PLACES_ICON_SIZE);
|
||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: 4 });
|
||||
let text = new St.Button({ style_class: 'places-item',
|
||||
label: info.name,
|
||||
x_align: St.Align.START });
|
||||
text.connect('clicked', Lang.bind(this, this._onClicked));
|
||||
let iconBox = new St.Bin({ child: this._icon, reactive: true });
|
||||
iconBox.connect('button-release-event',
|
||||
Lang.bind(this, this._onClicked));
|
||||
this.actor.append(iconBox, Big.BoxPackFlags.NONE);
|
||||
this.actor.append(text, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
if (info.isRemovable()) {
|
||||
let removeIcon = Shell.TextureCache.get_default().load_icon_name ('media-eject', PLACES_ICON_SIZE);
|
||||
let removeIconBox = new St.Button({ child: removeIcon,
|
||||
reactive: true });
|
||||
this.actor.append(removeIconBox, Big.BoxPackFlags.NONE);
|
||||
removeIconBox.connect('clicked',
|
||||
Lang.bind(this, function() {
|
||||
this._info.remove();
|
||||
}));
|
||||
}
|
||||
|
||||
this.actor._delegate = this;
|
||||
let draggable = DND.makeDraggable(this.actor);
|
||||
},
|
||||
|
||||
_onClicked: function(b) {
|
||||
this._info.launch();
|
||||
Main.overview.hide();
|
||||
},
|
||||
|
||||
getDragActorSource: function() {
|
||||
return this._icon;
|
||||
},
|
||||
|
||||
getDragActor: function(stageX, stageY) {
|
||||
return this._info.iconFactory(PLACES_ICON_SIZE);
|
||||
},
|
||||
|
||||
//// Drag and drop methods ////
|
||||
|
||||
shellWorkspaceLaunch: function() {
|
||||
this._info.launch();
|
||||
}
|
||||
};
|
||||
|
||||
function DashPlaceDisplay() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
DashPlaceDisplay.prototype = {
|
||||
_init: function() {
|
||||
|
||||
// Places is divided semi-arbitrarily into left and right; a grid would
|
||||
// look better in that there would be an even number of items left+right,
|
||||
// but it seems like we want some sort of differentiation between actions
|
||||
// like "Connect to server..." and regular folders
|
||||
this.actor = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
spacing: 4 });
|
||||
this._leftBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
|
||||
this.actor.append(this._leftBox, Big.BoxPackFlags.EXPAND);
|
||||
this._rightBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL });
|
||||
this.actor.append(this._rightBox, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
// Subdivide left into actions and devices
|
||||
this._actionsBox = new St.BoxLayout({ style_class: 'places-actions',
|
||||
vertical: true });
|
||||
|
||||
this._devBox = new St.BoxLayout({ style_class: 'places-actions',
|
||||
name: 'placesDevices',
|
||||
vertical: true });
|
||||
|
||||
this._dirsBox = new St.BoxLayout({ style_class: 'places-actions',
|
||||
vertical: true });
|
||||
|
||||
this._leftBox.append(this._actionsBox, Big.BoxPackFlags.NONE);
|
||||
this._leftBox.append(this._devBox, Big.BoxPackFlags.NONE);
|
||||
|
||||
this._rightBox.append(this._dirsBox, Big.BoxPackFlags.NONE);
|
||||
|
||||
Main.placesManager.connect('defaults-updated', Lang.bind(this, this._updateDefaults));
|
||||
Main.placesManager.connect('bookmarks-updated', Lang.bind(this, this._updateBookmarks));
|
||||
Main.placesManager.connect('mounts-updated', Lang.bind(this, this._updateMounts));
|
||||
|
||||
this._updateDefaults();
|
||||
this._updateMounts();
|
||||
this._updateBookmarks();
|
||||
},
|
||||
|
||||
_updateDefaults: function() {
|
||||
this._actionsBox.destroy_children();
|
||||
|
||||
let places = Main.placesManager.getDefaultPlaces();
|
||||
for (let i = 0; i < places.length; i++)
|
||||
this._actionsBox.add(new DashPlaceDisplayItem(places[i]).actor);
|
||||
},
|
||||
|
||||
_updateMounts: function() {
|
||||
this._devBox.destroy_children();
|
||||
|
||||
let places = Main.placesManager.getMounts();
|
||||
for (let i = 0; i < places.length; i++)
|
||||
this._devBox.add(new DashPlaceDisplayItem(places[i]).actor);
|
||||
},
|
||||
|
||||
_updateBookmarks: function() {
|
||||
this._dirsBox.destroy_children();
|
||||
|
||||
let places = Main.placesManager.getBookmarks();
|
||||
for (let i = 0; i < places.length; i ++)
|
||||
this._dirsBox.add(new DashPlaceDisplayItem(places[i]).actor);
|
||||
}
|
||||
};
|
||||
|
||||
Signals.addSignalMethods(DashPlaceDisplay.prototype);
|
||||
|
||||
function PlaceSearchProvider() {
|
||||
this._init();
|
||||
@ -516,7 +406,7 @@ PlaceSearchProvider.prototype = {
|
||||
__proto__: Search.SearchProvider.prototype,
|
||||
|
||||
_init: function() {
|
||||
Search.SearchProvider.prototype._init.call(this, _("PLACES"));
|
||||
Search.SearchProvider.prototype._init.call(this, _("PLACES & DEVICES"));
|
||||
},
|
||||
|
||||
getResultMeta: function(resultId) {
|
||||
@ -528,9 +418,9 @@ PlaceSearchProvider.prototype = {
|
||||
'icon': placeInfo.iconFactory(Search.RESULT_ICON_SIZE) };
|
||||
},
|
||||
|
||||
activateResult: function(id) {
|
||||
activateResult: function(id, params) {
|
||||
let placeInfo = Main.placesManager.lookupPlaceById(id);
|
||||
placeInfo.launch();
|
||||
placeInfo.launch(params);
|
||||
},
|
||||
|
||||
_compareResultMeta: function (idA, idB) {
|
||||
@ -540,8 +430,9 @@ PlaceSearchProvider.prototype = {
|
||||
},
|
||||
|
||||
_searchPlaces: function(places, terms) {
|
||||
let multipleResults = [];
|
||||
let multiplePrefixResults = [];
|
||||
let prefixResults = [];
|
||||
let multipleSubstringResults = [];
|
||||
let substringResults = [];
|
||||
|
||||
terms = terms.map(String.toLowerCase);
|
||||
@ -549,17 +440,20 @@ PlaceSearchProvider.prototype = {
|
||||
for (let i = 0; i < places.length; i++) {
|
||||
let place = places[i];
|
||||
let mtype = place.matchTerms(terms);
|
||||
if (mtype == Search.MatchType.MULTIPLE)
|
||||
multipleResults.push(place.id);
|
||||
if (mtype == Search.MatchType.MULTIPLE_PREFIX)
|
||||
multiplePrefixResults.push(place.id);
|
||||
else if (mtype == Search.MatchType.PREFIX)
|
||||
prefixResults.push(place.id);
|
||||
else if (mtype == Search.MatchType.MULTIPLE_SUBSTRING)
|
||||
multipleSubstringResults.push(place.id);
|
||||
else if (mtype == Search.MatchType.SUBSTRING)
|
||||
substringResults.push(place.id);
|
||||
}
|
||||
multipleResults.sort(this._compareResultMeta);
|
||||
multiplePrefixResults.sort(this._compareResultMeta);
|
||||
prefixResults.sort(this._compareResultMeta);
|
||||
multipleSubstringResults.sort(this._compareResultMeta);
|
||||
substringResults.sort(this._compareResultMeta);
|
||||
return multipleResults.concat(prefixResults.concat(substringResults));
|
||||
return multiplePrefixResults.concat(prefixResults.concat(multipleSubstringResults.concat(substringResults)));
|
||||
},
|
||||
|
||||
getInitialResultSet: function(terms) {
|
||||
@ -571,4 +465,4 @@ PlaceSearchProvider.prototype = {
|
||||
let places = previousResults.map(function (id) { return Main.placesManager.lookupPlaceById(id); });
|
||||
return this._searchPlaces(places, terms);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
1262
js/ui/popupMenu.js
Normal file
@ -1,32 +1,29 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Meta = imports.gi.Meta;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Lightbox = imports.ui.lightbox;
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
const Main = imports.ui.main;
|
||||
const ModalDialog = imports.ui.modalDialog;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const BOX_BACKGROUND_COLOR = new Clutter.Color();
|
||||
BOX_BACKGROUND_COLOR.from_pixel(0x000000cc);
|
||||
|
||||
const BOX_TEXT_COLOR = new Clutter.Color();
|
||||
BOX_TEXT_COLOR.from_pixel(0xffffffff);
|
||||
|
||||
const DIALOG_WIDTH = 320;
|
||||
const DIALOG_PADDING = 6;
|
||||
const ICON_SIZE = 24;
|
||||
const ICON_BOX_SIZE = 36;
|
||||
const MAX_FILE_DELETED_BEFORE_INVALID = 10;
|
||||
|
||||
const HISTORY_KEY = 'command-history';
|
||||
const HISTORY_LIMIT = 512;
|
||||
|
||||
const DIALOG_GROW_TIME = 0.1;
|
||||
|
||||
function CommandCompleter() {
|
||||
this._init();
|
||||
}
|
||||
@ -35,6 +32,7 @@ CommandCompleter.prototype = {
|
||||
_init : function() {
|
||||
this._changedCount = 0;
|
||||
this._paths = GLib.getenv('PATH').split(':');
|
||||
this._paths.push(GLib.get_home_dir());
|
||||
this._valid = false;
|
||||
this._updateInProgress = false;
|
||||
this._childs = new Array(this._paths.length);
|
||||
@ -42,7 +40,14 @@ CommandCompleter.prototype = {
|
||||
for (let i = 0; i < this._paths.length; i++) {
|
||||
this._childs[i] = [];
|
||||
let file = Gio.file_new_for_path(this._paths[i]);
|
||||
let info = file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, null);
|
||||
let info;
|
||||
try {
|
||||
info = file.query_info(Gio.FILE_ATTRIBUTE_STANDARD_TYPE, Gio.FileQueryInfoFlags.NONE, null);
|
||||
} catch (e) {
|
||||
// FIXME catchall
|
||||
this._paths[i] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_STANDARD_TYPE) != Gio.FileType.DIRECTORY)
|
||||
continue;
|
||||
@ -50,31 +55,15 @@ CommandCompleter.prototype = {
|
||||
this._paths[i] = file.get_path();
|
||||
this._monitors[i] = file.monitor_directory(Gio.FileMonitorFlags.NONE, null);
|
||||
if (this._monitors[i] != null) {
|
||||
this._monitors[i].connect("changed", Lang.bind(this, this._onChanged));
|
||||
this._monitors[i].connect('changed', Lang.bind(this, this._onChanged));
|
||||
}
|
||||
}
|
||||
this._paths = this._paths.filter(function(a) {
|
||||
return a != null;
|
||||
});
|
||||
this._update(0);
|
||||
},
|
||||
|
||||
_onGetEnumerateComplete : function(obj, res) {
|
||||
this._enumerator = obj.enumerate_children_finish(res);
|
||||
this._enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onNextFileComplete), null);
|
||||
},
|
||||
|
||||
_onNextFileComplete : function(obj, res) {
|
||||
let files = obj.next_files_finish(res);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
this._childs[this._i].push(files[i].get_name());
|
||||
}
|
||||
if (files.length) {
|
||||
this._enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onNextFileComplete), null);
|
||||
} else {
|
||||
this._enumerator.close(null);
|
||||
this._enumerator = null;
|
||||
this._update(this._i + 1);
|
||||
}
|
||||
},
|
||||
|
||||
update : function() {
|
||||
if (this._valid)
|
||||
return;
|
||||
@ -94,7 +83,12 @@ CommandCompleter.prototype = {
|
||||
}
|
||||
let file = Gio.file_new_for_path(this._paths[i]);
|
||||
this._childs[this._i] = [];
|
||||
file.enumerate_children_async(Gio.FILE_ATTRIBUTE_STANDARD_NAME, Gio.FileQueryInfoFlags.NONE, GLib.PRIORITY_LOW, null, Lang.bind(this, this._onGetEnumerateComplete), null);
|
||||
FileUtils.listDirAsync(file, Lang.bind(this, function (files) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
this._childs[this._i].push(files[i].get_name());
|
||||
}
|
||||
this._update(this._i + 1);
|
||||
}));
|
||||
},
|
||||
|
||||
_onChanged : function(m, f, of, type) {
|
||||
@ -128,7 +122,7 @@ CommandCompleter.prototype = {
|
||||
},
|
||||
|
||||
getCompletion: function(text) {
|
||||
let common = "";
|
||||
let common = '';
|
||||
let notInit = true;
|
||||
if (!this._valid) {
|
||||
this._update(0);
|
||||
@ -141,7 +135,7 @@ CommandCompleter.prototype = {
|
||||
break;
|
||||
}
|
||||
if (k == 0)
|
||||
return "";
|
||||
return '';
|
||||
return s1.substr(0, k);
|
||||
}
|
||||
function _hasPrefix(s1, prefix) {
|
||||
@ -166,17 +160,25 @@ CommandCompleter.prototype = {
|
||||
|
||||
function RunDialog() {
|
||||
this._init();
|
||||
};
|
||||
}
|
||||
|
||||
RunDialog.prototype = {
|
||||
__proto__: ModalDialog.ModalDialog.prototype,
|
||||
_init : function() {
|
||||
this._isOpen = false;
|
||||
ModalDialog.ModalDialog.prototype._init.call(this, { styleClass: 'run-dialog' });
|
||||
|
||||
let gconf = Shell.GConf.get_default();
|
||||
gconf.connect('changed::development_tools', Lang.bind(this, function () {
|
||||
this._enableInternalCommands = gconf.get_boolean('development_tools');
|
||||
global.settings.connect('changed::development-tools', Lang.bind(this, function () {
|
||||
this._enableInternalCommands = global.settings.get_boolean('development-tools');
|
||||
}));
|
||||
this._enableInternalCommands = global.settings.get_boolean('development-tools');
|
||||
|
||||
this._history = global.settings.get_strv(HISTORY_KEY);
|
||||
this._historyIndex = -1;
|
||||
|
||||
global.settings.connect('changed::' + HISTORY_KEY, Lang.bind(this, function() {
|
||||
this._history = global.settings.get_strv(HISTORY_KEY);
|
||||
this._historyIndex = this._history.length;
|
||||
}));
|
||||
this._enableInternalCommands = gconf.get_boolean('development_tools');
|
||||
|
||||
this._internalCommands = { 'lg':
|
||||
Lang.bind(this, function() {
|
||||
@ -194,95 +196,71 @@ RunDialog.prototype = {
|
||||
|
||||
'debugexit': Lang.bind(this, function() {
|
||||
Meta.exit(Meta.ExitCode.ERROR);
|
||||
}),
|
||||
|
||||
// rt is short for "reload theme"
|
||||
'rt': Lang.bind(this, function() {
|
||||
Main.loadTheme();
|
||||
})
|
||||
};
|
||||
|
||||
// All actors are inside _group. We create it initially
|
||||
// hidden then show it in show()
|
||||
this._group = new Clutter.Group({ visible: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: global.screen_width,
|
||||
height: global.screen_height });
|
||||
global.stage.add_actor(this._group);
|
||||
|
||||
let lightbox = new Lightbox.Lightbox(this._group);
|
||||
let label = new St.Label({ style_class: 'run-dialog-label',
|
||||
text: _("Please enter a command:") });
|
||||
|
||||
this._boxH = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
x_align: Big.BoxAlignment.CENTER,
|
||||
y_align: Big.BoxAlignment.CENTER });
|
||||
this.contentLayout.add(label, { y_align: St.Align.START });
|
||||
|
||||
this._group.add_actor(this._boxH);
|
||||
lightbox.highlight(this._boxH);
|
||||
let entry = new St.Entry({ style_class: 'run-dialog-entry' });
|
||||
|
||||
let boxV = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
y_align: Big.BoxAlignment.CENTER });
|
||||
this._entryText = entry.clutter_text;
|
||||
this.contentLayout.add(entry, { y_align: St.Align.START });
|
||||
this.connect('opened',
|
||||
Lang.bind(this, function() {
|
||||
this._entryText.grab_key_focus();
|
||||
}));
|
||||
|
||||
this._boxH.append(boxV, Big.BoxPackFlags.NONE);
|
||||
this._errorBox = new St.BoxLayout({ style_class: 'run-dialog-error-box' });
|
||||
|
||||
this.contentLayout.add(this._errorBox, { expand: true });
|
||||
|
||||
let dialogBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
background_color: BOX_BACKGROUND_COLOR,
|
||||
corner_radius: 4,
|
||||
reactive: false,
|
||||
padding: DIALOG_PADDING,
|
||||
width: DIALOG_WIDTH });
|
||||
let errorIcon = new St.Icon({ icon_name: 'dialog-error', icon_size: 24, style_class: 'run-dialog-error-icon' });
|
||||
|
||||
this._boxH.append(dialogBox, Big.BoxPackFlags.NONE);
|
||||
|
||||
let label = new Clutter.Text({ color: BOX_TEXT_COLOR,
|
||||
font_name: '18px Sans',
|
||||
text: _("Please enter a command:") });
|
||||
|
||||
dialogBox.append(label, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
this._entry = new Clutter.Text({ color: BOX_TEXT_COLOR,
|
||||
font_name: '20px Sans Bold',
|
||||
editable: true,
|
||||
activatable: true,
|
||||
singleLineMode: true });
|
||||
|
||||
dialogBox.append(this._entry, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
this._errorBox = new Big.Box({ orientation: Big.BoxOrientation.HORIZONTAL,
|
||||
padding_top: DIALOG_PADDING });
|
||||
|
||||
dialogBox.append(this._errorBox, Big.BoxPackFlags.EXPAND);
|
||||
|
||||
let iconBox = new Big.Box({ orientation: Big.BoxOrientation.VERTICAL,
|
||||
y_align: Big.BoxAlignment.CENTER,
|
||||
x_align: Big.BoxAlignment.CENTER,
|
||||
width: ICON_BOX_SIZE,
|
||||
height: ICON_BOX_SIZE });
|
||||
|
||||
this._errorBox.append(iconBox, Big.BoxPackFlags.NONE);
|
||||
this._errorBox.add(errorIcon, { y_align: St.Align.MIDDLE });
|
||||
|
||||
this._commandError = false;
|
||||
|
||||
let errorIcon = Shell.TextureCache.get_default().load_icon_name("gtk-dialog-error", ICON_SIZE);
|
||||
iconBox.append(errorIcon, Big.BoxPackFlags.EXPAND);
|
||||
this._errorMessage = new St.Label({ style_class: 'run-dialog-error-label' });
|
||||
this._errorMessage.clutter_text.line_wrap = true;
|
||||
|
||||
this._errorMessage = new Clutter.Text({ color: BOX_TEXT_COLOR,
|
||||
font_name: '18px Sans Bold',
|
||||
line_wrap: true });
|
||||
|
||||
this._errorBox.append(this._errorMessage, Big.BoxPackFlags.EXPAND);
|
||||
this._errorBox.add(this._errorMessage, { expand: true,
|
||||
y_align: St.Align.MIDDLE,
|
||||
y_fill: false });
|
||||
|
||||
this._errorBox.hide();
|
||||
|
||||
this._entry.connect('activate', Lang.bind(this, function (o, e) {
|
||||
this._run(o.get_text());
|
||||
if (!this._commandError)
|
||||
this.close();
|
||||
}));
|
||||
|
||||
this._pathCompleter = new Gio.FilenameCompleter();
|
||||
this._commandCompleter = new CommandCompleter();
|
||||
this._group.connect('notify::visible', Lang.bind(this._commandCompleter, this._commandCompleter.update));
|
||||
this._entry.connect('key-press-event', Lang.bind(this, function(o, e) {
|
||||
this._entryText.connect('key-press-event', Lang.bind(this, function(o, e) {
|
||||
let symbol = e.get_key_symbol();
|
||||
if (symbol == Clutter.Down) {
|
||||
this._setCommandFromHistory(this._historyIndex++);
|
||||
return true;
|
||||
}
|
||||
if (symbol == Clutter.Up) {
|
||||
this._setCommandFromHistory(this._historyIndex--);
|
||||
return true;
|
||||
}
|
||||
if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
|
||||
if (Shell.get_event_state(e) & Clutter.ModifierType.CONTROL_MASK)
|
||||
this._run(o.get_text(), true);
|
||||
else
|
||||
this._run(o.get_text(), false);
|
||||
if (!this._commandError)
|
||||
this.close(global.get_current_time());
|
||||
}
|
||||
if (symbol == Clutter.Escape) {
|
||||
this.close();
|
||||
this.close(global.get_current_time());
|
||||
return true;
|
||||
}
|
||||
if (symbol == Clutter.slash) {
|
||||
@ -325,68 +303,99 @@ RunDialog.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_run : function(command) {
|
||||
_saveHistory : function() {
|
||||
if (this._history.length > HISTORY_LIMIT) {
|
||||
this._history.splice(0, this._history.length - HISTORY_LIMIT);
|
||||
}
|
||||
global.settings.set_strv(HISTORY_KEY, this._history);
|
||||
},
|
||||
|
||||
_run : function(input, inTerminal) {
|
||||
let command = input;
|
||||
|
||||
if (this._history.length == 0 ||
|
||||
this._history[this._history.length - 1] != input) {
|
||||
this._history.push(input);
|
||||
this._saveHistory();
|
||||
}
|
||||
|
||||
this._commandError = false;
|
||||
let f;
|
||||
if (this._enableInternalCommands)
|
||||
f = this._internalCommands[command];
|
||||
f = this._internalCommands[input];
|
||||
else
|
||||
f = null;
|
||||
if (f) {
|
||||
f();
|
||||
} else if (command) {
|
||||
} else if (input) {
|
||||
try {
|
||||
let [ok, len, args] = GLib.shell_parse_argv(command);
|
||||
let p = new Shell.Process({'args' : args});
|
||||
p.run();
|
||||
if (inTerminal)
|
||||
command = 'gnome-terminal -x ' + input;
|
||||
Util.trySpawnCommandLine(command);
|
||||
} catch (e) {
|
||||
this._commandError = true;
|
||||
/*
|
||||
* The exception contains an error string like:
|
||||
* Error invoking Shell.run: Failed to execute child process "foo"
|
||||
* (No such file or directory)
|
||||
* We are only interested in the actual error, so parse that out.
|
||||
*/
|
||||
let m = /.+\((.+)\)/.exec(e);
|
||||
let errorStr = _("Execution of '%s' failed:").format(command) + "\n" + m[1];
|
||||
this._errorMessage.set_text(errorStr);
|
||||
this._errorBox.show();
|
||||
// Mmmh, that failed - see if @input matches an existing file
|
||||
let path = null;
|
||||
if (input.charAt(0) == '/') {
|
||||
path = input;
|
||||
} else {
|
||||
if (input.charAt(0) == '~')
|
||||
input = input.slice(1);
|
||||
path = GLib.get_home_dir() + '/' + input;
|
||||
}
|
||||
|
||||
if (GLib.file_test(path, GLib.FileTest.EXISTS)) {
|
||||
let file = Gio.file_new_for_path(path);
|
||||
Gio.app_info_launch_default_for_uri(file.get_uri(),
|
||||
global.create_app_launch_context());
|
||||
} else {
|
||||
this._commandError = true;
|
||||
|
||||
this._errorMessage.set_text(e.message);
|
||||
|
||||
if (!this._errorBox.visible) {
|
||||
let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1);
|
||||
|
||||
let parentActor = this._errorBox.get_parent();
|
||||
Tweener.addTween(parentActor,
|
||||
{ height: parentActor.height + errorBoxNaturalHeight,
|
||||
time: DIALOG_GROW_TIME,
|
||||
transition: 'easeOutQuad',
|
||||
onComplete: Lang.bind(this,
|
||||
function() {
|
||||
parentActor.set_height(-1);
|
||||
this._errorBox.show();
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
open : function() {
|
||||
if (this._isOpen) // Already shown
|
||||
return;
|
||||
_setCommandFromHistory: function(lastI) {
|
||||
if (this._historyIndex < 0)
|
||||
this._historyIndex = 0;
|
||||
if (this._historyIndex > this._history.length)
|
||||
this._historyIndex = this._history.length;
|
||||
|
||||
if (!Main.pushModal(this._group))
|
||||
return;
|
||||
|
||||
// Position the dialog on the current monitor
|
||||
let monitor = global.get_focus_monitor();
|
||||
|
||||
this._boxH.set_position(monitor.x, monitor.y);
|
||||
this._boxH.set_size(monitor.width, monitor.height);
|
||||
|
||||
this._isOpen = true;
|
||||
this._group.show();
|
||||
|
||||
global.stage.set_key_focus(this._entry);
|
||||
let text = this._entryText.get_text();
|
||||
if (text) {
|
||||
this._history[lastI] = text;
|
||||
}
|
||||
if (this._history[this._historyIndex]) {
|
||||
this._entryText.set_text(this._history[this._historyIndex]);
|
||||
} else
|
||||
this._entryText.set_text('');
|
||||
},
|
||||
|
||||
close : function() {
|
||||
if (!this._isOpen)
|
||||
return;
|
||||
|
||||
this._isOpen = false;
|
||||
|
||||
open: function() {
|
||||
this._historyIndex = this._history.length;
|
||||
this._errorBox.hide();
|
||||
this._entryText.set_text('');
|
||||
this._commandError = false;
|
||||
|
||||
this._group.hide();
|
||||
this._entry.set_text('');
|
||||
ModalDialog.ModalDialog.prototype.open.call(this);
|
||||
},
|
||||
|
||||
Main.popModal(this._group);
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(RunDialog.prototype);
|
||||
|
260
js/ui/scripting.js
Normal file
@ -0,0 +1,260 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const Meta = imports.gi.Meta;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
// This module provides functionality for driving the shell user interface
|
||||
// in an automated fashion. The primary current use case for this is
|
||||
// automated performance testing (see runPerfScript()), but it could
|
||||
// be applied to other forms of automation, such as testing for
|
||||
// correctness as well.
|
||||
//
|
||||
// When scripting an automated test we want to make a series of calls
|
||||
// in a linear fashion, but we also want to be able to let the main
|
||||
// loop run so actions can finish. For this reason we write the script
|
||||
// as a generator function that yields when it want to let the main
|
||||
// loop run.
|
||||
//
|
||||
// yield Scripting.sleep(1000);
|
||||
// main.overview.show();
|
||||
// yield Scripting.waitLeisure();
|
||||
//
|
||||
// While it isn't important to the person writing the script, the actual
|
||||
// yielded result is a function that the caller uses to provide the
|
||||
// callback for resuming the script.
|
||||
|
||||
/**
|
||||
* sleep:
|
||||
* @milliseconds: number of milliseconds to wait
|
||||
*
|
||||
* Used within an automation script to pause the the execution of the
|
||||
* current script for the specified amount of time. Use as
|
||||
* 'yield Scripting.sleep(500);'
|
||||
*/
|
||||
function sleep(milliseconds) {
|
||||
let cb;
|
||||
|
||||
Mainloop.timeout_add(milliseconds, function() {
|
||||
if (cb)
|
||||
cb();
|
||||
return false;
|
||||
});
|
||||
|
||||
return function(callback) {
|
||||
cb = callback;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* waitLeisure:
|
||||
*
|
||||
* Used within an automation script to pause the the execution of the
|
||||
* current script until the shell is completely idle. Use as
|
||||
* 'yield Scripting.waitLeisure();'
|
||||
*/
|
||||
function waitLeisure() {
|
||||
let cb;
|
||||
|
||||
global.run_at_leisure(function() {
|
||||
if (cb)
|
||||
cb();
|
||||
});
|
||||
|
||||
return function(callback) {
|
||||
cb = callback;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* defineScriptEvent
|
||||
* @name: The event will be called script.<name>
|
||||
* @description: Short human-readable description of the event
|
||||
*
|
||||
* Convenience function to define a zero-argument performance event
|
||||
* within the 'script' namespace that is reserved for events defined locally
|
||||
* within a performance automation script
|
||||
*/
|
||||
function defineScriptEvent(name, description) {
|
||||
Shell.PerfLog.get_default().define_event("script." + name,
|
||||
description,
|
||||
"");
|
||||
}
|
||||
|
||||
/**
|
||||
* scriptEvent
|
||||
* @name: Name registered with defineScriptEvent()
|
||||
*
|
||||
* Convenience function to record a script-local performance event
|
||||
* previously defined with defineScriptEvent
|
||||
*/
|
||||
function scriptEvent(name) {
|
||||
Shell.PerfLog.get_default().event("script." + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* collectStatistics
|
||||
*
|
||||
* Convenience function to trigger statistics collection
|
||||
*/
|
||||
function collectStatistics() {
|
||||
Shell.PerfLog.get_default().collect_statistics();
|
||||
}
|
||||
|
||||
function _step(g, finish, onError) {
|
||||
try {
|
||||
let waitFunction = g.next();
|
||||
waitFunction(function() {
|
||||
_step(g, finish, onError);
|
||||
});
|
||||
} catch (err if err instanceof StopIteration) {
|
||||
if (finish)
|
||||
finish();
|
||||
} catch (err) {
|
||||
if (onError)
|
||||
onError(err);
|
||||
}
|
||||
}
|
||||
|
||||
function _collect(scriptModule, outputFile) {
|
||||
let eventHandlers = {};
|
||||
|
||||
for (let f in scriptModule) {
|
||||
let m = /([A-Za-z]+)_([A-Za-z]+)/.exec(f);
|
||||
if (m)
|
||||
eventHandlers[m[1] + "." + m[2]] = scriptModule[f];
|
||||
}
|
||||
|
||||
Shell.PerfLog.get_default().replay(
|
||||
function(time, eventName, signature, arg) {
|
||||
if (eventName in eventHandlers)
|
||||
eventHandlers[eventName](time, arg);
|
||||
});
|
||||
|
||||
if ('finish' in scriptModule)
|
||||
scriptModule.finish();
|
||||
|
||||
if (outputFile) {
|
||||
let f = Gio.file_new_for_path(outputFile);
|
||||
let raw = f.replace(null, false,
|
||||
Gio.FileCreateFlags.NONE,
|
||||
null);
|
||||
let out = Gio.BufferedOutputStream.new_sized (raw, 4096);
|
||||
Shell.write_string_to_stream (out, "{\n");
|
||||
|
||||
Shell.write_string_to_stream(out, '"events":\n');
|
||||
Shell.PerfLog.get_default().dump_events(out);
|
||||
|
||||
let monitors = global.get_monitors()
|
||||
let primary = global.get_primary_monitor()
|
||||
Shell.write_string_to_stream(out, ',\n"monitors":\n[');
|
||||
for (let i = 0; i < monitors.length; i++) {
|
||||
let monitor = monitors[i];
|
||||
let is_primary = (monitor.x == primary.x &&
|
||||
monitor.y == primary.y &&
|
||||
monitor.width == primary.width &&
|
||||
monitor.height == primary.height);
|
||||
if (i != 0)
|
||||
Shell.write_string_to_stream(out, ', ');
|
||||
Shell.write_string_to_stream(out, '"%s%dx%d+%d+%d"'.format(is_primary ? "*" : "",
|
||||
monitor.width, monitor.height,
|
||||
monitor.x, monitor.y));
|
||||
}
|
||||
Shell.write_string_to_stream(out, ' ]');
|
||||
|
||||
Shell.write_string_to_stream(out, ',\n"metrics":\n[ ');
|
||||
let first = true;
|
||||
for (let name in scriptModule.METRICS) {
|
||||
let metric = scriptModule.METRICS[name];
|
||||
|
||||
if (!first)
|
||||
Shell.write_string_to_stream(out, ',\n ');
|
||||
first = false;
|
||||
|
||||
Shell.write_string_to_stream(out,
|
||||
'{ "name": ' + JSON.stringify(name) + ',\n' +
|
||||
' "description": ' + JSON.stringify(metric.description) + ',\n' +
|
||||
' "units": ' + JSON.stringify(metric.units) + ',\n' +
|
||||
' "value": ' + JSON.stringify(metric.value) + ' }');
|
||||
}
|
||||
Shell.write_string_to_stream(out, ' ]');
|
||||
|
||||
Shell.write_string_to_stream (out, ',\n"log":\n');
|
||||
Shell.PerfLog.get_default().dump_log(out);
|
||||
|
||||
Shell.write_string_to_stream (out, '\n}\n');
|
||||
out.close(null);
|
||||
} else {
|
||||
let metrics = [];
|
||||
for (let metric in scriptModule.METRICS)
|
||||
metrics.push(metric);
|
||||
|
||||
metrics.sort();
|
||||
|
||||
print ('------------------------------------------------------------');
|
||||
for (let i = 0; i < metrics.length; i++) {
|
||||
let metric = metrics[i];
|
||||
print ('# ' + scriptModule.METRIC_DESCRIPTIONS[metric]);
|
||||
print (metric + ': ' + scriptModule.METRICS[metric]);
|
||||
}
|
||||
print ('------------------------------------------------------------');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* runPerfScript
|
||||
* @scriptModule: module object with run and finish functions
|
||||
* and event handlers
|
||||
*
|
||||
* Runs a script for automated collection of performance data. The
|
||||
* script is defined as a Javascript module with specified contents.
|
||||
*
|
||||
* First the run() function within the module will be called as a
|
||||
* generator to automate a series of actions. These actions will
|
||||
* trigger performance events and the script can also record its
|
||||
* own performance events.
|
||||
*
|
||||
* Then the recorded event log is replayed using handler functions
|
||||
* within the module. The handler for the event 'foo.bar' is called
|
||||
* foo_bar().
|
||||
*
|
||||
* Finally if the module has a function called finish(), that will
|
||||
* be called.
|
||||
*
|
||||
* The event handler and finish functions are expected to fill in
|
||||
* metrics to an object within the module called METRICS. Each
|
||||
* property of this object represents an individual metric. The
|
||||
* name of the property is the name of the metric, the value
|
||||
* of the property is an object with the following properties:
|
||||
*
|
||||
* description: human readable description of the metric
|
||||
* units: a string representing the units of the metric. It has
|
||||
* the form '<unit> <unit> ... / <unit> / <unit> ...'. Certain
|
||||
* unit values are recognized: s, ms, us, B, KiB, MiB. Other
|
||||
* values can appear but are uninterpreted. Examples 's',
|
||||
* '/ s', 'frames', 'frames / s', 'MiB / s / frame'
|
||||
* value: computed value of the metric
|
||||
*
|
||||
* The resulting metrics will be written to @outputFile as JSON, or,
|
||||
* if @outputFile is not provided, logged.
|
||||
*
|
||||
* After running the script and collecting statistics from the
|
||||
* event log, GNOME Shell will exit.
|
||||
**/
|
||||
function runPerfScript(scriptModule, outputFile) {
|
||||
Shell.PerfLog.get_default().set_enabled(true);
|
||||
|
||||
let g = scriptModule.run();
|
||||
|
||||
_step(g,
|
||||
function() {
|
||||
_collect(scriptModule, outputFile);
|
||||
Meta.exit(Meta.ExitCode.SUCCESS);
|
||||
},
|
||||
function(err) {
|
||||
log("Script failed: " + err + "\n" + err.stack);
|
||||
Meta.exit(Meta.ExitCode.ERROR);
|
||||
});
|
||||
}
|
180
js/ui/search.js
@ -1,18 +1,31 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const RESULT_ICON_SIZE = 24;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const FileUtils = imports.misc.fileUtils;
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const DISABLED_OPEN_SEARCH_PROVIDERS_KEY = 'disabled-open-search-providers';
|
||||
|
||||
const RESULT_ICON_SIZE = 48;
|
||||
|
||||
// Not currently referenced by the search API, but
|
||||
// this enumeration can be useful for provider
|
||||
// implementations.
|
||||
const MatchType = {
|
||||
NONE: 0,
|
||||
MULTIPLE: 1,
|
||||
PREFIX: 2,
|
||||
SUBSTRING: 3
|
||||
SUBSTRING: 1,
|
||||
MULTIPLE_SUBSTRING: 2,
|
||||
PREFIX: 3,
|
||||
MULTIPLE_PREFIX: 4
|
||||
};
|
||||
|
||||
function SearchResultDisplay(provider) {
|
||||
@ -40,7 +53,7 @@ SearchResultDisplay.prototype = {
|
||||
* The terms are useful for search match highlighting.
|
||||
*/
|
||||
renderResults: function(results, terms) {
|
||||
throw new Error("not implemented");
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
@ -67,7 +80,7 @@ SearchResultDisplay.prototype = {
|
||||
* Returns: The number of actors visible.
|
||||
*/
|
||||
getVisibleResultCount: function() {
|
||||
throw new Error("not implemented");
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
@ -79,7 +92,14 @@ SearchResultDisplay.prototype = {
|
||||
* available.
|
||||
*/
|
||||
selectIndex: function() {
|
||||
throw new Error("not implemented");
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
* Activate the currently selected search result.
|
||||
*/
|
||||
activateSelected: function() {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
};
|
||||
|
||||
@ -101,7 +121,7 @@ SearchProvider.prototype = {
|
||||
|
||||
/**
|
||||
* getInitialResultSet:
|
||||
* @terms: Array of search terms, treated as logical OR
|
||||
* @terms: Array of search terms, treated as logical AND
|
||||
*
|
||||
* Called when the user first begins a search (most likely
|
||||
* therefore a single term of length one or two), or when
|
||||
@ -113,14 +133,15 @@ SearchProvider.prototype = {
|
||||
* item. Ordering of returned results is up to the discretion of the provider,
|
||||
* but you should follow these heruistics:
|
||||
*
|
||||
* * Put items which match multiple search terms before single matches
|
||||
* * Put items where the term matches multiple criteria (e.g. name and
|
||||
* description) before single matches
|
||||
* * Put items which match on a prefix before non-prefix substring matches
|
||||
*
|
||||
* This function should be fast; do not perform unindexed full-text searches
|
||||
* or network queries.
|
||||
*/
|
||||
getInitialResultSet: function(terms) {
|
||||
throw new Error("not implemented");
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
@ -137,7 +158,7 @@ SearchProvider.prototype = {
|
||||
* result set, rather than possibly performing a full re-query.
|
||||
*/
|
||||
getSubsearchResultSet: function(previousResults, newTerms) {
|
||||
throw new Error("not implemented");
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
@ -148,7 +169,7 @@ SearchProvider.prototype = {
|
||||
* properties which describe the given search result.
|
||||
*/
|
||||
getResultMeta: function(id) {
|
||||
throw new Error("not implemented");
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
@ -174,7 +195,7 @@ SearchProvider.prototype = {
|
||||
* implementation will show the icon next to the name.
|
||||
*
|
||||
* The actor should be an instance of St.Widget, with the style class
|
||||
* 'dash-search-result-content'.
|
||||
* 'search-result-content'.
|
||||
*/
|
||||
createResultActor: function(resultMeta, terms) {
|
||||
return null;
|
||||
@ -187,7 +208,7 @@ SearchProvider.prototype = {
|
||||
* Called when the user chooses a given result.
|
||||
*/
|
||||
activateResult: function(id) {
|
||||
throw new Error("not implemented");
|
||||
throw new Error('Not implemented');
|
||||
},
|
||||
|
||||
/**
|
||||
@ -198,10 +219,111 @@ SearchProvider.prototype = {
|
||||
* displaying search results for that item type.
|
||||
*/
|
||||
expandSearch: function(terms) {
|
||||
throw new Error("not implemented");
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(SearchProvider.prototype);
|
||||
|
||||
function OpenSearchSystem() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
OpenSearchSystem.prototype = {
|
||||
_init: function() {
|
||||
this._providers = [];
|
||||
global.settings.connect('changed::' + DISABLED_OPEN_SEARCH_PROVIDERS_KEY, Lang.bind(this, this._refresh));
|
||||
this._refresh();
|
||||
},
|
||||
|
||||
getProviders: function() {
|
||||
let res = [];
|
||||
for (let i = 0; i < this._providers.length; i++)
|
||||
res.push({ id: i, name: this._providers[i].name });
|
||||
|
||||
return res;
|
||||
},
|
||||
|
||||
setSearchTerms: function(terms) {
|
||||
this._terms = terms;
|
||||
},
|
||||
|
||||
_checkSupportedProviderLanguage: function(provider) {
|
||||
if (provider.url.search(/{language}/) == -1)
|
||||
return true;
|
||||
|
||||
let langs = GLib.get_language_names();
|
||||
|
||||
langs.push('en');
|
||||
let lang = null;
|
||||
for (let i = 0; i < langs.length; i++) {
|
||||
for (let k = 0; k < provider.langs.length; k++) {
|
||||
if (langs[i] == provider.langs[k])
|
||||
lang = langs[i];
|
||||
}
|
||||
if (lang)
|
||||
break;
|
||||
}
|
||||
provider.lang = lang;
|
||||
return lang != null;
|
||||
},
|
||||
|
||||
activateResult: function(id, params) {
|
||||
let searchTerms = this._terms.join(' ');
|
||||
|
||||
let url = this._providers[id].url.replace('{searchTerms}', encodeURIComponent(searchTerms));
|
||||
if (url.match('{language}'))
|
||||
url = url.replace('{language}', this._providers[id].lang);
|
||||
|
||||
try {
|
||||
Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context());
|
||||
} catch (e) {
|
||||
// TODO: remove this after glib will be removed from moduleset
|
||||
// In the default jhbuild, gio is in our prefix but gvfs is not
|
||||
Util.spawn(['gvfs-open', url])
|
||||
}
|
||||
|
||||
Main.overview.hide();
|
||||
},
|
||||
|
||||
_addProvider: function(fileName) {
|
||||
let file = Gio.file_new_for_path(global.datadir + '/search_providers/' + fileName);
|
||||
let source = '';
|
||||
|
||||
file.load_contents_async(null, Lang.bind(this, function (obj, res) {
|
||||
let [success, source] = file.load_contents_finish(res);
|
||||
if (source) {
|
||||
let [success, name, url, langs, icon_uri] = global.parse_search_provider(source);
|
||||
let provider ={ name: name,
|
||||
url: url,
|
||||
id: this._providers.length,
|
||||
icon_uri: icon_uri,
|
||||
langs: langs };
|
||||
if (this._checkSupportedProviderLanguage(provider)) {
|
||||
this._providers.push(provider);
|
||||
this.emit('changed');
|
||||
}
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_refresh: function() {
|
||||
this._providers = [];
|
||||
let names = global.settings.get_strv(DISABLED_OPEN_SEARCH_PROVIDERS_KEY);
|
||||
let file = Gio.file_new_for_path(global.datadir + '/search_providers');
|
||||
FileUtils.listDirAsync(file, Lang.bind(this, function(files) {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
let enabled = true;
|
||||
let name = files[i].get_name();
|
||||
for (let k = 0; k < names.length; k++)
|
||||
if (names[k] == name)
|
||||
enabled = false;
|
||||
if (enabled)
|
||||
this._addProvider(name);
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
Signals.addSignalMethods(SearchProvider.prototype);
|
||||
Signals.addSignalMethods(OpenSearchSystem.prototype);
|
||||
|
||||
function SearchSystem() {
|
||||
this._init();
|
||||
@ -231,7 +353,7 @@ SearchSystem.prototype = {
|
||||
},
|
||||
|
||||
updateSearch: function(searchString) {
|
||||
searchString = searchString.replace(/^\s+/g, "").replace(/\s+$/g, "");
|
||||
searchString = searchString.replace(/^\s+/g, '').replace(/\s+$/g, '');
|
||||
if (searchString == '')
|
||||
return null;
|
||||
|
||||
@ -250,16 +372,24 @@ SearchSystem.prototype = {
|
||||
if (isSubSearch) {
|
||||
for (let i = 0; i < this._previousResults.length; i++) {
|
||||
let [provider, previousResults] = this._previousResults[i];
|
||||
let providerResults = provider.getSubsearchResultSet(previousResults, terms);
|
||||
if (providerResults.length > 0)
|
||||
results.push([provider, providerResults]);
|
||||
try {
|
||||
let providerResults = provider.getSubsearchResultSet(previousResults, terms);
|
||||
if (providerResults.length > 0)
|
||||
results.push([provider, providerResults]);
|
||||
} catch (error) {
|
||||
global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < this._providers.length; i++) {
|
||||
let provider = this._providers[i];
|
||||
let providerResults = provider.getInitialResultSet(terms);
|
||||
if (providerResults.length > 0)
|
||||
results.push([provider, providerResults]);
|
||||
try {
|
||||
let providerResults = provider.getInitialResultSet(terms);
|
||||
if (providerResults.length > 0)
|
||||
results.push([provider, providerResults]);
|
||||
} catch (error) {
|
||||
global.log ('A ' + error.name + ' has occured in ' + provider.title + ': ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,5 +398,5 @@ SearchSystem.prototype = {
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(SearchSystem.prototype);
|
||||
|
425
js/ui/searchDisplay.js
Normal file
@ -0,0 +1,425 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Lang = imports.lang;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const DND = imports.ui.dnd;
|
||||
const IconGrid = imports.ui.iconGrid;
|
||||
const Main = imports.ui.main;
|
||||
const Overview = imports.ui.overview;
|
||||
const Search = imports.ui.search;
|
||||
|
||||
const MAX_SEARCH_RESULTS_ROWS = 2;
|
||||
|
||||
|
||||
function SearchResult(provider, metaInfo, terms) {
|
||||
this._init(provider, metaInfo, terms);
|
||||
}
|
||||
|
||||
SearchResult.prototype = {
|
||||
_init: function(provider, metaInfo, terms) {
|
||||
this.provider = provider;
|
||||
this.metaInfo = metaInfo;
|
||||
this.actor = new St.Clickable({ style_class: 'search-result',
|
||||
reactive: true,
|
||||
x_align: St.Align.START,
|
||||
y_fill: true });
|
||||
this.actor._delegate = this;
|
||||
|
||||
let content = provider.createResultActor(metaInfo, terms);
|
||||
if (content == null) {
|
||||
content = new St.Bin({ style_class: 'search-result-content',
|
||||
reactive: true,
|
||||
track_hover: true });
|
||||
let icon = new IconGrid.BaseIcon(this.metaInfo['name'],
|
||||
{ createIcon: Lang.bind(this, function(size) {
|
||||
return this.metaInfo['icon'];
|
||||
})});
|
||||
content.set_child(icon.actor);
|
||||
}
|
||||
this._content = content;
|
||||
this.actor.set_child(content);
|
||||
|
||||
this.actor.connect('clicked', Lang.bind(this, this._onResultClicked));
|
||||
|
||||
let draggable = DND.makeDraggable(this.actor);
|
||||
draggable.connect('drag-begin',
|
||||
Lang.bind(this, function() {
|
||||
Main.overview.beginItemDrag(this);
|
||||
}));
|
||||
draggable.connect('drag-end',
|
||||
Lang.bind(this, function() {
|
||||
Main.overview.endItemDrag(this);
|
||||
}));
|
||||
},
|
||||
|
||||
setSelected: function(selected) {
|
||||
if (selected)
|
||||
this._content.add_style_pseudo_class('selected');
|
||||
else
|
||||
this._content.remove_style_pseudo_class('selected');
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.provider.activateResult(this.metaInfo.id);
|
||||
Main.overview.toggle();
|
||||
},
|
||||
|
||||
_onResultClicked: function(actor, event) {
|
||||
this.activate();
|
||||
},
|
||||
|
||||
getDragActorSource: function() {
|
||||
return this.metaInfo['icon'];
|
||||
},
|
||||
|
||||
getDragActor: function(stageX, stageY) {
|
||||
return new Clutter.Clone({ source: this.metaInfo['icon'] });
|
||||
},
|
||||
|
||||
shellWorkspaceLaunch: function(params) {
|
||||
if (this.provider.dragActivateResult)
|
||||
this.provider.dragActivateResult(this.metaInfo.id, params);
|
||||
else
|
||||
this.provider.activateResult(this.metaInfo.id, params);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function GridSearchResults(provider) {
|
||||
this._init(provider);
|
||||
}
|
||||
|
||||
GridSearchResults.prototype = {
|
||||
__proto__: Search.SearchResultDisplay.prototype,
|
||||
|
||||
_init: function(provider) {
|
||||
Search.SearchResultDisplay.prototype._init.call(this, provider);
|
||||
this._grid = new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS,
|
||||
xAlign: St.Align.START });
|
||||
this.actor = new St.Bin({ x_align: St.Align.START });
|
||||
this.actor.set_child(this._grid.actor);
|
||||
this.selectionIndex = -1;
|
||||
},
|
||||
|
||||
getVisibleResultCount: function() {
|
||||
return this._grid.visibleItemsCount();
|
||||
},
|
||||
|
||||
renderResults: function(results, terms) {
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let result = results[i];
|
||||
let meta = this.provider.getResultMeta(result);
|
||||
let display = new SearchResult(this.provider, meta, terms);
|
||||
this._grid.addItem(display.actor);
|
||||
}
|
||||
},
|
||||
|
||||
clear: function () {
|
||||
this._grid.removeAll();
|
||||
this.selectionIndex = -1;
|
||||
},
|
||||
|
||||
selectIndex: function (index) {
|
||||
let nVisible = this.getVisibleResultCount();
|
||||
if (this.selectionIndex >= 0) {
|
||||
let prevActor = this._grid.getItemAtIndex(this.selectionIndex);
|
||||
prevActor._delegate.setSelected(false);
|
||||
}
|
||||
this.selectionIndex = -1;
|
||||
if (index >= nVisible)
|
||||
return false;
|
||||
else if (index < 0)
|
||||
return false;
|
||||
let targetActor = this._grid.getItemAtIndex(index);
|
||||
targetActor._delegate.setSelected(true);
|
||||
this.selectionIndex = index;
|
||||
return true;
|
||||
},
|
||||
|
||||
activateSelected: function() {
|
||||
if (this.selectionIndex < 0)
|
||||
return;
|
||||
let targetActor = this._grid.getItemAtIndex(this.selectionIndex);
|
||||
targetActor._delegate.activate();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function SearchResults(searchSystem, openSearchSystem) {
|
||||
this._init(searchSystem, openSearchSystem);
|
||||
}
|
||||
|
||||
SearchResults.prototype = {
|
||||
_init: function(searchSystem, openSearchSystem) {
|
||||
this._searchSystem = searchSystem;
|
||||
this._openSearchSystem = openSearchSystem;
|
||||
|
||||
this.actor = new St.BoxLayout({ name: 'searchResults',
|
||||
vertical: true });
|
||||
|
||||
this._content = new St.BoxLayout({ name: 'searchResultsContent',
|
||||
vertical: true });
|
||||
|
||||
let scrollView = new St.ScrollView({ x_fill: true,
|
||||
y_fill: false,
|
||||
vfade: true });
|
||||
scrollView.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
|
||||
scrollView.add_actor(this._content);
|
||||
|
||||
this.actor.add(scrollView, { x_fill: true,
|
||||
y_fill: false,
|
||||
expand: true,
|
||||
x_align: St.Align.START,
|
||||
y_align: St.Align.START });
|
||||
this.actor.connect('notify::mapped', Lang.bind(this,
|
||||
function() {
|
||||
if (!this.actor.mapped)
|
||||
return;
|
||||
|
||||
let adjustment = scrollView.vscroll.adjustment;
|
||||
let direction = Overview.SwipeScrollDirection.VERTICAL;
|
||||
Main.overview.setScrollAdjustment(adjustment, direction);
|
||||
}));
|
||||
|
||||
this._statusText = new St.Label({ style_class: 'search-statustext' });
|
||||
this._content.add(this._statusText);
|
||||
this._selectedProvider = -1;
|
||||
this._providers = this._searchSystem.getProviders();
|
||||
this._providerMeta = [];
|
||||
for (let i = 0; i < this._providers.length; i++)
|
||||
this.createProviderMeta(this._providers[i]);
|
||||
|
||||
this._searchProvidersBox = new St.BoxLayout({ style_class: 'search-providers-box' });
|
||||
this.actor.add(this._searchProvidersBox);
|
||||
|
||||
this._openSearchProviders = [];
|
||||
this._openSearchSystem.connect('changed', Lang.bind(this, this._updateOpenSearchProviderButtons));
|
||||
this._updateOpenSearchProviderButtons();
|
||||
},
|
||||
|
||||
_updateOpenSearchProviderButtons: function() {
|
||||
this._selectedOpenSearchButton = -1;
|
||||
for (let i = 0; i < this._openSearchProviders.length; i++)
|
||||
this._openSearchProviders[i].actor.destroy();
|
||||
this._openSearchProviders = this._openSearchSystem.getProviders();
|
||||
for (let i = 0; i < this._openSearchProviders.length; i++)
|
||||
this._createOpenSearchProviderButton(this._openSearchProviders[i]);
|
||||
},
|
||||
|
||||
_updateOpenSearchButtonState: function() {
|
||||
for (let i = 0; i < this._openSearchProviders.length; i++) {
|
||||
if (i == this._selectedOpenSearchButton)
|
||||
this._openSearchProviders[i].actor.add_style_pseudo_class('selected');
|
||||
else
|
||||
this._openSearchProviders[i].actor.remove_style_pseudo_class('selected');
|
||||
}
|
||||
},
|
||||
|
||||
_createOpenSearchProviderButton: function(provider) {
|
||||
let clickable = new St.Clickable({ style_class: 'dash-search-button',
|
||||
reactive: true,
|
||||
x_fill: true,
|
||||
y_align: St.Align.MIDDLE });
|
||||
let bin = new St.Bin({ x_fill: false,
|
||||
x_align:St.Align.MIDDLE });
|
||||
clickable.connect('clicked', Lang.bind(this, function() {
|
||||
this._openSearchSystem.activateResult(provider.id);
|
||||
}));
|
||||
let title = new St.Label({ text: provider.name,
|
||||
style_class: 'dash-search-button-label' });
|
||||
|
||||
bin.set_child(title);
|
||||
clickable.set_child(bin);
|
||||
provider.actor = clickable;
|
||||
|
||||
this._searchProvidersBox.add(clickable);
|
||||
},
|
||||
|
||||
createProviderMeta: function(provider) {
|
||||
let providerBox = new St.BoxLayout({ style_class: 'search-section',
|
||||
vertical: true });
|
||||
let titleButton = new St.Button({ style_class: 'search-section-header',
|
||||
reactive: true,
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
titleButton.connect('clicked', Lang.bind(this, function () { this._onHeaderClicked(provider); }));
|
||||
providerBox.add(titleButton);
|
||||
let titleBox = new St.BoxLayout();
|
||||
titleButton.set_child(titleBox);
|
||||
let title = new St.Label({ text: provider.title });
|
||||
let count = new St.Label();
|
||||
titleBox.add(title, { expand: true });
|
||||
titleBox.add(count);
|
||||
|
||||
let resultDisplayBin = new St.Bin({ style_class: 'search-section-results',
|
||||
x_fill: true,
|
||||
y_fill: true });
|
||||
providerBox.add(resultDisplayBin, { expand: true });
|
||||
let resultDisplay = provider.createResultContainerActor();
|
||||
if (resultDisplay == null) {
|
||||
resultDisplay = new GridSearchResults(provider);
|
||||
}
|
||||
resultDisplayBin.set_child(resultDisplay.actor);
|
||||
|
||||
this._providerMeta.push({ actor: providerBox,
|
||||
resultDisplay: resultDisplay,
|
||||
count: count });
|
||||
this._content.add(providerBox);
|
||||
},
|
||||
|
||||
_clearDisplay: function() {
|
||||
this._selectedProvider = -1;
|
||||
this._visibleResultsCount = 0;
|
||||
for (let i = 0; i < this._providerMeta.length; i++) {
|
||||
let meta = this._providerMeta[i];
|
||||
meta.resultDisplay.clear();
|
||||
meta.actor.hide();
|
||||
}
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
this._searchSystem.reset();
|
||||
this._statusText.hide();
|
||||
this._clearDisplay();
|
||||
this._selectedOpenSearchButton = -1;
|
||||
this._updateOpenSearchButtonState();
|
||||
},
|
||||
|
||||
startingSearch: function() {
|
||||
this.reset();
|
||||
this._statusText.set_text(_("Searching..."));
|
||||
this._statusText.show();
|
||||
},
|
||||
|
||||
_metaForProvider: function(provider) {
|
||||
return this._providerMeta[this._providers.indexOf(provider)];
|
||||
},
|
||||
|
||||
updateSearch: function (searchString) {
|
||||
let results = this._searchSystem.updateSearch(searchString);
|
||||
|
||||
this._clearDisplay();
|
||||
|
||||
if (results.length == 0) {
|
||||
this._statusText.set_text(_("No matching results."));
|
||||
this._statusText.show();
|
||||
} else {
|
||||
this._selectedOpenSearchButton = -1;
|
||||
this._updateOpenSearchButtonState();
|
||||
this._statusText.hide();
|
||||
}
|
||||
|
||||
let terms = this._searchSystem.getTerms();
|
||||
this._openSearchSystem.setSearchTerms(terms);
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
let [provider, providerResults] = results[i];
|
||||
let meta = this._metaForProvider(provider);
|
||||
meta.actor.show();
|
||||
meta.resultDisplay.renderResults(providerResults, terms);
|
||||
meta.count.set_text('' + providerResults.length);
|
||||
}
|
||||
|
||||
if (this._selectedOpenSearchButton == -1)
|
||||
this.selectDown(false);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_onHeaderClicked: function(provider) {
|
||||
provider.expandSearch(this._searchSystem.getTerms());
|
||||
},
|
||||
|
||||
_modifyActorSelection: function(resultDisplay, up) {
|
||||
let success;
|
||||
let index = resultDisplay.getSelectionIndex();
|
||||
if (up && index == -1)
|
||||
index = resultDisplay.getVisibleResultCount() - 1;
|
||||
else if (up)
|
||||
index = index - 1;
|
||||
else
|
||||
index = index + 1;
|
||||
return resultDisplay.selectIndex(index);
|
||||
},
|
||||
|
||||
selectUp: function(recursing) {
|
||||
if (this._selectedOpenSearchButton == -1) {
|
||||
for (let i = this._selectedProvider; i >= 0; i--) {
|
||||
let meta = this._providerMeta[i];
|
||||
if (!meta.actor.visible)
|
||||
continue;
|
||||
let success = this._modifyActorSelection(meta.resultDisplay, true);
|
||||
if (success) {
|
||||
this._selectedProvider = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this._selectedOpenSearchButton == -1)
|
||||
this._selectedOpenSearchButton = this._openSearchProviders.length;
|
||||
this._selectedOpenSearchButton--;
|
||||
this._updateOpenSearchButtonState();
|
||||
if (this._selectedOpenSearchButton >= 0)
|
||||
return;
|
||||
|
||||
if (this._providerMeta.length > 0 && !recursing) {
|
||||
this._selectedProvider = this._providerMeta.length - 1;
|
||||
this.selectUp(true);
|
||||
}
|
||||
},
|
||||
|
||||
selectDown: function(recursing) {
|
||||
let current = this._selectedProvider;
|
||||
if (this._selectedOpenSearchButton == -1) {
|
||||
if (current == -1)
|
||||
current = 0;
|
||||
for (let i = current; i < this._providerMeta.length; i++) {
|
||||
let meta = this._providerMeta[i];
|
||||
if (!meta.actor.visible)
|
||||
continue;
|
||||
let success = this._modifyActorSelection(meta.resultDisplay, false);
|
||||
if (success) {
|
||||
this._selectedProvider = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._selectedOpenSearchButton++;
|
||||
|
||||
if (this._selectedOpenSearchButton < this._openSearchProviders.length) {
|
||||
this._updateOpenSearchButtonState();
|
||||
return;
|
||||
}
|
||||
|
||||
this._selectedOpenSearchButton = -1;
|
||||
this._updateOpenSearchButtonState();
|
||||
|
||||
if (this._providerMeta.length > 0 && !recursing) {
|
||||
this._selectedProvider = 0;
|
||||
this.selectDown(true);
|
||||
}
|
||||
},
|
||||
|
||||
activateSelected: function() {
|
||||
if (this._selectedOpenSearchButton != -1) {
|
||||
let provider = this._openSearchProviders[this._selectedOpenSearchButton];
|
||||
this._openSearchSystem.activateResult(provider.id);
|
||||
Main.overview.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let current = this._selectedProvider;
|
||||
if (current < 0)
|
||||
return;
|
||||
let meta = this._providerMeta[current];
|
||||
let resultDisplay = meta.resultDisplay;
|
||||
resultDisplay.activateSelected();
|
||||
Main.overview.hide();
|
||||
}
|
||||
};
|
@ -1,23 +1,20 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Mainloop = imports.mainloop;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
|
||||
const GnomeShellIface = {
|
||||
name: "org.gnome.Shell",
|
||||
methods: [{ name: "Eval",
|
||||
inSignature: "s",
|
||||
outSignature: "bs"
|
||||
name: 'org.gnome.Shell',
|
||||
methods: [{ name: 'Eval',
|
||||
inSignature: 's',
|
||||
outSignature: 'bs'
|
||||
}
|
||||
],
|
||||
signals: [],
|
||||
properties: [{ name: "OverviewActive",
|
||||
signature: "b",
|
||||
access: "readwrite" }]
|
||||
properties: [{ name: 'OverviewActive',
|
||||
signature: 'b',
|
||||
access: 'readwrite' }]
|
||||
};
|
||||
|
||||
function GnomeShell() {
|
||||
@ -50,7 +47,7 @@ GnomeShell.prototype = {
|
||||
returnValue = JSON.stringify(eval(code));
|
||||
// A hack; DBus doesn't have null/undefined
|
||||
if (returnValue == undefined)
|
||||
returnValue = "";
|
||||
returnValue = '';
|
||||
success = true;
|
||||
} catch (e) {
|
||||
returnValue = JSON.stringify(e);
|
||||
|
186
js/ui/sidebar.js
@ -1,186 +0,0 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Big = imports.gi.Big;
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Lang = imports.lang;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const Panel = imports.ui.panel;
|
||||
const Tweener = imports.ui.tweener;
|
||||
const Widget = imports.ui.widget;
|
||||
const WidgetBox = imports.ui.widgetBox;
|
||||
|
||||
const SIDEBAR_SPACING = 4;
|
||||
const SIDEBAR_PADDING = 4;
|
||||
|
||||
// The total sidebar width is the widget width plus the widget padding
|
||||
// (counted twice for the widget box, and once again for the
|
||||
// out-of-screen padding), plus the empty space between the border of
|
||||
// the bar and of the windows
|
||||
const SIDEBAR_COLLAPSED_WIDTH = Widget.COLLAPSED_WIDTH + 3 * WidgetBox.WIDGETBOX_PADDING + SIDEBAR_PADDING;
|
||||
const SIDEBAR_EXPANDED_WIDTH = Widget.EXPANDED_WIDTH + 3 * WidgetBox.WIDGETBOX_PADDING + SIDEBAR_PADDING;
|
||||
|
||||
function Sidebar() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
Sidebar.prototype = {
|
||||
_init : function() {
|
||||
// The top-left corner of the sidebar is fixed at:
|
||||
// x = -WidgetBox.WIDGETBOX_PADDING, y = Panel.PANEL_HEIGHT.
|
||||
// (The negative X is so that we don't see the rounded
|
||||
// WidgetBox corners on the screen edge side.)
|
||||
this.actor = new Clutter.Group({ x: -WidgetBox.WIDGETBOX_PADDING,
|
||||
y: Panel.PANEL_HEIGHT,
|
||||
width: SIDEBAR_EXPANDED_WIDTH });
|
||||
|
||||
// The actual widgets go into a Big.Box inside this.actor. The
|
||||
// box's width will vary during the expand/collapse animations,
|
||||
// but this.actor's width will remain constant until we adjust
|
||||
// it at the end of the animation, because we don't want the
|
||||
// wm strut to move and cause windows to move multiple times
|
||||
// during the animation.
|
||||
this.box = new Big.Box ({ padding_top: SIDEBAR_PADDING,
|
||||
padding_bottom: SIDEBAR_PADDING,
|
||||
padding_right: 0,
|
||||
padding_left: 0,
|
||||
spacing: SIDEBAR_SPACING });
|
||||
this.actor.add_actor(this.box);
|
||||
|
||||
this._gconf = Shell.GConf.get_default();
|
||||
|
||||
this._expanded = this._gconf.get_boolean ("sidebar/expanded");
|
||||
if (!this._expanded)
|
||||
this.actor.width = SIDEBAR_COLLAPSED_WIDTH;
|
||||
|
||||
this._visible = this._gconf.get_boolean ("sidebar/visible");
|
||||
if (this._visible)
|
||||
Main.chrome.addActor(this.actor);
|
||||
|
||||
this._widgets = [];
|
||||
this.addWidget(new ToggleWidget());
|
||||
|
||||
let default_widgets = this._gconf.get_string_list("sidebar/widgets");
|
||||
for (let i = 0; i < default_widgets.length; i++)
|
||||
this.addWidget(default_widgets[i]);
|
||||
|
||||
this._gconf.connect('changed::sidebar/expanded',
|
||||
Lang.bind(this, this._expandedChanged));
|
||||
this._gconf.connect('changed::sidebar/visible',
|
||||
Lang.bind(this, this._visibleChanged));
|
||||
|
||||
this._adjustPosition();
|
||||
},
|
||||
|
||||
addWidget: function(widget) {
|
||||
let widgetBox;
|
||||
try {
|
||||
widgetBox = new WidgetBox.WidgetBox(widget, this._expanded);
|
||||
} catch(e) {
|
||||
logError(e, "Failed to add widget '" + widget + "'");
|
||||
return;
|
||||
}
|
||||
|
||||
this.box.append(widgetBox.actor, Big.BoxPackFlags.NONE);
|
||||
this._widgets.push(widgetBox);
|
||||
this._adjustPosition();
|
||||
},
|
||||
|
||||
_adjustPosition: function() {
|
||||
let primary=global.get_primary_monitor();
|
||||
|
||||
this.actor.y = Math.max(primary.y + Panel.PANEL_HEIGHT,primary.height/2 - this.actor.height/2);
|
||||
this.actor.x = primary.x;
|
||||
},
|
||||
|
||||
_visibleChanged: function() {
|
||||
let visible = this._gconf.get_boolean("sidebar/visible");
|
||||
if (visible == this._visible)
|
||||
return;
|
||||
|
||||
this._visible = visible;
|
||||
if (visible)
|
||||
Main.chrome.addActor(this.actor);
|
||||
else
|
||||
Main.chrome.removeActor(this.actor);
|
||||
},
|
||||
|
||||
_expandedChanged: function() {
|
||||
let expanded = this._gconf.get_boolean("sidebar/expanded");
|
||||
if (expanded == this._expanded)
|
||||
return;
|
||||
|
||||
this._expanded = expanded;
|
||||
if (expanded)
|
||||
this._expand();
|
||||
else
|
||||
this._collapse();
|
||||
},
|
||||
|
||||
_expand: function() {
|
||||
this._expanded = true;
|
||||
for (let i = 0; i < this._widgets.length; i++)
|
||||
this._widgets[i].expand();
|
||||
|
||||
// Updated the strut/stage area after the animation completes
|
||||
Tweener.addTween(this, { time: WidgetBox.ANIMATION_TIME,
|
||||
onComplete: function () {
|
||||
this.actor.width = SIDEBAR_EXPANDED_WIDTH;
|
||||
} });
|
||||
},
|
||||
|
||||
_collapse: function() {
|
||||
this._expanded = false;
|
||||
for (let i = 0; i < this._widgets.length; i++)
|
||||
this._widgets[i].collapse();
|
||||
|
||||
// Updated the strut/stage area after the animation completes
|
||||
Tweener.addTween(this, { time: WidgetBox.ANIMATION_TIME,
|
||||
onComplete: function () {
|
||||
this.actor.width = SIDEBAR_COLLAPSED_WIDTH;
|
||||
} });
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
this.hide();
|
||||
|
||||
for (let i = 0; i < this._widgets.length; i++)
|
||||
this._widgets[i].destroy();
|
||||
this.actor.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
const LEFT_DOUBLE_ARROW = "\u00AB";
|
||||
const RIGHT_DOUBLE_ARROW = "\u00BB";
|
||||
|
||||
function ToggleWidget() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
ToggleWidget.prototype = {
|
||||
__proto__ : Widget.Widget.prototype,
|
||||
|
||||
_init : function() {
|
||||
this._gconf = Shell.GConf.get_default();
|
||||
|
||||
this.actor = new Clutter.Text({ font_name: "Sans Bold 16px",
|
||||
text: LEFT_DOUBLE_ARROW,
|
||||
reactive: true });
|
||||
this.actor.connect('button-release-event',
|
||||
Lang.bind(this, this._collapse));
|
||||
this.collapsedActor = new Clutter.Text({ font_name: "Sans Bold 16px",
|
||||
text: RIGHT_DOUBLE_ARROW,
|
||||
reactive: true });
|
||||
this.collapsedActor.connect('button-release-event',
|
||||
Lang.bind(this, this._expand));
|
||||
},
|
||||
|
||||
_collapse : function () {
|
||||
this._gconf.set_boolean ("sidebar/expanded", false);
|
||||
},
|
||||
|
||||
_expand : function () {
|
||||
this._gconf.set_boolean ("sidebar/expanded", true);
|
||||
}
|
||||
};
|
226
js/ui/status/accessibility.js
Normal file
@ -0,0 +1,226 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const GConf = imports.gi.GConf;
|
||||
const Gio = imports.gi.Gio;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
|
||||
const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable';
|
||||
const KEY_BOUNCE_KEYS_ENABLED = 'bouncekeys-enable';
|
||||
const KEY_SLOW_KEYS_ENABLED = 'slowkeys-enable';
|
||||
const KEY_MOUSE_KEYS_ENABLED = 'mousekeys-enable';
|
||||
|
||||
const MAGNIFIER_SCHEMA = 'org.gnome.accessibility.magnifier';
|
||||
const AT_SCREEN_KEYBOARD_SCHEMA = 'org.gnome.desktop.default-applications.at.mobility';
|
||||
const AT_SCREEN_READER_SCHEMA = 'org.gnome.desktop.default-applications.at.visual';
|
||||
|
||||
const XSETTINGS_SCHEMA = 'org.gnome.settings-daemon.plugins.xsettings';
|
||||
const KEY_DPI = 'dpi';
|
||||
|
||||
const DPI_LOW_REASONABLE_VALUE = 50;
|
||||
const DPI_HIGH_REASONABLE_VALUE = 500;
|
||||
|
||||
const DPI_FACTOR_LARGE = 1.25;
|
||||
const DPI_FACTOR_LARGER = 1.5;
|
||||
const DPI_FACTOR_LARGEST = 2.0;
|
||||
const DPI_DEFAULT = 96;
|
||||
|
||||
const KEY_META_DIR = '/apps/metacity/general';
|
||||
const KEY_VISUAL_BELL = KEY_META_DIR + '/visual_bell';
|
||||
|
||||
const DESKTOP_INTERFACE_SCHEMA = 'org.gnome.desktop.interface';
|
||||
const KEY_GTK_THEME = 'gtk-theme';
|
||||
const KEY_ICON_THEME = 'icon-theme';
|
||||
|
||||
const HIGH_CONTRAST_THEME = 'HighContrast';
|
||||
|
||||
function getDPIFromX() {
|
||||
let screen = global.get_gdk_screen();
|
||||
if (screen) {
|
||||
let width_dpi = (screen.get_width() / (screen.get_width_mm() / 25.4));
|
||||
let height_dpi = (screen.get_height() / (screen.get_height_mm() / 25.4));
|
||||
if (width_dpi < DPI_LOW_REASONABLE_VALUE
|
||||
|| width_dpi > DPI_HIGH_REASONABLE_VALUE
|
||||
|| height_dpi < DPI_LOW_REASONABLE_VALUE
|
||||
|| height_dpi > DPI_HIGH_REASONABLE_VALUE)
|
||||
return DPI_DEFAULT;
|
||||
else
|
||||
return (width_dpi + height_dpi) / 2;
|
||||
}
|
||||
return DPI_DEFAULT;
|
||||
}
|
||||
|
||||
function ATIndicator() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
ATIndicator.prototype = {
|
||||
__proto__: PanelMenu.SystemStatusButton.prototype,
|
||||
|
||||
_init: function() {
|
||||
PanelMenu.SystemStatusButton.prototype._init.call(this, 'preferences-desktop-accessibility', null);
|
||||
|
||||
let client = GConf.Client.get_default();
|
||||
client.add_dir(KEY_META_DIR, GConf.ClientPreloadType.PRELOAD_ONELEVEL, null);
|
||||
client.notify_add(KEY_META_DIR, Lang.bind(this, this._keyChanged), null, null);
|
||||
|
||||
let highContrast = this._buildHCItem();
|
||||
this.menu.addMenuItem(highContrast);
|
||||
|
||||
let magnifier = this._buildItem(_("Zoom"), MAGNIFIER_SCHEMA, 'show-magnifier');
|
||||
this.menu.addMenuItem(magnifier);
|
||||
|
||||
let textZoom = this._buildFontItem();
|
||||
this.menu.addMenuItem(textZoom);
|
||||
|
||||
let screenReader = this._buildItem(_("Screen Reader"), AT_SCREEN_READER_SCHEMA, 'startup');
|
||||
this.menu.addMenuItem(screenReader);
|
||||
|
||||
let screenKeyboard = this._buildItem(_("Screen Keyboard"), AT_SCREEN_KEYBOARD_SCHEMA, 'startup');
|
||||
this.menu.addMenuItem(screenKeyboard);
|
||||
|
||||
let visualBell = this._buildItemGConf(_("Visual Alerts"), client, KEY_VISUAL_BELL);
|
||||
this.menu.addMenuItem(visualBell);
|
||||
|
||||
let stickyKeys = this._buildItem(_("Sticky Keys"), A11Y_SCHEMA, KEY_STICKY_KEYS_ENABLED);
|
||||
this.menu.addMenuItem(stickyKeys);
|
||||
|
||||
let slowKeys = this._buildItem(_("Slow Keys"), A11Y_SCHEMA, KEY_SLOW_KEYS_ENABLED);
|
||||
this.menu.addMenuItem(slowKeys);
|
||||
|
||||
let bounceKeys = this._buildItem(_("Bounce Keys"), A11Y_SCHEMA, KEY_BOUNCE_KEYS_ENABLED);
|
||||
this.menu.addMenuItem(bounceKeys);
|
||||
|
||||
let mouseKeys = this._buildItem(_("Mouse Keys"), A11Y_SCHEMA, KEY_MOUSE_KEYS_ENABLED);
|
||||
this.menu.addMenuItem(mouseKeys);
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_("Universal Access Settings"), function() {
|
||||
Util.spawnDesktop('gnome-universal-access-panel');
|
||||
});
|
||||
},
|
||||
|
||||
_buildItemExtended: function(string, initial_value, writable, on_set) {
|
||||
let widget = new PopupMenu.PopupSwitchMenuItem(string, initial_value);
|
||||
if (!writable)
|
||||
widget.actor.reactive = false;
|
||||
else
|
||||
widget.connect('toggled', function(item) {
|
||||
on_set(item.state);
|
||||
});
|
||||
return widget;
|
||||
},
|
||||
|
||||
_buildItemGConf: function(string, client, key) {
|
||||
function on_get() {
|
||||
return client.get_bool(key);
|
||||
}
|
||||
let widget = this._buildItemExtended(string,
|
||||
client.get_bool(key),
|
||||
client.key_is_writable(key),
|
||||
function(enabled) {
|
||||
client.set_bool(key, enabled);
|
||||
});
|
||||
this.connect('gconf-changed', function() {
|
||||
widget.setToggleState(client.get_bool(key));
|
||||
});
|
||||
return widget;
|
||||
},
|
||||
|
||||
_buildItem: function(string, schema, key) {
|
||||
let settings = new Gio.Settings({ schema: schema });
|
||||
let widget = this._buildItemExtended(string,
|
||||
settings.get_boolean(key),
|
||||
settings.is_writable(key),
|
||||
function(enabled) {
|
||||
return settings.set_boolean(key, enabled);
|
||||
});
|
||||
settings.connect('changed::'+key, function() {
|
||||
widget.setToggleState(settings.get_boolean(key));
|
||||
});
|
||||
return widget;
|
||||
},
|
||||
|
||||
_buildHCItem: function() {
|
||||
let settings = new Gio.Settings({ schema: DESKTOP_INTERFACE_SCHEMA });
|
||||
let gtkTheme = settings.get_string(KEY_GTK_THEME);
|
||||
let iconTheme = settings.get_string(KEY_ICON_THEME);
|
||||
let hasHC = (gtkTheme == HIGH_CONTRAST_THEME);
|
||||
let highContrast = this._buildItemExtended(
|
||||
_("High Contrast"),
|
||||
hasHC,
|
||||
settings.is_writable(KEY_GTK_THEME) && settings.is_writable(KEY_ICON_THEME),
|
||||
function (enabled) {
|
||||
if (enabled) {
|
||||
settings.set_string(KEY_GTK_THEME, HIGH_CONTRAST_THEME);
|
||||
settings.set_string(KEY_ICON_THEME, HIGH_CONTRAST_THEME);
|
||||
} else {
|
||||
settings.set_string(KEY_GTK_THEME, gtkTheme);
|
||||
settings.set_string(KEY_ICON_THEME, iconTheme);
|
||||
}
|
||||
});
|
||||
settings.connect('changed::' + KEY_GTK_THEME, function() {
|
||||
let value = settings.get_string(KEY_GTK_THEME);
|
||||
if (value == HIGH_CONTRAST_THEME) {
|
||||
highContrast.setToggleState(true);
|
||||
} else {
|
||||
highContrast.setToggleState(false);
|
||||
gtkTheme = value;
|
||||
}
|
||||
});
|
||||
settings.connect('changed::' + KEY_ICON_THEME, function() {
|
||||
let value = settings.get_string(KEY_ICON_THEME);
|
||||
if (value != HIGH_CONTRAST_THEME)
|
||||
iconTheme = value;
|
||||
});
|
||||
return highContrast;
|
||||
},
|
||||
|
||||
_buildFontItem: function() {
|
||||
let settings = new Gio.Settings({ schema: XSETTINGS_SCHEMA });
|
||||
|
||||
// we assume this never changes (which is not true if resolution
|
||||
// is changed, but we would need XRandR events for that)
|
||||
let x_value = getDPIFromX();
|
||||
let user_value;
|
||||
function on_get() {
|
||||
user_value = settings.get_double(KEY_DPI);
|
||||
return (user_value - (DPI_FACTOR_LARGE * x_value) > -1);
|
||||
}
|
||||
let initial_setting = on_get();
|
||||
let default_value = initial_setting ? x_value : user_value;
|
||||
let widget = this._buildItemExtended(_("Large Text"),
|
||||
initial_setting,
|
||||
settings.is_writable(KEY_DPI),
|
||||
function (enabled) {
|
||||
if (enabled)
|
||||
settings.set_double(KEY_DPI, DPI_FACTOR_LARGE * default_value);
|
||||
else
|
||||
settings.set_double(KEY_DPI, default_value);
|
||||
});
|
||||
settings.connect('changed::' + KEY_DPI, function() {
|
||||
let active = on_get();
|
||||
default_value = active ? x_value : user_value;
|
||||
widget.setToggleState(active);
|
||||
});
|
||||
return widget;
|
||||
},
|
||||
|
||||
_keyChanged: function() {
|
||||
this.emit('gconf-changed');
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(ATIndicator.prototype);
|
492
js/ui/status/bluetooth.js
Normal file
@ -0,0 +1,492 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const Gdk = imports.gi.Gdk;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GnomeBluetoothApplet = imports.gi.GnomeBluetoothApplet;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const St = imports.gi.St;
|
||||
const Shell = imports.gi.Shell;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const ConnectionState = {
|
||||
DISCONNECTED: 0,
|
||||
CONNECTED: 1,
|
||||
DISCONNECTING: 2,
|
||||
CONNECTING: 3
|
||||
}
|
||||
|
||||
function Indicator() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
Indicator.prototype = {
|
||||
__proto__: PanelMenu.SystemStatusButton.prototype,
|
||||
|
||||
_init: function() {
|
||||
PanelMenu.SystemStatusButton.prototype._init.call(this, 'bluetooth-disabled', null);
|
||||
|
||||
GLib.spawn_command_line_sync ('pkill -f "^bluetooth-applet$"');
|
||||
this._applet = new GnomeBluetoothApplet.Applet();
|
||||
|
||||
this._killswitch = new PopupMenu.PopupSwitchMenuItem(_("Bluetooth"), false);
|
||||
this._applet.connect('notify::killswitch-state', Lang.bind(this, this._updateKillswitch));
|
||||
this._killswitch.connect('toggled', Lang.bind(this, function() {
|
||||
let current_state = this._applet.killswitch_state;
|
||||
if (current_state != GnomeBluetoothApplet.KillswitchState.HARD_BLOCKED &&
|
||||
current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER) {
|
||||
this._applet.killswitch_state = this._killswitch.state ?
|
||||
GnomeBluetoothApplet.KillswitchState.UNBLOCKED:
|
||||
GnomeBluetoothApplet.KillswitchState.SOFT_BLOCKED;
|
||||
} else
|
||||
this._killswitch.setToggleState(false);
|
||||
}));
|
||||
|
||||
this._discoverable = new PopupMenu.PopupSwitchMenuItem(_("Visibility"), this._applet.discoverable);
|
||||
this._applet.connect('notify::discoverable', Lang.bind(this, function() {
|
||||
this._discoverable.setToggleState(this._applet.discoverable);
|
||||
}));
|
||||
this._discoverable.connect('toggled', Lang.bind(this, function() {
|
||||
this._applet.discoverable = this._discoverable.state;
|
||||
}));
|
||||
|
||||
this._updateKillswitch();
|
||||
this.menu.addMenuItem(this._killswitch);
|
||||
this.menu.addMenuItem(this._discoverable);
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
this._fullMenuItems = [new PopupMenu.PopupSeparatorMenuItem(),
|
||||
new PopupMenu.PopupMenuItem(_("Send Files to Device...")),
|
||||
new PopupMenu.PopupMenuItem(_("Setup a New Device...")),
|
||||
new PopupMenu.PopupSeparatorMenuItem()];
|
||||
this._hasDevices = false;
|
||||
this._deviceSep = this._fullMenuItems[0]; // hidden if no device exists
|
||||
|
||||
this._fullMenuItems[1].connect('activate', function() {
|
||||
GLib.spawn_command_line_async('bluetooth-sendto');
|
||||
});
|
||||
this._fullMenuItems[2].connect('activate', function() {
|
||||
GLib.spawn_command_line_async('bluetooth-wizard');
|
||||
});
|
||||
|
||||
for (let i = 0; i < this._fullMenuItems.length; i++) {
|
||||
let item = this._fullMenuItems[i];
|
||||
this.menu.addMenuItem(item);
|
||||
}
|
||||
|
||||
this._deviceItemPosition = 3;
|
||||
this._deviceItems = [];
|
||||
this._applet.connect('devices-changed', Lang.bind(this, this._updateDevices));
|
||||
this._updateDevices();
|
||||
|
||||
this._applet.connect('notify::show-full-menu', Lang.bind(this, this._updateFullMenu));
|
||||
this._updateFullMenu();
|
||||
|
||||
this.menu.addAction(_("Bluetooth Settings"), function() {
|
||||
GLib.spawn_command_line_async('gnome-control-center bluetooth');
|
||||
});
|
||||
|
||||
this._applet.connect('pincode-request', Lang.bind(this, this._pinRequest));
|
||||
this._applet.connect('confirm-request', Lang.bind(this, this._confirmRequest));
|
||||
this._applet.connect('auth-request', Lang.bind(this, this._authRequest));
|
||||
this._applet.connect('cancel-request', Lang.bind(this, this._cancelRequest));
|
||||
},
|
||||
|
||||
_updateKillswitch: function() {
|
||||
let current_state = this._applet.killswitch_state;
|
||||
let on = current_state == GnomeBluetoothApplet.KillswitchState.UNBLOCKED;
|
||||
let has_adapter = current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER;
|
||||
let can_toggle = current_state != GnomeBluetoothApplet.KillswitchState.NO_ADAPTER &&
|
||||
current_state != GnomeBluetoothApplet.KillswitchState.HARD_BLOCKED;
|
||||
|
||||
this._killswitch.setToggleState(on);
|
||||
this._killswitch.actor.reactive = can_toggle;
|
||||
|
||||
if (has_adapter)
|
||||
this.actor.show();
|
||||
else
|
||||
this.actor.hide();
|
||||
|
||||
if (on) {
|
||||
this._discoverable.actor.show();
|
||||
this.setIcon('bluetooth-active');
|
||||
} else {
|
||||
this._discoverable.actor.hide();
|
||||
this.setIcon('bluetooth-disabled');
|
||||
}
|
||||
},
|
||||
|
||||
_deviceCompare: function(d1, d2) {
|
||||
return d1.device_path == d2.device_path &&
|
||||
d1.bdaddr == d2.bdaddr &&
|
||||
d1.can_connect == d2.can_connect &&
|
||||
d1.capabilities == d2.capabilities;
|
||||
},
|
||||
|
||||
_updateDevices: function() {
|
||||
let devices = this._applet.get_devices();
|
||||
|
||||
for (let i = 0; i < this._deviceItems.length; i++) {
|
||||
let item = this._deviceItems[i];
|
||||
let destroy = true;
|
||||
for (let j = 0; j < devices.length; j++) {
|
||||
// we need to deep compare because BluetoothSimpleDevice is a boxed type
|
||||
// (but we take advantage of that, because _skip will disappear the next
|
||||
// time get_devices() is called)
|
||||
if (this._deviceCompare(item._device, devices[i])) {
|
||||
item.label.text = devices[i].alias;
|
||||
devices[i]._skip = true;
|
||||
destroy = false;
|
||||
}
|
||||
}
|
||||
if (destroy) {
|
||||
item.destroy();
|
||||
item._destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
let newlist = [ ];
|
||||
for (let i = 0; i < this._deviceItems.length; i++) {
|
||||
let item = this._deviceItems[i];
|
||||
if (!item._destroyed)
|
||||
newlist.push(item);
|
||||
}
|
||||
this._deviceItems = newlist;
|
||||
|
||||
this._hasDevices = newlist.length > 0;
|
||||
for (let i = 0; i < devices.length; i++) {
|
||||
let d = devices[i];
|
||||
if (d._skip)
|
||||
continue;
|
||||
let item = this._createDeviceItem(d);
|
||||
if (item) {
|
||||
this.menu.addMenuItem(item, this._deviceItemPosition + this._deviceItems.length);
|
||||
this._deviceItems.push(item);
|
||||
this._hasDevices = true;
|
||||
}
|
||||
}
|
||||
if (this._hasDevices)
|
||||
this._deviceSep.actor.show();
|
||||
else
|
||||
this._deviceSep.actor.hide();
|
||||
},
|
||||
|
||||
_createDeviceItem: function(device) {
|
||||
if (!device.can_connect && device.capabilities == GnomeBluetoothApplet.Capabilities.NONE)
|
||||
return null;
|
||||
let item = new PopupMenu.PopupSubMenuMenuItem(device.alias);
|
||||
item._device = device;
|
||||
|
||||
if (device.can_connect) {
|
||||
item._connected = device.connected;
|
||||
let menuitem = new PopupMenu.PopupSwitchMenuItem(_("Connection"), device.connected);
|
||||
|
||||
menuitem.connect('toggled', Lang.bind(this, function() {
|
||||
if (item._connected > ConnectionState.CONNECTED) {
|
||||
// operation already in progress, revert
|
||||
menuitem.setToggleState(menuitem.state);
|
||||
}
|
||||
if (item._connected) {
|
||||
item._connected = ConnectionState.DISCONNECTING;
|
||||
this._applet.disconnect_device(item._device.device_path, function(applet, success) {
|
||||
if (success) { // apply
|
||||
item._connected = ConnectionState.DISCONNECTED;
|
||||
menuitem.setToggleState(false);
|
||||
} else { // revert
|
||||
item._connected = ConnectionState.CONNECTED;
|
||||
menuitem.setToggleState(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
item._connected = ConnectionState.CONNECTING;
|
||||
this._applet.connect_device(item._device.device_path, function(applet, success) {
|
||||
if (success) { // apply
|
||||
item._connected = ConnectionState.CONNECTED;
|
||||
menuitem.setToggleState(true);
|
||||
} else { // revert
|
||||
item._connected = ConnectionState.DISCONNECTED;
|
||||
menuitem.setToggleState(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
item.menu.addMenuItem(menuitem);
|
||||
}
|
||||
|
||||
if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_PUSH) {
|
||||
item.menu.addAction(_("Send Files..."), Lang.bind(this, function() {
|
||||
this._applet.send_to_address(device.bdaddr, device.alias);
|
||||
}));
|
||||
}
|
||||
if (device.capabilities & GnomeBluetoothApplet.Capabilities.OBEX_FILE_TRANSFER) {
|
||||
item.menu.addAction(_("Browse Files..."), Lang.bind(this, function(event) {
|
||||
this._applet.browse_address(device.bdaddr, event.get_time(),
|
||||
Lang.bind(this, function(applet, result) {
|
||||
try {
|
||||
applet.browse_address_finish(result);
|
||||
} catch (e) {
|
||||
this._ensureSource();
|
||||
this._source.notify(new MessageTray.Notification(this._source,
|
||||
_("Bluetooth"),
|
||||
_("Error browsing device"),
|
||||
{ body: _("The requested device cannot be browsed, error is '%s'").format(e) }));
|
||||
}
|
||||
}));
|
||||
}));
|
||||
}
|
||||
|
||||
switch (device.type) {
|
||||
case GnomeBluetoothApplet.Type.KEYBOARD:
|
||||
item.menu.addAction(_("Keyboard Settings"), function() {
|
||||
GLib.spawn_command_line_async('gnome-control-center keyboard');
|
||||
});
|
||||
break;
|
||||
case GnomeBluetoothApplet.Type.MOUSE:
|
||||
item.menu.addAction(_("Mouse Settings"), function() {
|
||||
GLib.spawn_command_line_async('gnome-control-center mouse');
|
||||
});
|
||||
break;
|
||||
case GnomeBluetoothApplet.Type.HEADSET:
|
||||
case GnomeBluetoothApplet.Type.HEADPHONES:
|
||||
case GnomeBluetoothApplet.Type.OTHER_AUDIO:
|
||||
item.menu.addAction(_("Sound Settings"), function() {
|
||||
GLib.spawn_command_line_async('gnome-control-center sound');
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return item;
|
||||
},
|
||||
|
||||
_updateFullMenu: function() {
|
||||
if (this._applet.show_full_menu) {
|
||||
this._showAll(this._fullMenuItems);
|
||||
if (this._hasDevices)
|
||||
this._showAll(this._deviceItems);
|
||||
else
|
||||
this._deviceSep.actor.hide();
|
||||
} else {
|
||||
this._hideAll(this._fullMenuItems);
|
||||
this._hideAll(this._deviceItems);
|
||||
}
|
||||
},
|
||||
|
||||
_showAll: function(items) {
|
||||
for (let i = 0; i < items.length; i++)
|
||||
items[i].actor.show();
|
||||
},
|
||||
|
||||
_hideAll: function(items) {
|
||||
for (let i = 0; i < items.length; i++)
|
||||
items[i].actor.hide();
|
||||
},
|
||||
|
||||
_destroyAll: function(items) {
|
||||
for (let i = 0; i < items.length; i++)
|
||||
items[i].destroy();
|
||||
},
|
||||
|
||||
_ensureSource: function() {
|
||||
if (!this._source) {
|
||||
this._source = new Source();
|
||||
Main.messageTray.add(this._source);
|
||||
}
|
||||
},
|
||||
|
||||
_authRequest: function(applet, device_path, name, long_name, uuid) {
|
||||
this._ensureSource();
|
||||
this._source.notify(new AuthNotification(this._source, this._applet, device_path, name, long_name, uuid));
|
||||
},
|
||||
|
||||
_confirmRequest: function(applet, device_path, name, long_name, pin) {
|
||||
this._ensureSource();
|
||||
this._source.notify(new ConfirmNotification(this._source, this._applet, device_path, name, long_name, pin));
|
||||
},
|
||||
|
||||
_pinRequest: function(applet, device_path, name, long_name, numeric) {
|
||||
this._ensureSource();
|
||||
this._source.notify(new PinNotification(this._source, this._applet, device_path, name, long_name, numeric));
|
||||
},
|
||||
|
||||
_cancelRequest: function() {
|
||||
this._source.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
function Source() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
Source.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function() {
|
||||
MessageTray.Source.prototype._init.call(this, _("Bluetooth Agent"));
|
||||
|
||||
this._setSummaryIcon(this.createNotificationIcon());
|
||||
},
|
||||
|
||||
notify: function(notification) {
|
||||
this._private_destroyId = notification.connect('destroy', Lang.bind(this, function(notification) {
|
||||
if (this.notification == notification) {
|
||||
// the destroyed notification is the last for this source
|
||||
this.notification.disconnect(this._private_destroyId);
|
||||
this.destroy();
|
||||
}
|
||||
}));
|
||||
|
||||
MessageTray.Source.prototype.notify.call(this, notification);
|
||||
},
|
||||
|
||||
createNotificationIcon: function() {
|
||||
return new St.Icon({ icon_name: 'bluetooth-active',
|
||||
icon_type: St.IconType.SYMBOLIC,
|
||||
icon_size: this.ICON_SIZE });
|
||||
}
|
||||
}
|
||||
|
||||
function AuthNotification() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
AuthNotification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source, applet, device_path, name, long_name, uuid) {
|
||||
MessageTray.Notification.prototype._init.call(this,
|
||||
source,
|
||||
_("Bluetooth Agent"),
|
||||
_("Authorization request from %s").format(name),
|
||||
{ customContent: true });
|
||||
this.setResident(true);
|
||||
|
||||
this._applet = applet;
|
||||
this._devicePath = device_path;
|
||||
this.addBody(_("Device %s wants access to the service '%s'").format(long_name, uuid));
|
||||
|
||||
this.addButton('always-grant', _("Always grant access"));
|
||||
this.addButton('grant', _("Grant this time only"));
|
||||
this.addButton('reject', _("Reject"));
|
||||
|
||||
this.connect('action-invoked', Lang.bind(this, function(self, action) {
|
||||
switch (action) {
|
||||
case 'always-grant':
|
||||
this._applet.agent_reply_auth(this._devicePath, true, true);
|
||||
break;
|
||||
case 'grant':
|
||||
this._applet.agent_reply_auth(this._devicePath, true, false);
|
||||
break;
|
||||
case 'reject':
|
||||
default:
|
||||
this._applet.agent_reply_auth(this._devicePath, false, false);
|
||||
}
|
||||
this.destroy();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function ConfirmNotification() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
ConfirmNotification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source, applet, device_path, name, long_name, pin) {
|
||||
MessageTray.Notification.prototype._init.call(this,
|
||||
source,
|
||||
_("Bluetooth Agent"),
|
||||
_("Pairing confirmation for %s").format(name),
|
||||
{ customContent: true });
|
||||
this.setResident(true);
|
||||
|
||||
this._applet = applet;
|
||||
this._devicePath = device_path;
|
||||
this.addBody(_("Device %s wants to pair with this computer").format(long_name));
|
||||
this.addBody(_("Please confirm whether the PIN '%s' matches the one on the device.").format(pin));
|
||||
|
||||
this.addButton('matches', _("Matches"));
|
||||
this.addButton('does-not-match', _("Does not match"));
|
||||
|
||||
this.connect('action-invoked', Lang.bind(this, function(self, action) {
|
||||
if (action == 'matches')
|
||||
this._applet.agent_reply_confirm(this._devicePath, true);
|
||||
else
|
||||
this._applet.agent_reply_confirm(this._devicePath, false);
|
||||
this.destroy();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
function PinNotification() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
PinNotification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source, applet, device_path, name, long_name, numeric) {
|
||||
MessageTray.Notification.prototype._init.call(this,
|
||||
source,
|
||||
_("Bluetooth Agent"),
|
||||
_("Pairing request for %s").format(name),
|
||||
{ customContent: true });
|
||||
this.setResident(true);
|
||||
|
||||
this._applet = applet;
|
||||
this._devicePath = device_path;
|
||||
this._numeric = numeric;
|
||||
this.addBody(_("Device %s wants to pair with this computer").format(long_name));
|
||||
this.addBody(_("Please enter the PIN mentioned on the device."));
|
||||
|
||||
this._entry = new St.Entry();
|
||||
this._entry.connect('key-release-event', Lang.bind(this, function(entry, event) {
|
||||
let key = event.get_key_symbol();
|
||||
if (key == Clutter.KEY_Return) {
|
||||
this.emit('action-invoked', 'ok');
|
||||
return true;
|
||||
} else if (key == Clutter.KEY_Escape) {
|
||||
this.emit('action-invoked', 'cancel');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
this.addActor(this._entry);
|
||||
|
||||
this.addButton('ok', _("OK"));
|
||||
this.addButton('cancel', _("Cancel"));
|
||||
|
||||
this.connect('action-invoked', Lang.bind(this, function(self, action) {
|
||||
if (action == 'ok') {
|
||||
if (this._numeric)
|
||||
this._applet.agent_reply_passkey(this._devicePath, parseInt(this._entry.text));
|
||||
else
|
||||
this._applet.agent_reply_pincode(this._devicePath, this._entry.text);
|
||||
} else {
|
||||
if (this._numeric)
|
||||
this._applet.agent_reply_passkey(this._devicePath, -1);
|
||||
else
|
||||
this._applet.agent_reply_pincode(this._devicePath, null);
|
||||
}
|
||||
this.destroy();
|
||||
}));
|
||||
},
|
||||
|
||||
grabFocus: function(lockTray) {
|
||||
MessageTray.Notification.prototype.grabFocus.call(this, lockTray);
|
||||
global.stage.set_key_focus(this._entry);
|
||||
}
|
||||
}
|
206
js/ui/status/keyboard.js
Normal file
@ -0,0 +1,206 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||
const Gkbd = imports.gi.Gkbd;
|
||||
const Gio = imports.gi.Gio;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const INDICATOR_SCHEMA = 'org.gnome.settings-daemon.plugins.keyboard';
|
||||
|
||||
function LayoutMenuItem() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
LayoutMenuItem.prototype = {
|
||||
__proto__: PopupMenu.PopupBaseMenuItem.prototype,
|
||||
|
||||
_init: function(config, id, indicator, long_name) {
|
||||
PopupMenu.PopupBaseMenuItem.prototype._init.call(this);
|
||||
|
||||
this._config = config;
|
||||
this._id = id;
|
||||
this.label = new St.Label({ text: long_name });
|
||||
this.indicator = indicator;
|
||||
this.addActor(this.label);
|
||||
this.addActor(this.indicator);
|
||||
},
|
||||
|
||||
activate: function(event) {
|
||||
PopupMenu.PopupBaseMenuItem.prototype.activate.call(this);
|
||||
this._config.lock_group(this._id);
|
||||
}
|
||||
};
|
||||
|
||||
function XKBIndicator() {
|
||||
this._init.call(this);
|
||||
}
|
||||
|
||||
XKBIndicator.prototype = {
|
||||
__proto__: PanelMenu.Button.prototype,
|
||||
|
||||
_init: function() {
|
||||
PanelMenu.Button.prototype._init.call(this, St.Align.START);
|
||||
|
||||
this._container = new Shell.GenericContainer();
|
||||
this._container.connect('get-preferred-width', Lang.bind(this, this._get_preferred_width));
|
||||
this._container.connect('get-preferred-height', Lang.bind(this, this._get_preferred_height));
|
||||
this._container.connect('allocate', Lang.bind(this, this._allocate));
|
||||
this.actor.set_child(this._container);
|
||||
|
||||
this._iconActor = new St.Icon({ icon_name: 'keyboard', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' });
|
||||
this._container.add_actor(this._iconActor);
|
||||
this._labelActors = [ ];
|
||||
this._layoutItems = [ ];
|
||||
|
||||
this._indicatorSettings = new Gio.Settings({ schema: INDICATOR_SCHEMA });
|
||||
this._indicatorSettings.connect('changed::disable-indicator', Lang.bind(this, this._sync_config));
|
||||
|
||||
this._disableIndicator = false;
|
||||
this._showFlags = false;
|
||||
this._config = Gkbd.Configuration.get();
|
||||
this._config.connect('changed', Lang.bind(this, this._sync_config));
|
||||
this._config.connect('group-changed', Lang.bind(this, this._sync_group));
|
||||
this._config.start_listen();
|
||||
|
||||
this._sync_config();
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_("Localization Settings"), function() {
|
||||
GLib.spawn_command_line_async('gnome-control-center region');
|
||||
});
|
||||
},
|
||||
|
||||
_sync_config: function() {
|
||||
this._disableIndicator = this._indicatorSettings.get_boolean('disable-indicator');
|
||||
|
||||
this._showFlags = this._config.if_flags_shown();
|
||||
if (this._showFlags) {
|
||||
this._container.set_skip_paint(this._iconActor, false);
|
||||
} else {
|
||||
this._container.set_skip_paint(this._iconActor, true);
|
||||
}
|
||||
|
||||
let groups = this._config.get_group_names();
|
||||
if (groups.length > 1 && !this._disableIndicator) {
|
||||
this.actor.show();
|
||||
} else {
|
||||
this.menu.close();
|
||||
this.actor.hide();
|
||||
}
|
||||
|
||||
for (let i = 0; i < this._layoutItems.length; i++)
|
||||
this._layoutItems[i].destroy();
|
||||
|
||||
for (let i = 0; i < this._labelActors.length; i++)
|
||||
this._labelActors[i].destroy();
|
||||
|
||||
let short_names = this._config.get_short_group_names();
|
||||
|
||||
this._selectedLayout = null;
|
||||
this._layoutItems = [ ];
|
||||
this._selectedLabel = null;
|
||||
this._labelActors = [ ];
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
let icon_name = this._config.get_group_name(i);
|
||||
let actor;
|
||||
if (this._showFlags)
|
||||
actor = new St.Icon({ icon_name: icon_name, icon_type: St.IconType.SYMBOLIC, style_class: 'popup-menu-icon' });
|
||||
else
|
||||
actor = new St.Label({ text: short_names[i] });
|
||||
let item = new LayoutMenuItem(this._config, i, actor, groups[i]);
|
||||
item._short_group_name = short_names[i];
|
||||
item._icon_name = icon_name;
|
||||
this._layoutItems.push(item);
|
||||
this.menu.addMenuItem(item, i);
|
||||
|
||||
let shortLabel = new St.Label({ text: short_names[i] });
|
||||
this._labelActors.push(shortLabel);
|
||||
this._container.add_actor(shortLabel);
|
||||
this._container.set_skip_paint(shortLabel, true);
|
||||
}
|
||||
|
||||
this._sync_group();
|
||||
},
|
||||
|
||||
_sync_group: function() {
|
||||
let selected = this._config.get_current_group();
|
||||
|
||||
if (this._selectedLayout) {
|
||||
this._selectedLayout.setShowDot(false);
|
||||
this._selectedLayout = null;
|
||||
}
|
||||
|
||||
if (this._selectedLabel) {
|
||||
this._container.set_skip_paint(this._selectedLabel, true);
|
||||
this._selectedLabel = null;
|
||||
}
|
||||
|
||||
let item = this._layoutItems[selected];
|
||||
item.setShowDot(true);
|
||||
|
||||
this._iconActor.icon_name = item._icon_name;
|
||||
this._selectedLabel = this._labelActors[selected];
|
||||
this._container.set_skip_paint(this._selectedLabel, this._showFlags);
|
||||
|
||||
this._selectedLayout = item;
|
||||
},
|
||||
|
||||
_get_preferred_width: function(container, for_height, alloc) {
|
||||
/* Here, and in _get_preferred_height, we need to query for the
|
||||
height of all children, but we ignore the results for those
|
||||
we don't actually display. */
|
||||
let max_min_width = 0, max_natural_width = 0;
|
||||
if (this._showFlags)
|
||||
[max_min_width, max_natural_width] = this._iconActor.get_preferred_width(for_height);
|
||||
|
||||
for (let i = 0; i < this._labelActors.length; i++) {
|
||||
let [min_width, natural_width] = this._labelActors[i].get_preferred_width(for_height);
|
||||
if (!this._showFlags) {
|
||||
max_min_width = Math.max(max_min_width, min_width);
|
||||
max_natural_width = Math.max(max_natural_width, natural_width);
|
||||
}
|
||||
}
|
||||
|
||||
alloc.min_size = max_min_width;
|
||||
alloc.natural_size = max_natural_width;
|
||||
},
|
||||
|
||||
_get_preferred_height: function(container, for_width, alloc) {
|
||||
let max_min_height = 0, max_natural_height = 0;
|
||||
if (this._showFlags)
|
||||
[max_min_height, max_natural_height] = this._iconActor.get_preferred_height(for_width);
|
||||
|
||||
for (let i = 0; i < this._labelActors.length; i++) {
|
||||
let [min_height, natural_height] = this._labelActors[i].get_preferred_height(for_width);
|
||||
if (!this._showFlags) {
|
||||
max_min_height = Math.max(max_min_height, min_height);
|
||||
max_natural_height = Math.max(max_natural_height, natural_height);
|
||||
}
|
||||
}
|
||||
|
||||
alloc.min_size = max_min_height;
|
||||
alloc.natural_size = max_natural_height;
|
||||
},
|
||||
|
||||
_allocate: function(container, box, flags) {
|
||||
// translate box to (0, 0)
|
||||
box.x2 -= box.x1;
|
||||
box.x1 = 0;
|
||||
box.y2 -= box.y1;
|
||||
box.y1 = 0;
|
||||
|
||||
this._iconActor.allocate_align_fill(box, 0.5, 0, false, false, flags);
|
||||
for (let i = 0; i < this._labelActors.length; i++)
|
||||
this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags);
|
||||
}
|
||||
};
|
260
js/ui/status/power.js
Normal file
@ -0,0 +1,260 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Gio = imports.gi.Gio;
|
||||
const DBus = imports.dbus;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const BUS_NAME = 'org.gnome.PowerManager';
|
||||
const OBJECT_PATH = '/org/gnome/PowerManager';
|
||||
|
||||
const UPDeviceType = {
|
||||
UNKNOWN: 0,
|
||||
AC_POWER: 1,
|
||||
BATTERY: 2,
|
||||
UPS: 3,
|
||||
MONITOR: 4,
|
||||
MOUSE: 5,
|
||||
KEYBOARD: 6,
|
||||
PDA: 7,
|
||||
PHONE: 8,
|
||||
MEDIA_PLAYER: 9,
|
||||
TABLET: 10,
|
||||
COMPUTER: 11
|
||||
};
|
||||
|
||||
const UPDeviceState = {
|
||||
UNKNOWN: 0,
|
||||
CHARGING: 1,
|
||||
DISCHARGING: 2,
|
||||
EMPTY: 3,
|
||||
FULLY_CHARGED: 4,
|
||||
PENDING_CHARGE: 5,
|
||||
PENDING_DISCHARGE: 6
|
||||
};
|
||||
|
||||
const PowerManagerInterface = {
|
||||
name: 'org.gnome.PowerManager',
|
||||
methods: [
|
||||
{ name: 'GetDevices', inSignature: '', outSignature: 'a(susbut)' },
|
||||
{ name: 'GetPrimaryDevice', inSignature: '', outSignature: '(susbut)' },
|
||||
],
|
||||
signals: [
|
||||
{ name: 'Changed', outSignature: '' },
|
||||
],
|
||||
properties: [
|
||||
{ name: 'Icon', signature: 's', access: 'read' },
|
||||
]
|
||||
};
|
||||
let PowerManagerProxy = DBus.makeProxyClass(PowerManagerInterface);
|
||||
|
||||
function Indicator() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
Indicator.prototype = {
|
||||
__proto__: PanelMenu.SystemStatusButton.prototype,
|
||||
|
||||
_init: function() {
|
||||
PanelMenu.SystemStatusButton.prototype._init.call(this, 'battery-missing');
|
||||
this._proxy = new PowerManagerProxy(DBus.session, BUS_NAME, OBJECT_PATH);
|
||||
|
||||
this._deviceItems = [ ];
|
||||
this._hasPrimary = false;
|
||||
this._primaryDeviceId = null;
|
||||
|
||||
this._batteryItem = new PopupMenu.PopupMenuItem('');
|
||||
this._primaryPercentage = new St.Label();
|
||||
this._batteryItem.addActor(this._primaryPercentage, { align: St.Align.END });
|
||||
this.menu.addMenuItem(this._batteryItem);
|
||||
|
||||
this._deviceSep = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this.menu.addMenuItem(this._deviceSep);
|
||||
this._otherDevicePosition = 2;
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
|
||||
this.menu.addAction(_("Power Settings"),function() {
|
||||
Util.spawnDesktop('gnome-power-panel');
|
||||
});
|
||||
|
||||
this._proxy.connect('Changed', Lang.bind(this, this._devicesChanged));
|
||||
this._devicesChanged();
|
||||
},
|
||||
|
||||
_readPrimaryDevice: function() {
|
||||
this._proxy.GetPrimaryDeviceRemote(Lang.bind(this, function(device, error) {
|
||||
if (error) {
|
||||
this._checkError(error);
|
||||
this._hasPrimary = false;
|
||||
this._primaryDeviceId = null;
|
||||
this._batteryItem.actor.hide();
|
||||
this._deviceSep.actor.hide();
|
||||
return;
|
||||
}
|
||||
let [device_id, device_type, icon, percentage, state, seconds] = device;
|
||||
if (device_type == UPDeviceType.BATTERY) {
|
||||
this._hasPrimary = true;
|
||||
let time = Math.round(seconds / 60);
|
||||
let minutes = time % 60;
|
||||
let hours = Math.floor(time / 60);
|
||||
let timestring;
|
||||
if (time > 60) {
|
||||
if (minutes == 0) {
|
||||
timestring = Gettext.ngettext("%d hour remaining", "%d hours remaining", hours).format(hours);
|
||||
} else {
|
||||
/* TRANSLATORS: this is a time string, as in "%d hours %d minutes remaining" */
|
||||
let template = _("%d %s %d %s remaining");
|
||||
|
||||
timestring = template.format (hours, Gettext.ngettext("hour", "hours", hours), minutes, Gettext.ngettext("minute", "minutes", minutes));
|
||||
}
|
||||
} else
|
||||
timestring = Gettext.ngettext("%d minute remaining", "%d minutes remaining", minutes).format(minutes);
|
||||
this._batteryItem.label.text = timestring;
|
||||
this._primaryPercentage.text = Math.round(percentage) + '%';
|
||||
this._batteryItem.actor.show();
|
||||
if (this._deviceItems.length > 0)
|
||||
this._deviceSep.actor.show();
|
||||
} else {
|
||||
this._hasPrimary = false;
|
||||
this._batteryItem.actor.hide();
|
||||
this._deviceSep.actor.hide();
|
||||
}
|
||||
|
||||
this._primaryDeviceId = device_id;
|
||||
if (this._primaryDeviceId) {
|
||||
this._batteryItem.actor.reactive = true;
|
||||
this._batteryItem.actor.can_focus = true;
|
||||
this._batteryItem.connect('activate', function(item) {
|
||||
Util.spawn(['gnome-power-statistics', '--device', device_id]);
|
||||
});
|
||||
} else {
|
||||
// virtual device
|
||||
this._batteryItem.actor.reactive = false;
|
||||
this._batteryItem.actor.can_focus = false;
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_readOtherDevices: function() {
|
||||
this._proxy.GetDevicesRemote(Lang.bind(this, function(devices, error) {
|
||||
this._deviceItems.forEach(function(i) { i.destroy(); });
|
||||
this._deviceItems = [];
|
||||
|
||||
if (error) {
|
||||
this._checkError(error);
|
||||
this._deviceSep.actor.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
let position = 0;
|
||||
for (let i = 0; i < devices.length; i++) {
|
||||
let [device_id, device_type] = devices[i];
|
||||
if (device_type == UPDeviceType.AC_POWER || device_id == this._primaryDeviceId)
|
||||
continue;
|
||||
|
||||
let item = new DeviceItem (devices[i]);
|
||||
item.connect('activate', function() {
|
||||
Util.spawn(['gnome-power-statistics', '--device', device_id]);
|
||||
});
|
||||
this._deviceItems.push(item);
|
||||
this.menu.addMenuItem(item, this._otherDevicePosition + position);
|
||||
position++;
|
||||
}
|
||||
|
||||
if (this._hasPrimary && position > 0)
|
||||
this._deviceSep.actor.show();
|
||||
else
|
||||
this._deviceSep.actor.hide();
|
||||
}));
|
||||
},
|
||||
|
||||
_devicesChanged: function() {
|
||||
this._proxy.GetRemote('Icon', Lang.bind(this, function(icon, error) {
|
||||
if (icon) {
|
||||
let gicon = Shell.util_icon_from_string (icon);
|
||||
this.setGIcon(gicon);
|
||||
this.actor.show();
|
||||
} else {
|
||||
this._checkError(error);
|
||||
this.menu.close();
|
||||
this.actor.hide();
|
||||
}
|
||||
}));
|
||||
this._readPrimaryDevice();
|
||||
this._readOtherDevices();
|
||||
},
|
||||
|
||||
_checkError: function(error) {
|
||||
if (!this._restarted && error && error.message.match(/org\.freedesktop\.DBus\.Error\.(UnknownMethod|InvalidArgs)/)) {
|
||||
Util.killall('gnome-power-manager');
|
||||
Util.spawn(['gnome-power-manager']);
|
||||
this._restarted = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function DeviceItem() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
DeviceItem.prototype = {
|
||||
__proto__: PopupMenu.PopupBaseMenuItem.prototype,
|
||||
|
||||
_init: function(device) {
|
||||
PopupMenu.PopupBaseMenuItem.prototype._init.call(this);
|
||||
|
||||
let [device_id, device_type, icon, percentage, state, time] = device;
|
||||
|
||||
this._box = new St.BoxLayout({ style_class: 'popup-device-menu-item' });
|
||||
this._label = new St.Label({ text: this._deviceTypeToString(device_type) });
|
||||
|
||||
this._icon = new St.Icon({ gicon: Shell.util_icon_from_string(icon),
|
||||
icon_type: St.IconType.SYMBOLIC,
|
||||
style_class: 'popup-menu-icon' });
|
||||
|
||||
this._box.add_actor(this._icon);
|
||||
this._box.add_actor(this._label);
|
||||
this.addActor(this._box);
|
||||
|
||||
let percentLabel = new St.Label({ text: '%d%%'.format(Math.round(percentage)) });
|
||||
this.addActor(percentLabel, { align: St.Align.END });
|
||||
},
|
||||
|
||||
_deviceTypeToString: function(type) {
|
||||
switch (type) {
|
||||
case UPDeviceType.AC_POWER:
|
||||
return _("AC adapter");
|
||||
case UPDeviceType.BATTERY:
|
||||
return _("Laptop battery");
|
||||
case UPDeviceType.UPS:
|
||||
return _("UPS");
|
||||
case UPDeviceType.MONITOR:
|
||||
return _("Monitor");
|
||||
case UPDeviceType.MOUSE:
|
||||
return _("Mouse");
|
||||
case UPDeviceType.KEYBOARD:
|
||||
return _("Keyboard");
|
||||
case UPDeviceType.PDA:
|
||||
return _("PDA");
|
||||
case UPDeviceType.PHONE:
|
||||
return _("Cell phone");
|
||||
case UPDeviceType.MEDIA_PLAYER:
|
||||
return _("Media player");
|
||||
case UPDeviceType.TABLET:
|
||||
return _("Tablet");
|
||||
case UPDeviceType.COMPUTER:
|
||||
return _("Computer");
|
||||
default:
|
||||
return _("Unknown");
|
||||
}
|
||||
}
|
||||
}
|
216
js/ui/status/volume.js
Normal file
@ -0,0 +1,216 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Clutter = imports.gi.Clutter;
|
||||
const DBus = imports.dbus;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Gvc = imports.gi.Gvc;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const VOLUME_MAX = 65536.0; /* PA_VOLUME_NORM */
|
||||
const VOLUME_ADJUSTMENT_STEP = 0.05; /* Volume adjustment step in % */
|
||||
|
||||
function Indicator() {
|
||||
this._init.apply(this, arguments);
|
||||
}
|
||||
|
||||
Indicator.prototype = {
|
||||
__proto__: PanelMenu.SystemStatusButton.prototype,
|
||||
|
||||
_init: function() {
|
||||
PanelMenu.SystemStatusButton.prototype._init.call(this, 'audio-volume-muted', null);
|
||||
|
||||
this._control = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' });
|
||||
this._control.connect('ready', Lang.bind(this, this._onControlReady));
|
||||
this._control.connect('default-sink-changed', Lang.bind(this, this._readOutput));
|
||||
this._control.connect('default-source-changed', Lang.bind(this, this._readInput));
|
||||
this._control.connect('stream-added', Lang.bind(this, this._maybeShowInput));
|
||||
this._control.connect('stream-removed', Lang.bind(this, this._maybeShowInput));
|
||||
|
||||
this._output = null;
|
||||
this._outputVolumeId = 0;
|
||||
this._outputMutedId = 0;
|
||||
this._outputTitle = new PopupMenu.PopupMenuItem(_("Volume"), { reactive: false });
|
||||
this._outputSlider = new PopupMenu.PopupSliderMenuItem(0);
|
||||
this._outputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_output'));
|
||||
this._outputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
||||
this.menu.addMenuItem(this._outputTitle);
|
||||
this.menu.addMenuItem(this._outputSlider);
|
||||
|
||||
this._separator = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this.menu.addMenuItem(this._separator);
|
||||
|
||||
this._input = null;
|
||||
this._inputVolumeId = 0;
|
||||
this._inputMutedId = 0;
|
||||
this._inputTitle = new PopupMenu.PopupMenuItem(_("Microphone"), { reactive: false });
|
||||
this._inputSlider = new PopupMenu.PopupSliderMenuItem(0);
|
||||
this._inputSlider.connect('value-changed', Lang.bind(this, this._sliderChanged, '_input'));
|
||||
this._inputSlider.connect('drag-end', Lang.bind(this, this._notifyVolumeChange));
|
||||
this.menu.addMenuItem(this._inputTitle);
|
||||
this.menu.addMenuItem(this._inputSlider);
|
||||
|
||||
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
|
||||
this.menu.addAction(_("Sound Settings"), function() {
|
||||
Util.spawnDesktop('gnome-sound-panel');
|
||||
});
|
||||
|
||||
this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent));
|
||||
this._control.open();
|
||||
},
|
||||
|
||||
_onScrollEvent: function(actor, event) {
|
||||
let direction = event.get_scroll_direction();
|
||||
let currentVolume = this._output.volume;
|
||||
|
||||
if (direction == Clutter.ScrollDirection.DOWN) {
|
||||
let prev_muted = this._output.is_muted;
|
||||
this._output.volume = Math.max(0, currentVolume - VOLUME_MAX * VOLUME_ADJUSTMENT_STEP);
|
||||
if (this._output.volume < 1) {
|
||||
this._output.volume = 0;
|
||||
if (!prev_muted)
|
||||
this._output.change_is_muted(true);
|
||||
}
|
||||
this._output.push_volume();
|
||||
}
|
||||
else if (direction == Clutter.ScrollDirection.UP) {
|
||||
this._output.volume = Math.min(VOLUME_MAX, currentVolume + VOLUME_MAX * VOLUME_ADJUSTMENT_STEP);
|
||||
this._output.change_is_muted(false);
|
||||
this._output.push_volume();
|
||||
}
|
||||
},
|
||||
|
||||
_onControlReady: function() {
|
||||
this._readOutput();
|
||||
this._readInput();
|
||||
},
|
||||
|
||||
_readOutput: function() {
|
||||
if (this._outputVolumeId) {
|
||||
this._output.disconnect(this._outputVolumeId);
|
||||
this._output.disconnect(this._outputMutedId);
|
||||
this._outputVolumeId = 0;
|
||||
this._outputMutedId = 0;
|
||||
}
|
||||
this._output = this._control.get_default_sink();
|
||||
if (this._output) {
|
||||
this._outputMutedId = this._output.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_output'));
|
||||
this._outputVolumeId = this._output.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_output'));
|
||||
this._mutedChanged (null, null, '_output');
|
||||
this._volumeChanged (null, null, '_output');
|
||||
} else {
|
||||
this._outputSlider.setValue(0);
|
||||
this.setIcon('audio-volume-muted-symbolic');
|
||||
}
|
||||
},
|
||||
|
||||
_readInput: function() {
|
||||
if (this._inputVolumeId) {
|
||||
this._input.disconnect(this._inputVolumeId);
|
||||
this._input.disconnect(this._inputMutedId);
|
||||
this._inputVolumeId = 0;
|
||||
this._inputMutedId = 0;
|
||||
}
|
||||
this._input = this._control.get_default_source();
|
||||
if (this._input) {
|
||||
this._inputMutedId = this._input.connect('notify::is-muted', Lang.bind(this, this._mutedChanged, '_input'));
|
||||
this._inputVolumeId = this._input.connect('notify::volume', Lang.bind(this, this._volumeChanged, '_input'));
|
||||
this._mutedChanged (null, null, '_input');
|
||||
this._volumeChanged (null, null, '_input');
|
||||
} else {
|
||||
this._separator.actor.hide();
|
||||
this._inputTitle.actor.hide();
|
||||
this._inputSlider.actor.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_maybeShowInput: function() {
|
||||
// only show input widgets if any application is recording audio
|
||||
let showInput = false;
|
||||
let recordingApps = this._control.get_source_outputs();
|
||||
if (this._input && recordingApps) {
|
||||
for (let i = 0; i < recordingApps.length; i++) {
|
||||
let outputStream = recordingApps[i];
|
||||
let id = outputStream.get_application_id();
|
||||
// but skip gnome-volume-control and pavucontrol
|
||||
// (that appear as recording because they show the input level)
|
||||
if (!id || (id != 'org.gnome.VolumeControl' && id != 'org.PulseAudio.pavucontrol')) {
|
||||
showInput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showInput) {
|
||||
this._separator.actor.show();
|
||||
this._inputTitle.actor.show();
|
||||
this._inputSlider.actor.show();
|
||||
} else {
|
||||
this._separator.actor.hide();
|
||||
this._inputTitle.actor.hide();
|
||||
this._inputSlider.actor.hide();
|
||||
}
|
||||
},
|
||||
|
||||
_volumeToIcon: function(volume) {
|
||||
if (volume <= 0) {
|
||||
return 'audio-volume-muted';
|
||||
} else {
|
||||
let v = volume / VOLUME_MAX;
|
||||
if (v < 0.33)
|
||||
return 'audio-volume-low';
|
||||
if (v > 0.8)
|
||||
return 'audio-volume-high';
|
||||
return 'audio-volume-medium';
|
||||
}
|
||||
},
|
||||
|
||||
_sliderChanged: function(slider, value, property) {
|
||||
if (this[property] == null) {
|
||||
log ('Volume slider changed for %s, but %s does not exist'.format(property, property));
|
||||
return;
|
||||
}
|
||||
let volume = value * VOLUME_MAX;
|
||||
let prev_muted = this[property].is_muted;
|
||||
if (volume < 1) {
|
||||
this[property].volume = 0;
|
||||
if (!prev_muted)
|
||||
this[property].change_is_muted(true);
|
||||
} else {
|
||||
this[property].volume = volume;
|
||||
if (prev_muted)
|
||||
this[property].change_is_muted(false);
|
||||
}
|
||||
this[property].push_volume();
|
||||
},
|
||||
|
||||
_notifyVolumeChange: function() {
|
||||
global.play_theme_sound('audio-volume-change');
|
||||
},
|
||||
|
||||
_mutedChanged: function(object, param_spec, property) {
|
||||
let muted = this[property].is_muted;
|
||||
let slider = this[property+'Slider'];
|
||||
slider.setValue(muted ? 0 : (this[property].volume / VOLUME_MAX));
|
||||
if (property == '_output') {
|
||||
if (muted)
|
||||
this.setIcon('audio-volume-muted');
|
||||
else
|
||||
this.setIcon(this._volumeToIcon(this._output.volume));
|
||||
}
|
||||
},
|
||||
|
||||
_volumeChanged: function(object, param_spec, property) {
|
||||
this[property+'Slider'].setValue(this[property].volume / VOLUME_MAX);
|
||||
if (property == '_output' && !this._output.is_muted)
|
||||
this.setIcon(this._volumeToIcon(this._output.volume));
|
||||
}
|
||||
};
|
61
js/ui/statusIconDispatcher.js
Normal file
@ -0,0 +1,61 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const Signals = imports.signals;
|
||||
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
|
||||
'bluetooth-applet': 'bluetooth',
|
||||
'gnome-volume-control-applet': 'volume', // renamed to gnome-sound-applet
|
||||
// when moved to control center
|
||||
'gnome-sound-applet': 'volume',
|
||||
'nm-applet': 'network',
|
||||
'gnome-power-manager': 'battery',
|
||||
'keyboard': 'keyboard',
|
||||
'a11y-keyboard': 'a11y',
|
||||
'kbd-scrolllock': 'keyboard',
|
||||
'kbd-numlock': 'keyboard',
|
||||
'kbd-capslock': 'keyboard'
|
||||
};
|
||||
|
||||
function StatusIconDispatcher() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
StatusIconDispatcher.prototype = {
|
||||
_init: function() {
|
||||
this._traymanager = new Shell.TrayManager();
|
||||
this._traymanager.connect('tray-icon-added', Lang.bind(this, this._onTrayIconAdded));
|
||||
this._traymanager.connect('tray-icon-removed', Lang.bind(this, this._onTrayIconRemoved));
|
||||
this._traymanager.manage_stage(global.stage);
|
||||
|
||||
// Yet-another-Ubuntu-workaround - we have to kill their
|
||||
// app-indicators, so that applications fall back to normal
|
||||
// status icons
|
||||
// http://bugzilla.gnome.org/show_bug.cgi=id=621382
|
||||
Util.killall('indicator-application-service');
|
||||
},
|
||||
|
||||
_onTrayIconAdded: function(o, icon) {
|
||||
let wmClass = (icon.wm_class || 'unknown').toLowerCase();
|
||||
let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass];
|
||||
if (role)
|
||||
this.emit('status-icon-added', icon, role);
|
||||
else
|
||||
this.emit('message-icon-added', icon);
|
||||
},
|
||||
|
||||
_onTrayIconRemoved: function(o, icon) {
|
||||
let wmClass = (icon.wm_class || 'unknown').toLowerCase();
|
||||
let role = STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass];
|
||||
if (role)
|
||||
this.emit('status-icon-removed', icon);
|
||||
else
|
||||
this.emit('message-icon-removed', icon);
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(StatusIconDispatcher.prototype);
|
@ -1,297 +1,185 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const Gdm = imports.gi.Gdm;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Gtk = imports.gi.Gtk;
|
||||
const Lang = imports.lang;
|
||||
const Shell = imports.gi.Shell;
|
||||
const St = imports.gi.St;
|
||||
const Signals = imports.signals;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Panel = imports.ui.panel;
|
||||
const GnomeSession = imports.misc.gnomeSession;
|
||||
const Main = imports.ui.main;
|
||||
const PanelMenu = imports.ui.panelMenu;
|
||||
const PopupMenu = imports.ui.popupMenu;
|
||||
const Util = imports.misc.util;
|
||||
|
||||
// Adapted from gdm/gui/user-switch-applet/applet.c
|
||||
//
|
||||
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
|
||||
// Copyright (C) 2008,2009 Red Hat, Inc.
|
||||
|
||||
const SIDEBAR_VISIBLE_KEY = 'sidebar/visible';
|
||||
|
||||
function StatusMenu() {
|
||||
function StatusMenuButton() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
StatusMenu.prototype = {
|
||||
_init: function() {
|
||||
this._gdm = Gdm.UserManager.ref_default();
|
||||
this._user = this._gdm.get_user(GLib.get_user_name());
|
||||
this._presence = new GnomeSessionPresence();
|
||||
StatusMenuButton.prototype = {
|
||||
__proto__: PanelMenu.Button.prototype,
|
||||
|
||||
_init: function() {
|
||||
PanelMenu.Button.prototype._init.call(this, St.Align.START);
|
||||
let box = new St.BoxLayout({ name: 'panelStatusMenu' });
|
||||
this.actor.set_child(box);
|
||||
|
||||
this._gdm = Gdm.UserManager.ref_default();
|
||||
this._gdm.queue_load();
|
||||
|
||||
this._user = this._gdm.get_user(GLib.get_user_name());
|
||||
this._presence = new GnomeSession.Presence();
|
||||
this._presenceItems = {};
|
||||
|
||||
this.actor = new St.BoxLayout({ name: 'StatusMenu' });
|
||||
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
||||
|
||||
this._iconBox = new St.Bin();
|
||||
this.actor.add(this._iconBox, { y_align: St.Align.MIDDLE });
|
||||
box.add(this._iconBox, { y_align: St.Align.MIDDLE, y_fill: false });
|
||||
|
||||
let textureCache = Shell.TextureCache.get_default();
|
||||
// FIXME: these icons are all wrong (likewise in createSubMenu)
|
||||
this._availableIcon = textureCache.load_icon_name('gtk-yes', 16);
|
||||
this._busyIcon = textureCache.load_icon_name('gtk-no', 16);
|
||||
this._invisibleIcon = textureCache.load_icon_name('gtk-close', 16);
|
||||
this._idleIcon = textureCache.load_icon_name('gtk-media-pause', 16);
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
this._availableIcon = new St.Icon({ icon_name: 'user-available', style_class: 'popup-menu-icon' });
|
||||
this._busyIcon = new St.Icon({ icon_name: 'user-busy', style_class: 'popup-menu-icon' });
|
||||
this._invisibleIcon = new St.Icon({ icon_name: 'user-invisible', style_class: 'popup-menu-icon' });
|
||||
this._idleIcon = new St.Icon({ icon_name: 'user-idle', style_class: 'popup-menu-icon' });
|
||||
|
||||
this._presence.connect('StatusChanged', Lang.bind(this, this._updatePresenceIcon));
|
||||
this._presence.getStatus(Lang.bind(this, this._updatePresenceIcon));
|
||||
|
||||
this._name = new St.Label({ text: this._user.get_real_name() });
|
||||
this.actor.add(this._name, { expand: true, y_align: St.Align.MIDDLE });
|
||||
this._userNameChangedId = this._user.connect('notify::display-name', Lang.bind(this, this._updateUserName));
|
||||
this._name = new St.Label();
|
||||
box.add(this._name, { y_align: St.Align.MIDDLE, y_fill: false });
|
||||
this._userLoadedId = this._user.connect('notify::is-loaded', Lang.bind(this, this._updateUserName));
|
||||
this._userChangedId = this._user.connect('changed', Lang.bind(this, this._updateUserName));
|
||||
|
||||
this._createSubMenu();
|
||||
this._gdm.connect('users-loaded', Lang.bind(this, this._updateSwitchUser));
|
||||
this._gdm.connect('notify::is-loaded', Lang.bind(this, this._updateSwitchUser));
|
||||
this._gdm.connect('user-added', Lang.bind(this, this._updateSwitchUser));
|
||||
this._gdm.connect('user-removed', Lang.bind(this, this._updateSwitchUser));
|
||||
},
|
||||
|
||||
_onDestroy: function() {
|
||||
this._user.disconnect(this._userNameChangedId);
|
||||
this._user.disconnect(this._userLoadedId);
|
||||
this._user.disconnect(this._userChangedId);
|
||||
},
|
||||
|
||||
_updateUserName: function() {
|
||||
this._name.set_text(this._user.get_real_name());
|
||||
if (this._user.is_loaded)
|
||||
this._name.set_text(this._user.get_real_name());
|
||||
else
|
||||
this._name.set_text("");
|
||||
},
|
||||
|
||||
_updateSwitchUser: function() {
|
||||
let users = this._gdm.list_users();
|
||||
if (users.length > 1)
|
||||
this._loginScreenItem.show();
|
||||
if (this._gdm.can_switch ())
|
||||
this._loginScreenItem.actor.show();
|
||||
else
|
||||
this._loginScreenItem.hide();
|
||||
this._loginScreenItem.actor.hide();
|
||||
},
|
||||
|
||||
_updatePresenceIcon: function(presence, status) {
|
||||
if (status == GnomeSessionPresenceStatus.AVAILABLE)
|
||||
if (status == GnomeSession.PresenceStatus.AVAILABLE)
|
||||
this._iconBox.child = this._availableIcon;
|
||||
else if (status == GnomeSessionPresenceStatus.BUSY)
|
||||
else if (status == GnomeSession.PresenceStatus.BUSY)
|
||||
this._iconBox.child = this._busyIcon;
|
||||
else if (status == GnomeSessionPresenceStatus.INVISIBLE)
|
||||
else if (status == GnomeSession.PresenceStatus.INVISIBLE)
|
||||
this._iconBox.child = this._invisibleIcon;
|
||||
else
|
||||
this._iconBox.child = this._idleIcon;
|
||||
},
|
||||
|
||||
// The menu
|
||||
|
||||
_createImageMenuItem: function(label, iconName, forceIcon) {
|
||||
let image = new Gtk.Image();
|
||||
let item = new Gtk.ImageMenuItem({ label: label,
|
||||
image: image,
|
||||
always_show_image: forceIcon == true });
|
||||
item.connect('style-set', Lang.bind(this,
|
||||
function() {
|
||||
image.set_from_icon_name(iconName, Gtk.IconSize.MENU);
|
||||
}));
|
||||
|
||||
return item;
|
||||
for (let itemStatus in this._presenceItems)
|
||||
this._presenceItems[itemStatus].setShowDot(itemStatus == status);
|
||||
},
|
||||
|
||||
_createSubMenu: function() {
|
||||
this._menu = new Gtk.Menu();
|
||||
this._menu.connect('deactivate', Lang.bind(this, function() { this.emit('deactivated'); }));
|
||||
|
||||
let item;
|
||||
|
||||
item = this._createImageMenuItem(_('Available'), 'gtk-yes', true);
|
||||
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSessionPresenceStatus.AVAILABLE));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
item = new PopupMenu.PopupImageMenuItem(_("Available"), 'user-available', true);
|
||||
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSession.PresenceStatus.AVAILABLE));
|
||||
this.menu.addMenuItem(item);
|
||||
this._presenceItems[GnomeSession.PresenceStatus.AVAILABLE] = item;
|
||||
|
||||
item = this._createImageMenuItem(_('Busy'), 'gtk-no', true);
|
||||
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSessionPresenceStatus.BUSY));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
item = new PopupMenu.PopupImageMenuItem(_("Busy"), 'user-busy', true);
|
||||
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSession.PresenceStatus.BUSY));
|
||||
this.menu.addMenuItem(item);
|
||||
this._presenceItems[GnomeSession.PresenceStatus.BUSY] = item;
|
||||
|
||||
item = this._createImageMenuItem(_('Invisible'), 'gtk-close', true);
|
||||
item.connect('activate', Lang.bind(this, this._setPresenceStatus, GnomeSessionPresenceStatus.INVISIBLE));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this.menu.addMenuItem(item);
|
||||
|
||||
item = new Gtk.SeparatorMenuItem();
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
item = new PopupMenu.PopupMenuItem(_("My Account"));
|
||||
item.connect('activate', Lang.bind(this, this._onMyAccountActivate));
|
||||
this.menu.addMenuItem(item);
|
||||
|
||||
item = this._createImageMenuItem(_('Account Information...'), 'user-info');
|
||||
item.connect('activate', Lang.bind(this, this._onAccountInformationActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
|
||||
let gconf = Shell.GConf.get_default();
|
||||
item = new Gtk.CheckMenuItem({ label: _('Sidebar'),
|
||||
active: gconf.get_boolean(SIDEBAR_VISIBLE_KEY) });
|
||||
item.connect('activate', Lang.bind(this,
|
||||
function() {
|
||||
gconf.set_boolean(SIDEBAR_VISIBLE_KEY, this._sidebarItem.active);
|
||||
}));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
this._sidebarItem = item;
|
||||
|
||||
item = this._createImageMenuItem(_('System Preferences...'), 'preferences-desktop');
|
||||
item = new PopupMenu.PopupMenuItem(_("System Settings"));
|
||||
item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
this.menu.addMenuItem(item);
|
||||
|
||||
item = new Gtk.SeparatorMenuItem();
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this.menu.addMenuItem(item);
|
||||
|
||||
item = this._createImageMenuItem(_('Lock Screen'), 'system-lock-screen');
|
||||
item = new PopupMenu.PopupMenuItem(_("Lock Screen"));
|
||||
item.connect('activate', Lang.bind(this, this._onLockScreenActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
this.menu.addMenuItem(item);
|
||||
|
||||
item = this._createImageMenuItem(_('Switch User'), 'system-users');
|
||||
item = new PopupMenu.PopupMenuItem(_("Switch User"));
|
||||
item.connect('activate', Lang.bind(this, this._onLoginScreenActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
this.menu.addMenuItem(item);
|
||||
this._loginScreenItem = item;
|
||||
|
||||
item = this._createImageMenuItem(_('Log Out...'), 'system-log-out');
|
||||
item = new PopupMenu.PopupMenuItem(_("Log Out..."));
|
||||
item.connect('activate', Lang.bind(this, this._onQuitSessionActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
this.menu.addMenuItem(item);
|
||||
|
||||
item = this._createImageMenuItem(_('Shut Down...'), 'system-shutdown');
|
||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this.menu.addMenuItem(item);
|
||||
|
||||
item = new PopupMenu.PopupMenuItem(_("Suspend..."));
|
||||
item.connect('activate', Lang.bind(this, this._onShutDownActivate));
|
||||
this._menu.append(item);
|
||||
item.show();
|
||||
this.menu.addMenuItem(item);
|
||||
|
||||
item = new PopupMenu.PopupMenuItem(_("Shut Down..."));
|
||||
item.connect('activate', Lang.bind(this, this._onShutDownActivate));
|
||||
this.menu.addMenuItem(item);
|
||||
},
|
||||
|
||||
_setPresenceStatus: function(item, status) {
|
||||
_setPresenceStatus: function(item, event, status) {
|
||||
this._presence.setStatus(status);
|
||||
},
|
||||
|
||||
_onAccountInformationActivate: function() {
|
||||
this._spawn(['gnome-about-me']);
|
||||
_onMyAccountActivate: function() {
|
||||
Main.overview.hide();
|
||||
Util.spawnDesktop('gnome-user-accounts-panel');
|
||||
},
|
||||
|
||||
_onPreferencesActivate: function() {
|
||||
this._spawn(['gnome-control-center']);
|
||||
Main.overview.hide();
|
||||
Util.spawnDesktop('gnome-control-center');
|
||||
},
|
||||
|
||||
_onLockScreenActivate: function() {
|
||||
this._spawn(['gnome-screensaver-command', '--lock']);
|
||||
Main.overview.hide();
|
||||
Util.spawn(['gnome-screensaver-command', '--lock']);
|
||||
},
|
||||
|
||||
_onLoginScreenActivate: function() {
|
||||
Main.overview.hide();
|
||||
this._gdm.goto_login_session();
|
||||
this._onLockScreenActivate();
|
||||
},
|
||||
|
||||
_onQuitSessionActivate: function() {
|
||||
this._spawn(['gnome-session-save', '--logout-dialog']);
|
||||
Main.overview.hide();
|
||||
Util.spawn(['gnome-session-save', '--logout-dialog']);
|
||||
},
|
||||
|
||||
_onShutDownActivate: function() {
|
||||
this._spawn(['gnome-session-save', '--shutdown-dialog']);
|
||||
},
|
||||
|
||||
_spawn: function(args) {
|
||||
// FIXME: once Shell.Process gets support for signalling
|
||||
// errors we should pop up an error dialog or something here
|
||||
// on failure
|
||||
let p = new Shell.Process({'args' : args});
|
||||
p.run();
|
||||
},
|
||||
|
||||
// shell_status_menu_toggle:
|
||||
// @event: event causing the toggle
|
||||
//
|
||||
// If the menu is not currently up, pops it up. Otherwise, hides it.
|
||||
// Popping up may fail if another grab is already active; check with
|
||||
// isActive().
|
||||
toggle: function(event) {
|
||||
if (this._menu.visible)
|
||||
this._menu.popdown();
|
||||
else {
|
||||
// We don't want to overgrab a Mutter grab with the grab
|
||||
// that GTK+ uses on menus.
|
||||
if (global.display_is_grabbed())
|
||||
return;
|
||||
|
||||
let [menuWidth, menuHeight] = this._menu.get_size_request ();
|
||||
|
||||
let panel;
|
||||
for (panel = this.actor; panel; panel = panel.get_parent()) {
|
||||
if (panel._delegate instanceof Panel.Panel)
|
||||
break;
|
||||
}
|
||||
|
||||
let [panelX, panelY] = panel.get_transformed_position();
|
||||
let [panelWidth, panelHeight] = panel.get_transformed_size();
|
||||
|
||||
let menuX = Math.round(panelX + panelWidth - menuWidth);
|
||||
let menuY = Math.round(panelY + panelHeight);
|
||||
|
||||
Shell.popup_menu(this._menu, event.get_button(), event.get_time(),
|
||||
menuX, menuY);
|
||||
}
|
||||
},
|
||||
|
||||
// isActive:
|
||||
//
|
||||
// Gets whether the menu is currently popped up
|
||||
//
|
||||
// Return value: %true if the menu is currently popped up
|
||||
isActive: function() {
|
||||
return this._menu.visible;
|
||||
Main.overview.hide();
|
||||
Util.spawn(['gnome-session-save', '--shutdown-dialog']);
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(StatusMenu.prototype);
|
||||
|
||||
|
||||
const GnomeSessionPresenceIface = {
|
||||
name: 'org.gnome.SessionManager.Presence',
|
||||
methods: [{ name: 'SetStatus',
|
||||
inSignature: 'u' }],
|
||||
properties: [{ name: 'status',
|
||||
signature: 'u',
|
||||
access: 'readwrite' }],
|
||||
signals: [{ name: 'StatusChanged',
|
||||
inSignature: 'u' }]
|
||||
};
|
||||
|
||||
const GnomeSessionPresenceStatus = {
|
||||
AVAILABLE: 0,
|
||||
INVISIBLE: 1,
|
||||
BUSY: 2,
|
||||
IDLE: 3
|
||||
};
|
||||
|
||||
function GnomeSessionPresence() {
|
||||
this._init();
|
||||
}
|
||||
|
||||
GnomeSessionPresence.prototype = {
|
||||
_init: function() {
|
||||
DBus.session.proxifyObject(this, 'org.gnome.SessionManager', '/org/gnome/SessionManager/Presence', this);
|
||||
this.connect('StatusChanged', Lang.bind(this, function (proxy, status) { this.status = status; }));
|
||||
},
|
||||
|
||||
getStatus: function(callback) {
|
||||
this.GetRemote('status', Lang.bind(this,
|
||||
function(status, ex) {
|
||||
if (!ex)
|
||||
callback(this, status);
|
||||
}));
|
||||
},
|
||||
|
||||
setStatus: function(status) {
|
||||
this.SetStatusRemote(status);
|
||||
}
|
||||
};
|
||||
DBus.proxifyPrototype(GnomeSessionPresence.prototype, GnomeSessionPresenceIface);
|
||||
|
||||
|
703
js/ui/telepathyClient.js
Normal file
@ -0,0 +1,703 @@
|
||||
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||
|
||||
const DBus = imports.dbus;
|
||||
const GLib = imports.gi.GLib;
|
||||
const Lang = imports.lang;
|
||||
const Mainloop = imports.mainloop;
|
||||
const Signals = imports.signals;
|
||||
const St = imports.gi.St;
|
||||
const Gettext = imports.gettext.domain('gnome-shell');
|
||||
const _ = Gettext.gettext;
|
||||
|
||||
const Main = imports.ui.main;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const Telepathy = imports.misc.telepathy;
|
||||
|
||||
let contactManager;
|
||||
let channelDispatcher;
|
||||
|
||||
// See Notification.appendMessage
|
||||
const SCROLLBACK_IMMEDIATE_TIME = 60; // 1 minute
|
||||
const SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes
|
||||
const SCROLLBACK_RECENT_LENGTH = 20;
|
||||
const SCROLLBACK_IDLE_LENGTH = 5;
|
||||
|
||||
// A 'Qualified_Property_Value_Map' that represents a single-user
|
||||
// text-based chat.
|
||||
let singleUserTextChannel = {};
|
||||
singleUserTextChannel[Telepathy.CHANNEL_NAME + '.ChannelType'] = Telepathy.CHANNEL_TEXT_NAME;
|
||||
singleUserTextChannel[Telepathy.CHANNEL_NAME + '.TargetHandleType'] = Telepathy.HandleType.CONTACT;
|
||||
|
||||
// Some protocols only support 'multi-user' chats, and single-user
|
||||
// chats are just treated as multi-user chats with only one other
|
||||
// participant. Telepathy uses HandleType.NONE for all chats in these
|
||||
// protocols; there's no good way for us to tell if the channel is
|
||||
// single- or multi-user.
|
||||
let oneOrMoreUserTextChannel = {};
|
||||
oneOrMoreUserTextChannel[Telepathy.CHANNEL_NAME + '.ChannelType'] = Telepathy.CHANNEL_TEXT_NAME;
|
||||
oneOrMoreUserTextChannel[Telepathy.CHANNEL_NAME + '.TargetHandleType'] = Telepathy.HandleType.NONE;
|
||||
|
||||
// The (non-chat) channel indicating the users whose presence
|
||||
// information we subscribe to
|
||||
let subscribedContactsChannel = {};
|
||||
subscribedContactsChannel[Telepathy.CHANNEL_NAME + '.ChannelType'] = Telepathy.CHANNEL_CONTACT_LIST_NAME;
|
||||
subscribedContactsChannel[Telepathy.CHANNEL_NAME + '.TargetHandleType'] = Telepathy.HandleType.LIST;
|
||||
subscribedContactsChannel[Telepathy.CHANNEL_NAME + '.TargetID'] = 'subscribe';
|
||||
|
||||
const NotificationDirection = {
|
||||
SENT: 'chat-sent',
|
||||
RECEIVED: 'chat-received'
|
||||
};
|
||||
|
||||
// This is GNOME Shell's implementation of the Telepathy 'Client'
|
||||
// interface. Specifically, the shell is a Telepathy 'Observer', which
|
||||
// lets us see messages even if they belong to another app (eg,
|
||||
// Empathy).
|
||||
|
||||
function Client() {
|
||||
this._init();
|
||||
};
|
||||
|
||||
Client.prototype = {
|
||||
_init : function() {
|
||||
let name = Telepathy.CLIENT_NAME + '.GnomeShell';
|
||||
DBus.session.exportObject(Telepathy.nameToPath(name), this);
|
||||
DBus.session.acquire_name(name, DBus.SINGLE_INSTANCE,
|
||||
function (name) { /* FIXME: acquired */ },
|
||||
function (name) { /* FIXME: lost */ });
|
||||
|
||||
this._accounts = {};
|
||||
this._sources = {};
|
||||
|
||||
contactManager = new ContactManager();
|
||||
contactManager.connect('presence-changed', Lang.bind(this, this._presenceChanged));
|
||||
|
||||
channelDispatcher = new Telepathy.ChannelDispatcher(DBus.session,
|
||||
Telepathy.CHANNEL_DISPATCHER_NAME,
|
||||
Telepathy.nameToPath(Telepathy.CHANNEL_DISPATCHER_NAME));
|
||||
|
||||
// Acquire existing connections. (Needed to make things work
|
||||
// through a restart.)
|
||||
let accountManager = new Telepathy.AccountManager(DBus.session,
|
||||
Telepathy.ACCOUNT_MANAGER_NAME,
|
||||
Telepathy.nameToPath(Telepathy.ACCOUNT_MANAGER_NAME));
|
||||
accountManager.GetRemote('ValidAccounts', Lang.bind(this,
|
||||
function (accounts, err) {
|
||||
if (!accounts)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < accounts.length; i++)
|
||||
this._gotAccount(accounts[i]);
|
||||
}));
|
||||
accountManager.connect('AccountValidityChanged', Lang.bind(this, this._accountValidityChanged));
|
||||
},
|
||||
|
||||
_accountValidityChanged: function(accountManager, accountPath, valid) {
|
||||
if (!valid) {
|
||||
delete this._accounts[accountPath];
|
||||
// We don't need to clean up connections, sources, etc; they'll
|
||||
// get Closed and cleaned up independently.
|
||||
} else
|
||||
this._gotAccount(accountPath);
|
||||
},
|
||||
|
||||
_gotAccount: function(accountPath) {
|
||||
let account = new Telepathy.Account(DBus.session,
|
||||
Telepathy.ACCOUNT_MANAGER_NAME,
|
||||
accountPath);
|
||||
this._accounts[accountPath] = account;
|
||||
account.GetRemote('Connection', Lang.bind(this,
|
||||
function (connPath, err) {
|
||||
if (!connPath || connPath == '/')
|
||||
return;
|
||||
|
||||
let connReq = new Telepathy.ConnectionRequests(DBus.session,
|
||||
Telepathy.pathToName(connPath),
|
||||
connPath);
|
||||
connReq.GetRemote('Channels', Lang.bind(this,
|
||||
function(channels, err) {
|
||||
if (!channels)
|
||||
return;
|
||||
|
||||
this._addChannels(accountPath, connPath, channels);
|
||||
}));
|
||||
|
||||
contactManager.addConnection(connPath);
|
||||
}));
|
||||
},
|
||||
|
||||
get Interfaces() {
|
||||
return [ Telepathy.CLIENT_OBSERVER_NAME ];
|
||||
},
|
||||
|
||||
get ObserverChannelFilter() {
|
||||
return [ singleUserTextChannel, oneOrMoreUserTextChannel ];
|
||||
},
|
||||
|
||||
ObserveChannels: function(accountPath, connPath, channels,
|
||||
dispatchOperation, requestsSatisfied,
|
||||
observerInfo) {
|
||||
this._addChannels(accountPath, connPath, channels);
|
||||
},
|
||||
|
||||
_addChannels: function(accountPath, connPath, channelDetailsList) {
|
||||
for (let i = 0; i < channelDetailsList.length; i++) {
|
||||
let [channelPath, props] = channelDetailsList[i];
|
||||
|
||||
// If this is being called from the startup code then it
|
||||
// won't have passed through our filters, so we need to
|
||||
// check the channel/targetHandle type ourselves.
|
||||
|
||||
let channelType = props[Telepathy.CHANNEL_NAME + '.ChannelType'];
|
||||
if (channelType != Telepathy.CHANNEL_TEXT_NAME)
|
||||
continue;
|
||||
|
||||
let targetHandleType = props[Telepathy.CHANNEL_NAME + '.TargetHandleType'];
|
||||
if (targetHandleType != Telepathy.HandleType.CONTACT &&
|
||||
targetHandleType != Telepathy.HandleType.NONE)
|
||||
continue;
|
||||
|
||||
let targetHandle = props[Telepathy.CHANNEL_NAME + '.TargetHandle'];
|
||||
let targetId = props[Telepathy.CHANNEL_NAME + '.TargetID'];
|
||||
|
||||
if (this._sources[connPath + ':' + targetHandle])
|
||||
continue;
|
||||
|
||||
let source = new Source(accountPath, connPath, channelPath,
|
||||
targetHandle, targetHandleType, targetId);
|
||||
this._sources[connPath + ':' + targetHandle] = source;
|
||||
source.connect('destroy', Lang.bind(this,
|
||||
function() {
|
||||
delete this._sources[connPath + ':' + targetHandle];
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
_presenceChanged: function(contactManager, connPath, handle,
|
||||
type, message) {
|
||||
let source = this._sources[connPath + ':' + handle];
|
||||
if (!source)
|
||||
return;
|
||||
|
||||
source.setPresence(type, message);
|
||||
}
|
||||
};
|
||||
DBus.conformExport(Client.prototype, Telepathy.ClientIface);
|
||||
DBus.conformExport(Client.prototype, Telepathy.ClientObserverIface);
|
||||
|
||||
|
||||
function ContactManager() {
|
||||
this._init();
|
||||
};
|
||||
|
||||
ContactManager.prototype = {
|
||||
_init: function() {
|
||||
this._connections = {};
|
||||
// Note that if we changed this to '/telepathy/avatars' then
|
||||
// we would share cache files with empathy. But since this is
|
||||
// not documented/guaranteed, it seems a little sketchy
|
||||
this._cacheDir = GLib.get_user_cache_dir() + '/gnome-shell/avatars';
|
||||
},
|
||||
|
||||
addConnection: function(connPath) {
|
||||
let info = this._connections[connPath];
|
||||
if (info)
|
||||
return info;
|
||||
|
||||
info = {};
|
||||
|
||||
// Figure out the cache subdirectory for this connection by
|
||||
// parsing the connection manager name (eg, 'gabble') and
|
||||
// protocol name (eg, 'jabber') from the Connection's path.
|
||||
// Telepathy requires the D-Bus path for a connection to have
|
||||
// a specific form, and explicitly says that clients are
|
||||
// allowed to parse it.
|
||||
let match = connPath.match(/\/org\/freedesktop\/Telepathy\/Connection\/([^\/]*\/[^\/]*)\/.*/);
|
||||
if (!match)
|
||||
throw new Error('Could not parse connection path ' + connPath);
|
||||
|
||||
info.cacheDir = this._cacheDir + '/' + match[1];
|
||||
GLib.mkdir_with_parents(info.cacheDir, 0x1c0); // 0x1c0 = octal 0700
|
||||
|
||||
// info.names[handle] is @handle's real name
|
||||
// info.tokens[handle] is the token for @handle's avatar
|
||||
info.names = {};
|
||||
info.tokens = {};
|
||||
|
||||
// info.icons[handle] is an array of the icon actors currently
|
||||
// being displayed for @handle. These will be updated
|
||||
// automatically if @handle's avatar changes.
|
||||
info.icons = {};
|
||||
|
||||
let connName = Telepathy.pathToName(connPath);
|
||||
|
||||
info.connectionAvatars = new Telepathy.ConnectionAvatars(DBus.session, connName, connPath);
|
||||
info.updatedId = info.connectionAvatars.connect(
|
||||
'AvatarUpdated', Lang.bind(this, this._avatarUpdated));
|
||||
info.retrievedId = info.connectionAvatars.connect(
|
||||
'AvatarRetrieved', Lang.bind(this, this._avatarRetrieved));
|
||||
|
||||
info.connectionContacts = new Telepathy.ConnectionContacts(DBus.session, connName, connPath);
|
||||
|
||||
info.connectionPresence = new Telepathy.ConnectionSimplePresence(DBus.session, connName, connPath);
|
||||
info.presenceChangedId = info.connectionPresence.connect(
|
||||
'PresencesChanged', Lang.bind(this, this._presencesChanged));
|
||||
|
||||
let conn = new Telepathy.Connection(DBus.session, connName, connPath);
|
||||
info.statusChangedId = conn.connect('StatusChanged', Lang.bind(this,
|
||||
function (status, reason) {
|
||||
if (status == Telepathy.ConnectionStatus.DISCONNECTED)
|
||||
this._removeConnection(conn);
|
||||
}));
|
||||
|
||||
let connReq = new Telepathy.ConnectionRequests(DBus.session,
|
||||
connName, connPath);
|
||||
connReq.EnsureChannelRemote(subscribedContactsChannel, Lang.bind(this,
|
||||
function (result, err) {
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
let [mine, channelPath, props] = result;
|
||||
this._gotContactsChannel(connPath, channelPath, props);
|
||||
}));
|
||||
|
||||
this._connections[connPath] = info;
|
||||
return info;
|
||||
},
|
||||
|
||||
_gotContactsChannel: function(connPath, channelPath, props) {
|
||||
let info = this._connections[connPath];
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
info.contactsGroup = new Telepathy.ChannelGroup(DBus.session,
|
||||
Telepathy.pathToName(connPath),
|
||||
channelPath);
|
||||
info.contactsListChangedId =
|
||||
info.contactsGroup.connect('MembersChanged', Lang.bind(this, this._contactsListChanged, info));
|
||||
|
||||
info.contactsGroup.GetRemote('Members', Lang.bind(this,
|
||||
function(contacts, err) {
|
||||
if (!contacts)
|
||||
return;
|
||||
|
||||
info.connectionContacts.GetContactAttributesRemote(
|
||||
contacts, [Telepathy.CONNECTION_ALIASING_NAME], false,
|
||||
Lang.bind(this, this._gotContactAttributes, info));
|
||||
}));
|
||||
},
|
||||
|
||||
_contactsListChanged: function(group, message, added, removed,
|
||||
local_pending, remote_pending,
|
||||
actor, reason, info) {
|
||||
for (let i = 0; i < removed.length; i++)
|
||||
delete info.names[removed[i]];
|
||||
|
||||
info.connectionContacts.GetContactAttributesRemote(
|
||||
added, [Telepathy.CONNECTION_ALIASING_NAME], false,
|
||||
Lang.bind(this, this._gotContactAttributes, info));
|
||||
},
|
||||
|
||||
_gotContactAttributes: function(attrs, err, info) {
|
||||
if (!attrs)
|
||||
return;
|
||||
|
||||
for (let handle in attrs)
|
||||
info.names[handle] = attrs[handle][Telepathy.CONNECTION_ALIASING_NAME + '/alias'];
|
||||
},
|
||||
|
||||
_presencesChanged: function(conn, presences, err) {
|
||||
if (!presences)
|
||||
return;
|
||||
|
||||
let info = this._connections[conn.getPath()];
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
for (let handle in presences) {
|
||||
let [type, status, message] = presences[handle];
|
||||
this.emit('presence-changed', conn.getPath(), handle, type, message);
|
||||
}
|
||||
},
|
||||
|
||||
_removeConnection: function(conn) {
|
||||
let info = this._connections[conn.getPath()];
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
conn.disconnect(info.statusChangedId);
|
||||
info.connectionAvatars.disconnect(info.updatedId);
|
||||
info.connectionAvatars.disconnect(info.retrievedId);
|
||||
info.connectionPresence.disconnect(info.presenceChangedId);
|
||||
info.contactsGroup.disconnect(info.contactsListChangedId);
|
||||
|
||||
delete this._connections[conn.getPath()];
|
||||
},
|
||||
|
||||
_getFileForToken: function(info, token) {
|
||||
return info.cacheDir + '/' + Telepathy.escapeAsIdentifier(token);
|
||||
},
|
||||
|
||||
_setIcon: function(iconBox, info, handle) {
|
||||
let textureCache = St.TextureCache.get_default();
|
||||
let token = info.tokens[handle];
|
||||
let file;
|
||||
|
||||
if (token) {
|
||||
file = this._getFileForToken(info, token);
|
||||
if (!GLib.file_test(file, GLib.FileTest.EXISTS))
|
||||
file = null;
|
||||
}
|
||||
|
||||
if (file) {
|
||||
let uri = GLib.filename_to_uri(file, null);
|
||||
iconBox.child = textureCache.load_uri_async(uri, iconBox._size, iconBox._size);
|
||||
} else {
|
||||
iconBox.child = new St.Icon({ icon_name: 'stock_person',
|
||||
icon_type: St.IconType.FULLCOLOR,
|
||||
icon_size: iconBox._size });
|
||||
}
|
||||
},
|
||||
|
||||
_updateIcons: function(info, handle) {
|
||||
if (!info.icons[handle])
|
||||
return;
|
||||
|
||||
for (let i = 0; i < info.icons[handle].length; i++) {
|
||||
let iconBox = info.icons[handle][i];
|
||||
this._setIcon(iconBox, info, handle);
|
||||
}
|
||||
},
|
||||
|
||||
_avatarUpdated: function(conn, handle, token) {
|
||||
let info = this._connections[conn.getPath()];
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
if (info.tokens[handle] == token)
|
||||
return;
|
||||
|
||||
info.tokens[handle] = token;
|
||||
if (token != '') {
|
||||
let file = this._getFileForToken(info, token);
|
||||
if (!GLib.file_test(file, GLib.FileTest.EXISTS)) {
|
||||
info.connectionAvatars.RequestAvatarsRemote([handle]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this._updateIcons(info, handle);
|
||||
},
|
||||
|
||||
_avatarRetrieved: function(conn, handle, token, avatarData, mimeType) {
|
||||
let info = this._connections[conn.getPath()];
|
||||
if (!info)
|
||||
return;
|
||||
|
||||
let file = this._getFileForToken(info, token);
|
||||
let success = false;
|
||||
try {
|
||||
success = GLib.file_set_contents(file, avatarData, avatarData.length);
|
||||
} catch (e) {
|
||||
logError(e, 'Error caching avatar data');
|
||||
}
|
||||
|
||||
if (success)
|
||||
this._updateIcons(info, handle);
|
||||
},
|
||||
|
||||
createAvatar: function(conn, handle, size) {
|
||||
let iconBox = new St.Bin({ style_class: 'avatar-box' });
|
||||
iconBox._size = size;
|
||||
|
||||
let info = this._connections[conn.getPath()];
|
||||
if (!info)
|
||||
info = this.addConnection(conn.getPath());
|
||||
|
||||
if (!info.icons[handle])
|
||||
info.icons[handle] = [];
|
||||
info.icons[handle].push(iconBox);
|
||||
|
||||
iconBox.connect('destroy', Lang.bind(this,
|
||||
function() {
|
||||
let i = info.icons[handle].indexOf(iconBox);
|
||||
if (i != -1)
|
||||
info.icons[handle].splice(i, 1);
|
||||
}));
|
||||
|
||||
// If we already have the icon cached and know its token, this
|
||||
// will fill it in. Otherwise it will fill in the default
|
||||
// icon.
|
||||
this._setIcon(iconBox, info, handle);
|
||||
|
||||
// Asynchronously load the real avatar if we don't have it yet.
|
||||
if (info.tokens[handle] == null) {
|
||||
info.connectionAvatars.GetKnownAvatarTokensRemote([handle], Lang.bind(this,
|
||||
function (tokens, err) {
|
||||
let token = tokens && tokens[handle] ? tokens[handle] : '';
|
||||
this._avatarUpdated(conn, handle, token);
|
||||
}));
|
||||
}
|
||||
|
||||
return iconBox;
|
||||
}
|
||||
};
|
||||
Signals.addSignalMethods(ContactManager.prototype);
|
||||
|
||||
|
||||
function Source(accountPath, connPath, channelPath, targetHandle, targetHandleType, targetId) {
|
||||
this._init(accountPath, connPath, channelPath, targetHandle, targetHandleType, targetId);
|
||||
}
|
||||
|
||||
Source.prototype = {
|
||||
__proto__: MessageTray.Source.prototype,
|
||||
|
||||
_init: function(accountPath, connPath, channelPath, targetHandle, targetHandleType, targetId) {
|
||||
MessageTray.Source.prototype._init.call(this, targetId);
|
||||
|
||||
this.isChat = true;
|
||||
|
||||
this._accountPath = accountPath;
|
||||
|
||||
let connName = Telepathy.pathToName(connPath);
|
||||
this._conn = new Telepathy.Connection(DBus.session, connName, connPath);
|
||||
this._channel = new Telepathy.Channel(DBus.session, connName, channelPath);
|
||||
this._closedId = this._channel.connect('Closed', Lang.bind(this, this._channelClosed));
|
||||
|
||||
this._targetHandle = targetHandle;
|
||||
this._targetHandleType = targetHandleType;
|
||||
this._targetId = targetId;
|
||||
|
||||
if (targetHandleType == Telepathy.HandleType.CONTACT) {
|
||||
let aliasing = new Telepathy.ConnectionAliasing(DBus.session, connName, connPath);
|
||||
aliasing.RequestAliasesRemote([this._targetHandle], Lang.bind(this,
|
||||
function (aliases, err) {
|
||||
if (aliases && aliases.length)
|
||||
this.title = aliases[0];
|
||||
}));
|
||||
}
|
||||
|
||||
this._notification = new Notification(this);
|
||||
this._notification.setUrgency(MessageTray.Urgency.HIGH);
|
||||
|
||||
// Since we only create sources when receiving a message, this
|
||||
// is a plausible default
|
||||
this._presence = Telepathy.ConnectionPresenceType.AVAILABLE;
|
||||
|
||||
this._channelText = new Telepathy.ChannelText(DBus.session, connName, channelPath);
|
||||
this._sentId = this._channelText.connect('Sent', Lang.bind(this, this._messageSent));
|
||||
this._receivedId = this._channelText.connect('Received', Lang.bind(this, this._messageReceived));
|
||||
|
||||
this._channelText.ListPendingMessagesRemote(false, Lang.bind(this, this._gotPendingMessages));
|
||||
|
||||
this._setSummaryIcon(this.createNotificationIcon());
|
||||
},
|
||||
|
||||
createNotificationIcon: function() {
|
||||
return contactManager.createAvatar(this._conn, this._targetHandle,
|
||||
this.ICON_SIZE);
|
||||
},
|
||||
|
||||
_notificationClicked: function(notification) {
|
||||
channelDispatcher.EnsureChannelRemote(this._accountPath,
|
||||
{ 'org.freedesktop.Telepathy.Channel.ChannelType': Telepathy.CHANNEL_TEXT_NAME,
|
||||
'org.freedesktop.Telepathy.Channel.TargetHandle': this._targetHandle,
|
||||
'org.freedesktop.Telepathy.Channel.TargetHandleType': this._targetHandleType },
|
||||
global.get_current_time(),
|
||||
'',
|
||||
Lang.bind(this, this._gotChannelRequest));
|
||||
},
|
||||
|
||||
_gotChannelRequest: function (chanReqPath, ex) {
|
||||
if (ex) {
|
||||
log ('EnsureChannelRemote failed? ' + ex);
|
||||
return;
|
||||
}
|
||||
|
||||
let chanReq = new Telepathy.ChannelRequest(DBus.session, Telepathy.CHANNEL_DISPATCHER_NAME, chanReqPath);
|
||||
chanReq.ProceedRemote();
|
||||
},
|
||||
|
||||
_gotPendingMessages: function(msgs, err) {
|
||||
if (!msgs)
|
||||
return;
|
||||
|
||||
for (let i = 0; i < msgs.length; i++)
|
||||
this._messageReceived.apply(this, [this._channel].concat(msgs[i]));
|
||||
},
|
||||
|
||||
_channelClosed: function() {
|
||||
this._channel.disconnect(this._closedId);
|
||||
this._channelText.disconnect(this._receivedId);
|
||||
this._channelText.disconnect(this._sentId);
|
||||
this.destroy();
|
||||
},
|
||||
|
||||
_messageReceived: function(channel, id, timestamp, sender,
|
||||
type, flags, text) {
|
||||
this._notification.appendMessage(text, timestamp, NotificationDirection.RECEIVED);
|
||||
this.notify();
|
||||
},
|
||||
|
||||
// This is called for both messages we send from
|
||||
// our client and other clients as well.
|
||||
_messageSent: function(channel, timestamp, type, text) {
|
||||
this._notification.appendMessage(text, timestamp, NotificationDirection.SENT);
|
||||
},
|
||||
|
||||
notify: function() {
|
||||
if (!Main.messageTray.contains(this))
|
||||
Main.messageTray.add(this);
|
||||
|
||||
MessageTray.Source.prototype.notify.call(this, this._notification);
|
||||
},
|
||||
|
||||
respond: function(text) {
|
||||
this._channelText.SendRemote(Telepathy.ChannelTextMessageType.NORMAL, text);
|
||||
},
|
||||
|
||||
setPresence: function(presence, message) {
|
||||
let msg, shouldNotify;
|
||||
|
||||
if (presence == Telepathy.ConnectionPresenceType.AVAILABLE) {
|
||||
msg = _("%s is online.").format(this.title);
|
||||
shouldNotify = (this._presence == Telepathy.ConnectionPresenceType.OFFLINE);
|
||||
} else if (presence == Telepathy.ConnectionPresenceType.OFFLINE ||
|
||||
presence == Telepathy.ConnectionPresenceType.EXTENDED_AWAY) {
|
||||
presence = Telepathy.ConnectionPresenceType.OFFLINE;
|
||||
msg = _("%s is offline.").format(this.title);
|
||||
shouldNotify = (this._presence != Telepathy.ConnectionPresenceType.OFFLINE);
|
||||
} else if (presence == Telepathy.ConnectionPresenceType.AWAY) {
|
||||
msg = _("%s is away.").format(this.title);
|
||||
shouldNotify = false;
|
||||
} else if (presence == Telepathy.ConnectionPresenceType.BUSY) {
|
||||
msg = _("%s is busy.").format(this.title);
|
||||
shouldNotify = false;
|
||||
} else
|
||||
return;
|
||||
|
||||
this._presence = presence;
|
||||
|
||||
if (message)
|
||||
msg += ' <i>(' + GLib.markup_escape_text(message, -1) + ')</i>';
|
||||
|
||||
this._notification.appendPresence(msg, shouldNotify);
|
||||
if (shouldNotify)
|
||||
this.notify();
|
||||
}
|
||||
};
|
||||
|
||||
function Notification(source) {
|
||||
this._init(source);
|
||||
}
|
||||
|
||||
Notification.prototype = {
|
||||
__proto__: MessageTray.Notification.prototype,
|
||||
|
||||
_init: function(source) {
|
||||
MessageTray.Notification.prototype._init.call(this, source, source.title, null, { customContent: true });
|
||||
this.setResident(true);
|
||||
|
||||
this._responseEntry = new St.Entry({ style_class: 'chat-response' });
|
||||
this._responseEntry.clutter_text.connect('activate', Lang.bind(this, this._onEntryActivated));
|
||||
this.setActionArea(this._responseEntry);
|
||||
|
||||
this._history = [];
|
||||
this._timestampTimeoutId = 0;
|
||||
},
|
||||
|
||||
appendMessage: function(text, timestamp, direction) {
|
||||
this.update(this.source.title, text, { customContent: true });
|
||||
this._append(text, direction, timestamp);
|
||||
},
|
||||
|
||||
_append: function(text, style, timestamp) {
|
||||
let currentTime = (Date.now() / 1000);
|
||||
if (!timestamp)
|
||||
timestamp = currentTime;
|
||||
let lastMessageTime = -1;
|
||||
if (this._history.length > 0)
|
||||
lastMessageTime = this._history[0].time;
|
||||
|
||||
// Reset the old message timeout
|
||||
if (this._timestampTimeoutId)
|
||||
Mainloop.source_remove(this._timestampTimeoutId);
|
||||
|
||||
let body = this.addBody(text);
|
||||
body.add_style_class_name(style);
|
||||
this.scrollTo(St.Side.BOTTOM);
|
||||
|
||||
this._history.unshift({ actor: body, time: timestamp, realMessage: true });
|
||||
|
||||
if (timestamp < currentTime - SCROLLBACK_IMMEDIATE_TIME)
|
||||
this._appendTimestamp();
|
||||
else
|
||||
// Schedule a new timestamp in SCROLLBACK_IMMEDIATE_TIME
|
||||
// from the timestamp of the message.
|
||||
this._timestampTimeoutId = Mainloop.timeout_add_seconds(
|
||||
SCROLLBACK_IMMEDIATE_TIME - (currentTime - timestamp),
|
||||
Lang.bind(this, this._appendTimestamp));
|
||||
|
||||
if (this._history.length > 1) {
|
||||
// Keep the scrollback from growing too long. If the most
|
||||
// recent message (before the one we just added) is within
|
||||
// SCROLLBACK_RECENT_TIME, we will keep
|
||||
// SCROLLBACK_RECENT_LENGTH previous messages. Otherwise
|
||||
// we'll keep SCROLLBACK_IDLE_LENGTH messages.
|
||||
|
||||
let maxLength = (lastMessageTime < currentTime - SCROLLBACK_RECENT_TIME) ?
|
||||
SCROLLBACK_IDLE_LENGTH : SCROLLBACK_RECENT_LENGTH;
|
||||
let filteredHistory = this._history.filter(function(item) { return item.realMessage });
|
||||
if (filteredHistory.length > maxLength) {
|
||||
let lastMessageToKeep = filteredHistory[maxLength];
|
||||
let expired = this._history.splice(this._history.indexOf(lastMessageToKeep));
|
||||
for (let i = 0; i < expired.length; i++)
|
||||
expired[i].actor.destroy();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_appendTimestamp: function() {
|
||||
let lastMessageTime = this._history[0].time;
|
||||
let lastMessageDate = new Date(lastMessageTime * 1000);
|
||||
|
||||
/* Translators: this is a time format string followed by a date.
|
||||
If applicable, replace %X with a strftime format valid for your
|
||||
locale, without seconds. */
|
||||
// xgettext:no-c-format
|
||||
let timeLabel = this.addBody(lastMessageDate.toLocaleFormat(_("Sent at %X on %A")), false, { expand: true, x_fill: false, x_align: St.Align.END });
|
||||
timeLabel.add_style_class_name('chat-meta-message');
|
||||
this._history.unshift({ actor: timeLabel, time: lastMessageTime, realMessage: false });
|
||||
|
||||
this._timestampTimeoutId = 0;
|
||||
return false;
|
||||
},
|
||||
|
||||
appendPresence: function(text, asTitle) {
|
||||
if (asTitle)
|
||||
this.update(text, null, { customContent: true });
|
||||
else
|
||||
this.update(this.source.title, null, { customContent: true });
|
||||
let label = this.addBody(text);
|
||||
label.add_style_class_name('chat-meta-message');
|
||||
this._history.unshift({ actor: label, time: (Date.now() / 1000), realMessage: false});
|
||||
},
|
||||
|
||||
grabFocus: function(lockTray) {
|
||||
// Need to call the base class function first so that
|
||||
// it saves where the key focus was before.
|
||||
MessageTray.Notification.prototype.grabFocus.call(this, lockTray);
|
||||
global.stage.set_key_focus(this._responseEntry.clutter_text);
|
||||
},
|
||||
|
||||
_onEntryActivated: function() {
|
||||
let text = this._responseEntry.get_text();
|
||||
if (text == '')
|
||||
return;
|
||||
|
||||
// Telepathy sends out the Sent signal for us.
|
||||
// see Source._messageSent
|
||||
this._responseEntry.set_text('');
|
||||
this.source.respond(text);
|
||||
}
|
||||
};
|