mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-25 08:01:39 +00:00
Compare commits
835 Commits
before-tig
...
se3.4.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91a157cb2b | ||
|
|
7ced1e7b9d | ||
|
|
4c0545189e | ||
|
|
21af7b2fcd | ||
|
|
df9af9a796 | ||
|
|
933474400c | ||
|
|
b2c8e779bd | ||
|
|
e570a308bb | ||
|
|
69c01a658b | ||
|
|
0741cbe17b | ||
|
|
ad4ccfe347 | ||
|
|
57375f83f6 | ||
|
|
2d75ffefa7 | ||
|
|
87aa181e4b | ||
|
|
1f359e0391 | ||
|
|
c8af8ccc61 | ||
|
|
2daaaee893 | ||
|
|
03712860b1 | ||
|
|
9e1f79c152 | ||
|
|
edc385cfbc | ||
|
|
0f62917b55 | ||
|
|
515c195780 | ||
|
|
c63df97dd6 | ||
|
|
1f312d8532 | ||
|
|
fa547bb95e | ||
|
|
b500b34ef1 | ||
|
|
23cb71b522 | ||
|
|
720d2fce83 | ||
|
|
97f447fc27 | ||
|
|
6ee56dc55c | ||
|
|
771f22f208 | ||
|
|
80ff5f64f7 | ||
|
|
d0bfa2a6ca | ||
|
|
04a547656e | ||
|
|
657f6743c2 | ||
|
|
42b57431e0 | ||
|
|
3fc83d6245 | ||
|
|
ae16845477 | ||
|
|
49a7043b4d | ||
|
|
878c744c21 | ||
|
|
b4b9393e14 | ||
|
|
bf17eb715a | ||
|
|
cd9f54163b | ||
|
|
ef0a66f794 | ||
|
|
5fb7742cf4 | ||
|
|
22de2d803a | ||
|
|
188aa4bf2e | ||
|
|
05552c160c | ||
|
|
dd70bd6d41 | ||
|
|
70f88ba39c | ||
|
|
496f29b5c3 | ||
|
|
b0818f2bdf | ||
|
|
e0efd660f6 | ||
|
|
4592000464 | ||
|
|
93781a0f35 | ||
|
|
7dfb42fb41 | ||
|
|
a1fc64cd36 | ||
|
|
b12b70b0a1 | ||
|
|
9457a43993 | ||
|
|
ab201bfae0 | ||
|
|
66204ff0b5 | ||
|
|
95f77aef3f | ||
|
|
1e96b6f9c4 | ||
|
|
f813673d6c | ||
|
|
1da325a56b | ||
|
|
d885bf0b18 | ||
|
|
5f7e7fbb13 | ||
|
|
757f8569e6 | ||
|
|
bf86f56371 | ||
|
|
7d0948cd23 | ||
|
|
c4824b0f16 | ||
|
|
ea32af04fb | ||
|
|
e519e41e42 | ||
|
|
c937f2b20f | ||
|
|
fc477ab3b5 | ||
|
|
c53ddcdee1 | ||
|
|
9e668b11b8 | ||
|
|
f53a4c261c | ||
|
|
9a2554d24e | ||
|
|
b9ac135b89 | ||
|
|
235d3ae521 | ||
|
|
a487e83f3c | ||
|
|
4aaff8ffb1 | ||
|
|
302050b2d6 | ||
|
|
1b571f6fd2 | ||
|
|
baa2177439 | ||
|
|
29796e87b7 | ||
|
|
017e483b5a | ||
|
|
7a167208d0 | ||
|
|
475f2c7238 | ||
|
|
9392f818cc | ||
|
|
58347bc36f | ||
|
|
55db21f3e0 | ||
|
|
950cd0c341 | ||
|
|
937ea73c87 | ||
|
|
7bce70c128 | ||
|
|
f9ced08e6d | ||
|
|
4c8ce4b52d | ||
|
|
0d78201548 | ||
|
|
45d4915d88 | ||
|
|
a699a2ef45 | ||
|
|
f6dd1a6a42 | ||
|
|
e6819781f6 | ||
|
|
12467c9493 | ||
|
|
0c7d73854d | ||
|
|
a7eeb7db89 | ||
|
|
989026051c | ||
|
|
56fd94e205 | ||
|
|
c8e00cf0f7 | ||
|
|
0cca745d0d | ||
|
|
80043ccbea | ||
|
|
d62bfac95e | ||
|
|
f636333938 | ||
|
|
01f1e5e46e | ||
|
|
7ce72b1998 | ||
|
|
c4f95a4901 | ||
|
|
5b0d9f311c | ||
|
|
11d8f824e9 | ||
|
|
ceaf2ee4ba | ||
|
|
3b80de869a | ||
|
|
08813ce39c | ||
|
|
478f462ecc | ||
|
|
be53b6de76 | ||
|
|
ca602480d9 | ||
|
|
185cdbb6fa | ||
|
|
2d4903da26 | ||
|
|
98954bd582 | ||
|
|
f862f32fb4 | ||
|
|
804a5a1bbf | ||
|
|
f004535820 | ||
|
|
2abd932709 | ||
|
|
2a78b8ce41 | ||
|
|
2301082307 | ||
|
|
168546608d | ||
|
|
27c1a03496 | ||
|
|
d382cec0fe | ||
|
|
c5b7f6b3d5 | ||
|
|
725f9d51db | ||
|
|
b4815d91c7 | ||
|
|
28e5924633 | ||
|
|
66303a2076 | ||
|
|
d3918724c0 | ||
|
|
fa294ea142 | ||
|
|
565c58b3a9 | ||
|
|
d8970ca6b4 | ||
|
|
1b7068bfe9 | ||
|
|
756190cb8e | ||
|
|
3342b32882 | ||
|
|
561b469e41 | ||
|
|
69fbda5d2c | ||
|
|
63180eaa5b | ||
|
|
25faa458b9 | ||
|
|
760e4085fa | ||
|
|
fbe66d27c9 | ||
|
|
08fd17f208 | ||
|
|
f8af6dbd18 | ||
|
|
54d6fb080c | ||
|
|
8409a01bcc | ||
|
|
1e136d2703 | ||
|
|
25afe54be3 | ||
|
|
d9ae967439 | ||
|
|
9226a4fb7c | ||
|
|
fc5a0d914b | ||
|
|
fca66d5108 | ||
|
|
0571151c5f | ||
|
|
7e95404903 | ||
|
|
eb83b830df | ||
|
|
0b1bf79796 | ||
|
|
6ab074decb | ||
|
|
b7462f1d17 | ||
|
|
a2a8397e78 | ||
|
|
5c0d9411e5 | ||
|
|
cd2afeb32b | ||
|
|
b2fd022d07 | ||
|
|
878046b579 | ||
|
|
428a400848 | ||
|
|
8aa5826080 | ||
|
|
6b5d1e9894 | ||
|
|
07a6a37502 | ||
|
|
dd0af2fe15 | ||
|
|
df6d7141f1 | ||
|
|
81d4cdde33 | ||
|
|
88a613268d | ||
|
|
b7aa4a1ad8 | ||
|
|
6c75d3afdf | ||
|
|
771f26ba0f | ||
|
|
30676fd20b | ||
|
|
607433d918 | ||
|
|
29db39f144 | ||
|
|
49e49d5e1a | ||
|
|
b9b84c9b7d | ||
|
|
d80a56db78 | ||
|
|
036026d64a | ||
|
|
13ef2fae90 | ||
|
|
54731e4ba0 | ||
|
|
aa341bc5ed | ||
|
|
4426f924e2 | ||
|
|
a6deb04049 | ||
|
|
806d6be36c | ||
|
|
152a8772da | ||
|
|
5885ead5ab | ||
|
|
6fc5ce4bad | ||
|
|
a4ae503bd4 | ||
|
|
020746be10 | ||
|
|
436a8e686d | ||
|
|
b6d66f6d0b | ||
|
|
5284decd67 | ||
|
|
73d22de752 | ||
|
|
26e496a051 | ||
|
|
75f0ed14aa | ||
|
|
27cecb0dbc | ||
|
|
094d6702ba | ||
|
|
f8750dd392 | ||
|
|
76b873a504 | ||
|
|
dd031ffa1d | ||
|
|
a0991745e2 | ||
|
|
3553d1a458 | ||
|
|
1b855ad64b | ||
|
|
9a7a20472d | ||
|
|
9fac97c147 | ||
|
|
11aa2c147c | ||
|
|
2c260742f6 | ||
|
|
b8ac192d2a | ||
|
|
ae21ff988a | ||
|
|
8102c89802 | ||
|
|
48e2acf0a2 | ||
|
|
01731a8277 | ||
|
|
abe25d6967 | ||
|
|
669e4b390b | ||
|
|
1fafe04f19 | ||
|
|
43c4dcb267 | ||
|
|
b44e52689f | ||
|
|
f0441db88a | ||
|
|
0da1947902 | ||
|
|
3b4ea50119 | ||
|
|
e21627bbde | ||
|
|
70689ce057 | ||
|
|
60462698ac | ||
|
|
f2164924f7 | ||
|
|
f730f4f55a | ||
|
|
841b249b67 | ||
|
|
0f12103616 | ||
|
|
edac54c5e6 | ||
|
|
818bc908a0 | ||
|
|
26e81a8cbf | ||
|
|
664803c2ca | ||
|
|
5a26f1c2ae | ||
|
|
880f0787ce | ||
|
|
549e3e1f3b | ||
|
|
cf606a494c | ||
|
|
90f9493ccc | ||
|
|
3ec2a3ef81 | ||
|
|
b65c9b8c9a | ||
|
|
2dc588e0fd | ||
|
|
9c30486f14 | ||
|
|
518228a368 | ||
|
|
ff228035a3 | ||
|
|
eeb7f84601 | ||
|
|
ee24234156 | ||
|
|
d462fd44c4 | ||
|
|
22fedc4ee4 | ||
|
|
548bd84a4b | ||
|
|
d66afca753 | ||
|
|
7222ec3f02 | ||
|
|
f2d77bb60b | ||
|
|
4b9cba4d7f | ||
|
|
47c9d39150 | ||
|
|
14aba2b507 | ||
|
|
bb2faa27f9 | ||
|
|
e10e9a6976 | ||
|
|
73ba4954c1 | ||
|
|
2c8c077b82 | ||
|
|
f7a3e78870 | ||
|
|
38638a90f1 | ||
|
|
11cff312f5 | ||
|
|
17656d8e7c | ||
|
|
42cb788d35 | ||
|
|
327fe0b660 | ||
|
|
2cad94941b | ||
|
|
d93b5d65b9 | ||
|
|
9bf9dd330a | ||
|
|
4449831ace | ||
|
|
303cf52d6a | ||
|
|
21b1a63687 | ||
|
|
b1dce31542 | ||
|
|
a64ddcb804 | ||
|
|
905c194cdd | ||
|
|
e61c698b03 | ||
|
|
77f70d120a | ||
|
|
3df35be0cf | ||
|
|
04938bb573 | ||
|
|
df57d993f6 | ||
|
|
b74984f3b6 | ||
|
|
0bbdeb0846 | ||
|
|
5441da4630 | ||
|
|
366a55b27d | ||
|
|
34a1b5d9b5 | ||
|
|
2ba3584b7e | ||
|
|
99e3c34060 | ||
|
|
577cee1a38 | ||
|
|
734b790581 | ||
|
|
526bcf2566 | ||
|
|
56207f4dbb | ||
|
|
cd9fd3a10b | ||
|
|
4399fe9d17 | ||
|
|
2a6f524a5b | ||
|
|
caee5e37f0 | ||
|
|
bbd9d68dfd | ||
|
|
16b1b00906 | ||
|
|
7183408535 | ||
|
|
591b4c7c6a | ||
|
|
8b1170a82b | ||
|
|
1f26fbeacc | ||
|
|
cc7ccff48e | ||
|
|
a0809333c1 | ||
|
|
8975f78a5f | ||
|
|
56bc1c1373 | ||
|
|
417233a47f | ||
|
|
59eaf5305a | ||
|
|
275c6be108 | ||
|
|
a0e2b11663 | ||
|
|
de23ce90d8 | ||
|
|
9d5f3029d0 | ||
|
|
33ee220933 | ||
|
|
23d36b58c8 | ||
|
|
39b895f01b | ||
|
|
5f4252cddc | ||
|
|
fc54a1ea39 | ||
|
|
285f338dce | ||
|
|
379e420577 | ||
|
|
0b20b35ffb | ||
|
|
d887cd118c | ||
|
|
54ffcfab79 | ||
|
|
f28ffc680a | ||
|
|
f33f30eabf | ||
|
|
279d44b7f3 | ||
|
|
0fea59007c | ||
|
|
54720b15ca | ||
|
|
39fc7a91d7 | ||
|
|
7f9c322d48 | ||
|
|
b2ff02c773 | ||
|
|
2c242aedfd | ||
|
|
70e4e6f5af | ||
|
|
ebeb068042 | ||
|
|
731e68f164 | ||
|
|
fa0c3aeb78 | ||
|
|
5a36f15667 | ||
|
|
b96fae74b6 | ||
|
|
404743a27f | ||
|
|
58c8fd0f09 | ||
|
|
fb3d3a135d | ||
|
|
96bddd1995 | ||
|
|
6e60ea6984 | ||
|
|
e410f88926 | ||
|
|
1b52feb8b8 | ||
|
|
ec8e915830 | ||
|
|
0bdbbbdf16 | ||
|
|
92e0647f19 | ||
|
|
69498147a2 | ||
|
|
4249c528e9 | ||
|
|
084068852e | ||
|
|
c524a85897 | ||
|
|
d39d46be5a | ||
|
|
b8980b4667 | ||
|
|
e0adec7b2b | ||
|
|
eb8b9d663f | ||
|
|
fa4b0cf9ec | ||
|
|
f72db8dd1d | ||
|
|
c5bf0f228a | ||
|
|
e150b26cab | ||
|
|
da41d07dae | ||
|
|
c885cb35d8 | ||
|
|
7c38217308 | ||
|
|
a88519b814 | ||
|
|
0aa91b170c | ||
|
|
e9bb1c01f7 | ||
|
|
883875e88e | ||
|
|
4cab6b0ad2 | ||
|
|
91a2664830 | ||
|
|
6abbeaf987 | ||
|
|
21efef42f7 | ||
|
|
9d0e8d94ca | ||
|
|
0f57ca698c | ||
|
|
69c572e875 | ||
|
|
4083b60ff3 | ||
|
|
41fbeb7ec9 | ||
|
|
1162357b9b | ||
|
|
a2a526866f | ||
|
|
d3338b699e | ||
|
|
6c60e76b55 | ||
|
|
8a0d31f612 | ||
|
|
6fc7e5ace1 | ||
|
|
8175762e74 | ||
|
|
f48e14af8a | ||
|
|
cd2a61d926 | ||
|
|
f45997afe4 | ||
|
|
b6f56721cb | ||
|
|
f9e7e82772 | ||
|
|
dbcd7b63d8 | ||
|
|
bf807684dd | ||
|
|
f02fcb5e4b | ||
|
|
2c127adf59 | ||
|
|
7f8a357019 | ||
|
|
99daf5b7b7 | ||
|
|
42cff20710 | ||
|
|
04d7880a0c | ||
|
|
e7d26e3f82 | ||
|
|
19308bf686 | ||
|
|
92970489c5 | ||
|
|
d51f5184d7 | ||
|
|
30eb26af7d | ||
|
|
3ea43f8213 | ||
|
|
9833067ba7 | ||
|
|
ad3114c56b | ||
|
|
9da9c269c1 | ||
|
|
0a22bb8469 | ||
|
|
c9fd1b1a17 | ||
|
|
19b40d45c0 | ||
|
|
90e2a1cda0 | ||
|
|
5e47b9f4a7 | ||
|
|
50b6948250 | ||
|
|
3ef118c9fa | ||
|
|
064707db43 | ||
|
|
8f71a1318d | ||
|
|
1b8ab35fdd | ||
|
|
4a1fe2f8ab | ||
|
|
e6e4e14781 | ||
|
|
d139157234 | ||
|
|
94104f4e03 | ||
|
|
8bea978715 | ||
|
|
eefe464fba | ||
|
|
33c0ba808c | ||
|
|
e0cc8ecda2 | ||
|
|
2d423b2358 | ||
|
|
b5b27b141c | ||
|
|
800a879927 | ||
|
|
f6806e42db | ||
|
|
3aae21b810 | ||
|
|
75239d6a64 | ||
|
|
09082955a3 | ||
|
|
6a6f2d51aa | ||
|
|
7b0d3ea8ac | ||
|
|
1c88b6bb26 | ||
|
|
e5e8e5d908 | ||
|
|
92fadd26b7 | ||
|
|
45d783ac43 | ||
|
|
ea9e76e7ae | ||
|
|
28426c0e91 | ||
|
|
3a9f51b600 | ||
|
|
f1b4db368e | ||
|
|
95efac187b | ||
|
|
6770d22438 | ||
|
|
4ce97613c4 | ||
|
|
030eb8eb6e | ||
|
|
c9da8e26e6 | ||
|
|
7ddf9772df | ||
|
|
0382ad1534 | ||
|
|
1b6e1369a0 | ||
|
|
835050c337 | ||
|
|
ca6a42e6eb | ||
|
|
a2e4d893ac | ||
|
|
657520b0b3 | ||
|
|
ea4b87895c | ||
|
|
19db500a19 | ||
|
|
1366cfd478 | ||
|
|
56a6df1f68 | ||
|
|
a1b35a8abf | ||
|
|
8a8a181186 | ||
|
|
463a551f7d | ||
|
|
fc613fb325 | ||
|
|
4517bea664 | ||
|
|
81dcfbe6ae | ||
|
|
fa8e64d04a | ||
|
|
562123b219 | ||
|
|
b217309618 | ||
|
|
357a02c74b | ||
|
|
508eeffa6e | ||
|
|
31555aa473 | ||
|
|
d2f968def7 | ||
|
|
d574bc611b | ||
|
|
a50a3b0123 | ||
|
|
5b6891dd45 | ||
|
|
4886982d43 | ||
|
|
7360f57beb | ||
|
|
491279b7a8 | ||
|
|
05b79f81af | ||
|
|
96ef2f2dd3 | ||
|
|
2542af17b6 | ||
|
|
c86bc649ff | ||
|
|
4b8e48ed88 | ||
|
|
a1addfd416 | ||
|
|
a1a57d8933 | ||
|
|
864970b860 | ||
|
|
a056be0842 | ||
|
|
c672e75739 | ||
|
|
7b5dd3f964 | ||
|
|
a6072f608b | ||
|
|
06462c65a5 | ||
|
|
359f9c0680 | ||
|
|
01db7c4948 | ||
|
|
f67f14a78d | ||
|
|
0a64d653e1 | ||
|
|
456a835285 | ||
|
|
0d8ed92a68 | ||
|
|
9bd093a03c | ||
|
|
361d4698a9 | ||
|
|
b342b15011 | ||
|
|
95638a3a80 | ||
|
|
2204fe3355 | ||
|
|
abcd774c9d | ||
|
|
ee209f8f88 | ||
|
|
b1f2e1c191 | ||
|
|
33f372f6c6 | ||
|
|
8e5c2a8875 | ||
|
|
36f3638ae4 | ||
|
|
d10210011f | ||
|
|
e867840d81 | ||
|
|
fb7e3189a8 | ||
|
|
5733c0143b | ||
|
|
ac4881f231 | ||
|
|
939efd7dab | ||
|
|
a93d96d742 | ||
|
|
f21804c769 | ||
|
|
4bc05a8d46 | ||
|
|
eebe2b0e80 | ||
|
|
250a496a78 | ||
|
|
29163ed053 | ||
|
|
cc05661f9e | ||
|
|
89409c22d1 | ||
|
|
e2f240ebc9 | ||
|
|
8d56f4c33b | ||
|
|
36eccb7122 | ||
|
|
c8827769b4 | ||
|
|
12e6c400b9 | ||
|
|
4c273a7910 | ||
|
|
58da335b17 | ||
|
|
5b2d506462 | ||
|
|
531430d44a | ||
|
|
7450eec7eb | ||
|
|
3a5802435f | ||
|
|
1b6b058097 | ||
|
|
a5797a2350 | ||
|
|
e81a5147c5 | ||
|
|
565c990687 | ||
|
|
0ccdfe0e26 | ||
|
|
f8a558e3a7 | ||
|
|
c5fa195cc6 | ||
|
|
3a821edd45 | ||
|
|
854d194f88 | ||
|
|
fb79daad6a | ||
|
|
b2ae0e8759 | ||
|
|
09f73988b3 | ||
|
|
9e6f289319 | ||
|
|
d2a55ffd31 | ||
|
|
793c2aa423 | ||
|
|
5daa332b6c | ||
|
|
d5511a857c | ||
|
|
7fecd21331 | ||
|
|
88b79e512f | ||
|
|
853bf63777 | ||
|
|
ff16fea54a | ||
|
|
a03e2a69d4 | ||
|
|
56a39df635 | ||
|
|
ac1593ff75 | ||
|
|
4d66b4667c | ||
|
|
fdde538b66 | ||
|
|
de1147219c | ||
|
|
371426a08e | ||
|
|
75eb005ba0 | ||
|
|
601b67145c | ||
|
|
c65afbc057 | ||
|
|
378589a473 | ||
|
|
fa264972a4 | ||
|
|
6b10e01c03 | ||
|
|
5a6d74ab37 | ||
|
|
73f1bb6968 | ||
|
|
d1a7f51859 | ||
|
|
2ae16396a6 | ||
|
|
ef090a5dc5 | ||
|
|
5c0799e82b | ||
|
|
fa2ee01d3f | ||
|
|
d6ba80bd3f | ||
|
|
ee96d5f88c | ||
|
|
e96a917bef | ||
|
|
769b816998 | ||
|
|
ff891c210c | ||
|
|
3ed5e1bf95 | ||
|
|
5bc8581389 | ||
|
|
7346b422d5 | ||
|
|
5c80ac1c74 | ||
|
|
699023992c | ||
|
|
454ce604ad | ||
|
|
1e0f6bfecb | ||
|
|
7f10aa3de2 | ||
|
|
f8764ab85e | ||
|
|
aa8544308e | ||
|
|
31fc70e0f8 | ||
|
|
a16af4560b | ||
|
|
0782ba0dab | ||
|
|
83725667a4 | ||
|
|
f4b3163b04 | ||
|
|
6cd745f429 | ||
|
|
6131f7f6bf | ||
|
|
dd4faa030f | ||
|
|
ab8691f5ac | ||
|
|
77ab073cdb | ||
|
|
87e0011525 | ||
|
|
7af3bb7226 | ||
|
|
5573352ce6 | ||
|
|
e6486e08ab | ||
|
|
48badaa927 | ||
|
|
2f13bf677e | ||
|
|
e63abc1b4b | ||
|
|
88334acdef | ||
|
|
0491aa9f6e | ||
|
|
5be76d7c0f | ||
|
|
3b510389fc | ||
|
|
32d88e9249 | ||
|
|
7b1a1ff4bb | ||
|
|
19beb919d0 | ||
|
|
ba09e8bf4d | ||
|
|
26dd2d0e8e | ||
|
|
69b15d58a2 | ||
|
|
ba68789fb9 | ||
|
|
47a6ceffbc | ||
|
|
b17ca66f73 | ||
|
|
93bc609026 | ||
|
|
3ea51c2e15 | ||
|
|
1d9897ea60 | ||
|
|
b6cb00bc79 | ||
|
|
6dd53c6bfd | ||
|
|
07df5126b3 | ||
|
|
47b38c7d45 | ||
|
|
0e97bec7b2 | ||
|
|
b182585d46 | ||
|
|
e8f92535d3 | ||
|
|
d62c3663e9 | ||
|
|
6b0bfda9fb | ||
|
|
7477330961 | ||
|
|
1f71157063 | ||
|
|
905988c592 | ||
|
|
310951bfa8 | ||
|
|
64c1087856 | ||
|
|
cab6d924aa | ||
|
|
c3a972d39b | ||
|
|
33d44d4d24 | ||
|
|
fd89cf2482 | ||
|
|
112ffb981f | ||
|
|
514426b980 | ||
|
|
a4bf1c8be6 | ||
|
|
9b82e1478f | ||
|
|
d5f145d57e | ||
|
|
bab891ee74 | ||
|
|
a65fd7d0d0 | ||
|
|
46836cc805 | ||
|
|
42559f13d8 | ||
|
|
87351b5920 | ||
|
|
e68dcf189c | ||
|
|
5d62b8389c | ||
|
|
c50aebe76d | ||
|
|
a610f3fde7 | ||
|
|
626391a1d9 | ||
|
|
1bedfe75ea | ||
|
|
86ecc8d4d5 | ||
|
|
9eca84efe1 | ||
|
|
8a6fb6dcba | ||
|
|
e3706fa923 | ||
|
|
8193bc5f60 | ||
|
|
504ecaee5e | ||
|
|
7c9e836572 | ||
|
|
5db0f09b43 | ||
|
|
195bc4ef21 | ||
|
|
6b190bc184 | ||
|
|
39f1cac2c8 | ||
|
|
d193eed519 | ||
|
|
2d80b0e12f | ||
|
|
b50d99be9c | ||
|
|
af41876a5e | ||
|
|
76d351d8be | ||
|
|
b5dd9651c3 | ||
|
|
3e34502014 | ||
|
|
5e57f9cbd6 | ||
|
|
8edb869fdc | ||
|
|
37238c7f57 | ||
|
|
9edee82fa1 | ||
|
|
f7aaea79af | ||
|
|
3c75d2f8b7 | ||
|
|
64c67e19d2 | ||
|
|
d4db8faad8 | ||
|
|
7957b73b4a | ||
|
|
69838c44af | ||
|
|
8e2953aef6 | ||
|
|
8dda616502 | ||
|
|
484512e35b | ||
|
|
c8cd05c07d | ||
|
|
7ffefe6259 | ||
|
|
cd9b7f2f11 | ||
|
|
b372974437 | ||
|
|
7464e0f799 | ||
|
|
25e12f1775 | ||
|
|
6416469f78 | ||
|
|
922ce5ae36 | ||
|
|
9ca8a199c0 | ||
|
|
a570406ac8 | ||
|
|
719edb6b6e | ||
|
|
d075218621 | ||
|
|
7509943938 | ||
|
|
774da9d2f8 | ||
|
|
978fd383e8 | ||
|
|
8551fc23fe | ||
|
|
fb711edeeb | ||
|
|
352a21acaa | ||
|
|
0b9d936317 | ||
|
|
dc500243e9 | ||
|
|
21203b8341 | ||
|
|
5b03447640 | ||
|
|
d34158db2c | ||
|
|
65a17390c7 | ||
|
|
0e96f0917c | ||
|
|
3d62a7e64a | ||
|
|
962805936e | ||
|
|
967aeecf5b | ||
|
|
348b039fa3 | ||
|
|
6e9b1f4fa3 | ||
|
|
f1d447d1aa | ||
|
|
a7c6f47dbe | ||
|
|
0446e89bfe | ||
|
|
e41457913f | ||
|
|
cea1ec7641 | ||
|
|
cc362deb87 | ||
|
|
7ec64e8a3d | ||
|
|
ff2461df9d | ||
|
|
192cd2733c | ||
|
|
ecef95469d | ||
|
|
55d30d5e4b | ||
|
|
2d5502cc2f | ||
|
|
5cda4a1eb4 | ||
|
|
812b914b70 | ||
|
|
9b870ad863 | ||
|
|
0f250ac92d | ||
|
|
552e6b7836 | ||
|
|
28f70b281b | ||
|
|
32d9b573c0 | ||
|
|
fc76a843d5 | ||
|
|
06607aabb2 | ||
|
|
a1edc0e4f1 | ||
|
|
787c5d2189 | ||
|
|
492c577184 | ||
|
|
f5d0e22dc7 | ||
|
|
dc5ba01f1e | ||
|
|
a31f6e68aa | ||
|
|
c95b356a99 | ||
|
|
b5e645cb10 | ||
|
|
627e638251 | ||
|
|
d2e2f337f6 | ||
|
|
e6d4d44f15 | ||
|
|
55f4df19a9 | ||
|
|
9f006ec08a | ||
|
|
d62ff40bed | ||
|
|
da194007fb | ||
|
|
d06ce0c748 | ||
|
|
c14fecb415 | ||
|
|
99f7308a67 | ||
|
|
99ee45ba2d | ||
|
|
615f3f77a6 | ||
|
|
12e4c00c5d | ||
|
|
d954cb468f | ||
|
|
e0fadc7af5 | ||
|
|
c5e9fd99b8 | ||
|
|
7efbbb2153 | ||
|
|
4eb505e24a | ||
|
|
b6b08cccd7 | ||
|
|
70af8541da | ||
|
|
838f8ae352 | ||
|
|
5645515d90 | ||
|
|
d114ffb2c4 | ||
|
|
a9f9534ce6 | ||
|
|
7dee2c67c6 | ||
|
|
e18f8ba6d4 | ||
|
|
4d44753f6e | ||
|
|
f5accbfaed | ||
|
|
6eba99eba1 | ||
|
|
1e18a08998 | ||
|
|
2526380184 | ||
|
|
74dba7cb6c | ||
|
|
63aad8ca84 | ||
|
|
b8bb40de62 | ||
|
|
67dff7fbf2 | ||
|
|
6e226f32fd | ||
|
|
cf819dc0a8 | ||
|
|
4f6af6e4dc | ||
|
|
a6d2a9b7b3 | ||
|
|
cf34164191 | ||
|
|
9a7bb30df4 | ||
|
|
5dc78809b6 | ||
|
|
2b53a6e7d6 | ||
|
|
eb82a35e5b | ||
|
|
51b14435e0 | ||
|
|
59de033523 | ||
|
|
c9b0a278ca | ||
|
|
b487189742 | ||
|
|
d5a60b1580 | ||
|
|
e2665610e9 | ||
|
|
3262ee9938 | ||
|
|
2f153003b3 | ||
|
|
6724e710d8 | ||
|
|
9729e05fe8 | ||
|
|
686c60b83b | ||
|
|
5fe11f3b3a | ||
|
|
30d29c6b34 | ||
|
|
fbfb16e77a | ||
|
|
7aea384f86 | ||
|
|
78bef5c3c6 | ||
|
|
5c8d90a57c | ||
|
|
13dc9ff76d | ||
|
|
eb3645d493 | ||
|
|
0a06e52d65 | ||
|
|
e004c0c2d4 | ||
|
|
6d5f6a0c3c | ||
|
|
024e3c380f | ||
|
|
06859fe9cd | ||
|
|
6392d08584 | ||
|
|
80a5290bc8 | ||
|
|
6b30c88fba | ||
|
|
6adce9bf03 | ||
|
|
3c90ad81a7 | ||
|
|
484529add0 | ||
|
|
600c7906a4 | ||
|
|
f070e90347 | ||
|
|
88127d8b8d | ||
|
|
607ab86188 | ||
|
|
c936f9ccc6 | ||
|
|
d4d8917956 | ||
|
|
89bce95c27 | ||
|
|
f0a38a2b3f |
37
.hgignore
Normal file
37
.hgignore
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
syntax: glob
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
run.py
|
||||||
|
*.pyc
|
||||||
|
*.so
|
||||||
|
*.mo
|
||||||
|
*.pyd
|
||||||
|
.tm_*
|
||||||
|
*.xcodeproj/xcuserdata
|
||||||
|
*.xcodeproj/project.xcworkspace/xcuserdata
|
||||||
|
conf.json
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
install
|
||||||
|
installer_tmp-cache
|
||||||
|
cocoa/autogen
|
||||||
|
cocoa/*/Info.plist
|
||||||
|
cocoa/*/build
|
||||||
|
cocoa/*/*.app
|
||||||
|
cocoa/*/dg_cocoa.plugin
|
||||||
|
cocoa/*/fr.lproj/*.xib
|
||||||
|
cocoa/*/de.lproj/*.xib
|
||||||
|
cocoa/*/zh_CN.lproj/*.xib
|
||||||
|
cocoa/*/cs.lproj/*.xib
|
||||||
|
cocoa/*/it.lproj/*.xib
|
||||||
|
cs.lproj
|
||||||
|
de.lproj
|
||||||
|
fr.lproj
|
||||||
|
it.lproj
|
||||||
|
hy.lproj
|
||||||
|
ru.lproj
|
||||||
|
uk.lproj
|
||||||
|
zh_CN.lproj
|
||||||
|
qt/base/*_rc.py
|
||||||
|
help/*/conf.py
|
||||||
|
help/*/changelog.rst
|
||||||
73
.hgtags
Normal file
73
.hgtags
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
0ef0ca83b49ad009c896f55824189acc932bcf22 se2.8.2
|
||||||
|
0ef0ca83b49ad009c896f55824189acc932bcf22 me5.6.6
|
||||||
|
0ef0ca83b49ad009c896f55824189acc932bcf22 pe1.7.8
|
||||||
|
a8f232f880b6f9ada565d472996a627ebf69b6e9 before-tiger-drop
|
||||||
|
321d15e818cf9a3f1fc037543090bb2fca2cccd7 me5.7.0
|
||||||
|
adc73ccd14b1386cb04dee773c53a2d126800e31 se2.9.0
|
||||||
|
cbcf9c80fee4c908ef2efbf1c143c9e47676c9b2 pe1.8.0
|
||||||
|
61c4101851bdea3cb37dfb76f0d404c78c7c594c se2.9.1
|
||||||
|
0e923897a3389331d4ab3debbc40b8dd616199d9 pe1.8.1
|
||||||
|
2c454eca9ebe93b6cf34916068f828a6a39e3eaf me5.7.1
|
||||||
|
19e40bab20521d4256acf325dba9b32e95e135c5 pe1.8.2
|
||||||
|
7b7c5a66ebee4e4b8125330d24fe9ce1a070ff25 se2.9.2
|
||||||
|
1cef6d39855f85d4be728646bc78b860e6d4e398 pe1.8.3
|
||||||
|
90ed56ee602666db2f267f73eac6f824347039b5 me5.7.2
|
||||||
|
4c3cb1e671a333eabde1151c7c6ffb3609cab025 pe1.8.4
|
||||||
|
0a71306434bca51bea9a5d5ae54fe1bf0e4900d8 pe1.8.5
|
||||||
|
556baf4a410779e9bbf43129de133e4c4b26d679 pe1.8.6
|
||||||
|
9149024283959a50fe9a47a5f175b905d1672c19 se2.10.0
|
||||||
|
388a7e5aef6385e515189f4a15b4c4fed3ae2fcf me5.8.0
|
||||||
|
27501167e3b9262ecb60c967941294f36d77eb25 pe1.9.0
|
||||||
|
cb0a860430bacd712820bce426bcf47a4135efe1 se2.10.1
|
||||||
|
cb0a860430bacd712820bce426bcf47a4135efe1 se2.10.1
|
||||||
|
f71d405e62badcfdc1b037facaac043cece40ee5 se2.10.1
|
||||||
|
3742e83edd9eadf44e1a501859f5e2462b1ef6fd me5.8.1
|
||||||
|
724ff565dd785fb739774588c6ee652cfc0612d5 pe1.9.1
|
||||||
|
634b66415c6529f46ae4f837318027cc9d70c3b5 before-py3k
|
||||||
|
2b67955db2b0580a8b0854dc918b6ab0d1fa3b88 se2.11.0
|
||||||
|
b56fe4dd8c95bca270b078a09e86848df77e2b2d me5.9.0
|
||||||
|
618a7365457d56fdc6920c70843a244762e2ea00 pe1.10.0
|
||||||
|
95b3a4b564c6222b414f2b40182dde2bd6d0e8a4 me5.9.1
|
||||||
|
9735a5218d2b5b3b1e1dfe17f2f874177cf8f61c se2.11.1
|
||||||
|
dbfee3ee2fa5cbb9e7ab36570659c17cd5b8561f se2.12.0
|
||||||
|
d3fe0d0dcda1e0bf1100d02f117503d3bf6baacf me5.10.0
|
||||||
|
b07ac1398703dd358912c1f3d20bd995633db9fe pe1.11.0
|
||||||
|
96b6aee668398d663b04eafc8d5dae05e18500ee before-fairware
|
||||||
|
22239f94589baf2a9fad2123045b8a718dbd68f5 se2.12.2
|
||||||
|
f9cae82a0752191276b24ffb2cc4e4a8afb5d754 me5.10.2
|
||||||
|
154c8cb6f018d446d88fa099490c900906e86386 pe1.11.2
|
||||||
|
ca93352ce35184853ad9fcb881935a43a8b1e249 me5.10.3
|
||||||
|
44f6ff67066c083f79daa18a9d2f1ab909e0a62e me5.10.4
|
||||||
|
3f71a8f5bf8f6d0729748a27af9163e013723294 pe1.11.3
|
||||||
|
0056293b0dade8b8230f68c1fe6f0c2d1e0b74d8 se2.12.3
|
||||||
|
8d12cab3b12b723e3a86d02cf8002731a0f73f95 se3.0.0
|
||||||
|
778876a8a9787658aa6adf6944b53aebcb7faeea se3.0.1
|
||||||
|
f1d40b556c01f32c58f9ef9f9acac5b78e01ba7a pe2.0.0
|
||||||
|
2fd901a516f8cb6b4438491f63f2ebfd52a57c13 me6.0.0
|
||||||
|
ff43c6d9feb388f103b7857eaa6f7809185f78ec before-pluginbuilder
|
||||||
|
d274bcb98f2d02b86470a04cd62e718eff33b74f pe2.1.0
|
||||||
|
77e169f757195c11e9c1c5febeb2db8eb3589510 se3.0.2
|
||||||
|
97893f37d7d0767b5aedf1b4b40de57ee36d426b se3.1.0
|
||||||
|
e44d5127ed605daa7a17a01eee65d0a157de20c0 pe2.2.0
|
||||||
|
ecf9aaa568340e3d03e8854b7556edd5a3285107 pe2.2.1
|
||||||
|
db1f325c907ffa9808a49cb7bc2886b9fca7aee2 se3.1.1
|
||||||
|
e62183e907d6177cf0bac354e31afa9546c199e6 se3.1.2
|
||||||
|
28ba95706dc54ba32b1c0cf4e1e6350515d19ba3 me6.0.2
|
||||||
|
925847384dcef62a5c3518fc9e5ce42feab2b093 pe2.2.2
|
||||||
|
383b14d6e8555ed2c8d5552259bc7ce600dfb1d0 before-leopard-drop
|
||||||
|
a2f7b7302e178f08725a6404ddc28464409510b1 se3.2.0
|
||||||
|
5a5134a4fa9bb7ca80ae3e32010030f5bd7ba434 me6.1.0
|
||||||
|
0fd77be57ff716d5c93232e829dc02acec036d7c se3.2.1
|
||||||
|
3dd08060135b0b9cef70b6f5a81f191ea339c8d5 me6.1.1
|
||||||
|
4e6cbef6bcdfcc0e56ff9690fbfe1cac1f4b1b09 pe2.3.0
|
||||||
|
9ea9af1b886cd1adc4f42fd2276cc2b38376eab0 se3.3.0
|
||||||
|
6e3379be6821bb36d7f0877a17dd6c07aa037b0a se3.3.1
|
||||||
|
015ba7e2c10d09afb944f387c2a9c97f7eff7571 me6.2.0
|
||||||
|
8178bda48324461a17118c98634241952c074f29 pe2.4.0
|
||||||
|
2a96f2fb3ddb6f1e0ae87951586733fc3e4a28a0 se3.3.2
|
||||||
|
6a08c1205dfe5e537e5c2dc99d05e05d1d3928f6 me6.2.1
|
||||||
|
a619f313712e2923160b8f90d8250ee0e184c7b9 pe2.4.1
|
||||||
|
fad463ae749b7189dce92f1e42a57ac4ee03987d se3.3.3
|
||||||
|
236cf9b690a144392e7e86e7c9749fc834a8b271 me6.3.0
|
||||||
|
90318f1303858d9d01065d92d78d98b888b38ea0 se3.4.0
|
||||||
|
93ed33410df2d2f21229a77ae49c83ece2c50a55 pe2.5.0
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2009 Hardcoded Software Inc. (http://www.hardcoded.net)
|
Copyright 2012 Hardcoded Software Inc. (http://www.hardcoded.net)
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
@@ -6,6 +6,5 @@ Redistribution and use in source and binary forms, with or without modification,
|
|||||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
* Neither the name of Hardcoded Software Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
* Neither the name of Hardcoded Software Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
* If the source code has been published less than two years ago, any redistribution, in whole or in part, must retain full licensing functionality, without any attempt to change, obscure or in other ways circumvent its intent.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
119
README
Normal file
119
README
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
Contents
|
||||||
|
========
|
||||||
|
|
||||||
|
This package contains the source for dupeGuru. To learn how to build it, refer to the "Build dupeGuru" section. Below is the description of the various subfolders:
|
||||||
|
|
||||||
|
- core: Contains the core logic code for dupeGuru. It's Python code written in TDD style.
|
||||||
|
- core_*: Edition-specific-cross-toolkit code written in Python.
|
||||||
|
- cocoa: UI code for the Cocoa toolkit. It's Objective-C code.
|
||||||
|
- qt: UI code for the Qt toolkit. It's written in Python and uses PyQt.
|
||||||
|
- images: Images used by the different UI codebases.
|
||||||
|
- debian_*: Skeleton files required to create a .deb package
|
||||||
|
- help: Help document, written for Sphinx.
|
||||||
|
|
||||||
|
There are also other sub-folder that comes from external repositories (automatically checked out
|
||||||
|
with as mercurial subrepos):
|
||||||
|
|
||||||
|
- hscommon: A collection of helpers used across HS applications.
|
||||||
|
- cocoalib: A collection of helpers used across Cocoa UI codebases of HS applications.
|
||||||
|
- qtlib: A collection of helpers used across Qt UI codebases of HS applications.
|
||||||
|
|
||||||
|
dupeGuru Dependencies
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Before being able to build dupeGuru, a few dependencies have to be installed:
|
||||||
|
|
||||||
|
General dependencies
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
- Python 3.2 (http://www.python.org)
|
||||||
|
- Send2Trash3k (http://hg.hardcoded.net/send2trash)
|
||||||
|
- hsaudiotag3k 1.1.0 (for ME) (http://hg.hardcoded.net/hsaudiotag)
|
||||||
|
- jobprogress 1.0.3 (http://hg.hardcoded.net/jobprogress)
|
||||||
|
- Sphinx 1.1 (http://sphinx.pocoo.org/)
|
||||||
|
- polib 0.7.0 (http://bitbucket.org/izi/polib)
|
||||||
|
- pytest 2.0.0, to run unit tests. (http://pytest.org/)
|
||||||
|
|
||||||
|
OS X prerequisites
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- XCode 4.1
|
||||||
|
- Sparkle (http://sparkle.andymatuschak.org/)
|
||||||
|
- objp 1.1.0 (http://bitbucket.org/hsoft/objp)
|
||||||
|
- pluginbuilder 1.1.0 (http://bitbucket.org/hsoft/pluginbuilder)
|
||||||
|
- appscript 1.0.0 for ME and PE (http://appscript.sourceforge.net/)
|
||||||
|
|
||||||
|
Windows prerequisites
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
- Visual Studio 2008 (Express is enough) is needed to build C extensions. (http://www.microsoft.com/Express/)
|
||||||
|
- PyQt 4.7.5 (http://www.riverbankcomputing.co.uk/news)
|
||||||
|
- cx_Freeze, if you want to build a exe. You don't need it if you just want to run dupeGuru. (http://cx-freeze.sourceforge.net/)
|
||||||
|
- Advanced Installer, if you want to build the installer file. (http://www.advancedinstaller.com/)
|
||||||
|
|
||||||
|
Linux prerequisites
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
- PyQt 4.7.5 (http://www.riverbankcomputing.co.uk/news)
|
||||||
|
|
||||||
|
The easy way!
|
||||||
|
-------------
|
||||||
|
|
||||||
|
There's an easy way to install the majority of the prerequisites above, and it's `pip <http://www.pip-installer.org/>`_ which has recently started to support Python 3. So install it and then run::
|
||||||
|
|
||||||
|
pip install -r requirements-[osx|win].txt
|
||||||
|
|
||||||
|
([osx|win] depends, of course, on your platform. On other platforms, just use requirements.txt).
|
||||||
|
You might have to compile PyObjC manually (see gotchas below). Sparkle and
|
||||||
|
Advanced Installer, having nothing to do with Python, are also manual installs.
|
||||||
|
|
||||||
|
PyQt isn't in the requirements file either (there's no package uploaded on PyPI) and you also have
|
||||||
|
to install it manually.
|
||||||
|
|
||||||
|
Prerequisite gotchas
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Correctly installing the prerequisites is tricky. Make sure you have at least the version number
|
||||||
|
required for each prerequisite.
|
||||||
|
|
||||||
|
If you didn't use mercurial to download this source, you probably have an incomplete source folder!
|
||||||
|
External projects (hscommon, qtlib, cocoalib) need to be at the root of the dupeGuru project folder.
|
||||||
|
You'll have to download those separately. Or use mercurial, it's much easier.
|
||||||
|
|
||||||
|
As far as I can tell, you don't *have* to compile/install everything manually and you can normally
|
||||||
|
use `easy_install` to install python dependencies. However, be aware that compiling/installing
|
||||||
|
manually from the repositories of each project is what I personally do, so if you hit a snag
|
||||||
|
somewhere, you might want to try the manual way.
|
||||||
|
|
||||||
|
PyObjC's website is badly outdated. Also, as far as I can tell, the package installable with
|
||||||
|
`easy_install` has good chances of not working. Your best bet is to download the latest tagged
|
||||||
|
version from the repository and compile it from source.
|
||||||
|
|
||||||
|
Another one on OS X: I wouldn't use macports/fink/whatever. Whenever I tried using those, I always
|
||||||
|
ended up with problems.
|
||||||
|
|
||||||
|
On OSX Lion, for dupeGuru PE, make sure that you installed the latest version of macholib because there was a
|
||||||
|
10.7 related bug that was fixed recently. Right now, the fix hasn't even been released yet so you
|
||||||
|
have to install directly from the repo ( http://bitbucket.org/ronaldoussoren/macholib ). The fix
|
||||||
|
in question is at http://bitbucket.org/ronaldoussoren/macholib/changeset/4ab0de0f5b60
|
||||||
|
|
||||||
|
Whenever you have a problem, always double-check that you're running the correct python version.
|
||||||
|
You'll probably have to tweak your $PATH.
|
||||||
|
|
||||||
|
Building dupeGuru
|
||||||
|
=================
|
||||||
|
|
||||||
|
First, make sure you meet the dependencies listed in the section above. Then you need to configure your build with:
|
||||||
|
|
||||||
|
python configure.py
|
||||||
|
|
||||||
|
If you want, you can specify a UI to use with the `--ui` option. So, if you want to build dupeGuru with Qt on OS X, then you have to type `python configure.py --ui=qt`. You can also use the `--dev` flag to indicate a dev build (it will build `dg_cocoa.plugin` in alias mode and use the "dev" config in XCode).
|
||||||
|
|
||||||
|
Then, just build the thing and then run it with:
|
||||||
|
|
||||||
|
python build.py
|
||||||
|
python run.py
|
||||||
|
|
||||||
|
If you want to create ready-to-upload package, run:
|
||||||
|
|
||||||
|
python package.py
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "RecentDirectories.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface AppDelegateBase : NSObject
|
|
||||||
{
|
|
||||||
IBOutlet PyDupeGuruBase *py;
|
|
||||||
IBOutlet RecentDirectories *recentDirectories;
|
|
||||||
IBOutlet NSMenuItem *unlockMenuItem;
|
|
||||||
|
|
||||||
NSString *_appName;
|
|
||||||
}
|
|
||||||
- (IBAction)unlockApp:(id)sender;
|
|
||||||
|
|
||||||
- (PyDupeGuruBase *)py;
|
|
||||||
- (RecentDirectories *)recentDirectories;
|
|
||||||
@end
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "ProgressController.h"
|
|
||||||
#import "RegistrationInterface.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation AppDelegateBase
|
|
||||||
- (id)init
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
_appName = @"";
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)unlockApp:(id)sender
|
|
||||||
{
|
|
||||||
if ([[self py] isRegistered])
|
|
||||||
return;
|
|
||||||
RegistrationInterface *ri = [[RegistrationInterface alloc] initWithApp:[self py] name:_appName limitDescription:LIMIT_DESC];
|
|
||||||
if ([ri enterCode] == NSOKButton)
|
|
||||||
{
|
|
||||||
NSString *menuTitle = [NSString stringWithFormat:@"Thanks for buying %@",_appName];
|
|
||||||
[unlockMenuItem setTitle:menuTitle];
|
|
||||||
}
|
|
||||||
[ri release];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (PyDupeGuruBase *)py { return py; }
|
|
||||||
- (RecentDirectories *)recentDirectories { return recentDirectories; }
|
|
||||||
@end
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
#define DuplicateSelectionChangedNotification @"DuplicateSelectionChangedNotification"
|
|
||||||
/* ResultsChangedNotification happens on major changes, which requires a complete reload of the data*/
|
|
||||||
#define ResultsChangedNotification @"ResultsChangedNotification"
|
|
||||||
/* ResultsChangedNotification happens on minor changes, which requires buffer flush*/
|
|
||||||
#define ResultsUpdatedNotification @"ResultsUpdatedNotification"
|
|
||||||
#define ResultsMarkingChangedNotification @"ResultsMarkingChangedNotification"
|
|
||||||
#define RegistrationRequired @"RegistrationRequired"
|
|
||||||
#define JobStarted @"JobStarted"
|
|
||||||
#define JobInProgress @"JobInProgress"
|
|
||||||
|
|
||||||
#define jobLoad @"job_load"
|
|
||||||
#define jobScan @"job_scan"
|
|
||||||
#define jobCopy @"job_copy"
|
|
||||||
#define jobMove @"job_move"
|
|
||||||
#define jobDelete @"job_delete"
|
|
||||||
|
|
||||||
#define DEMO_MAX_ACTION_COUNT 10
|
|
||||||
#define LIMIT_DESC @"In the demo version, only 10 duplicates per session can be sent to Trash, moved or copied."
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "PyApp.h"
|
|
||||||
#import "Table.h"
|
|
||||||
|
|
||||||
|
|
||||||
@interface DetailsPanelBase : NSWindowController
|
|
||||||
{
|
|
||||||
IBOutlet TableView *detailsTable;
|
|
||||||
}
|
|
||||||
- (id)initWithPy:(PyApp *)aPy;
|
|
||||||
|
|
||||||
- (void)refresh;
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)duplicateSelectionChanged:(NSNotification *)aNotification;
|
|
||||||
@end
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation DetailsPanelBase
|
|
||||||
- (id)initWithPy:(PyApp *)aPy
|
|
||||||
{
|
|
||||||
self = [super initWithWindowNibName:@"Details"];
|
|
||||||
[self window]; //So the detailsTable is initialized.
|
|
||||||
[detailsTable setPy:aPy];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(duplicateSelectionChanged:) name:DuplicateSelectionChangedNotification object:nil];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)refresh
|
|
||||||
{
|
|
||||||
[detailsTable reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)duplicateSelectionChanged:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
if ([[self window] isVisible])
|
|
||||||
[self refresh];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "RecentDirectories.h"
|
|
||||||
#import "Outline.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface DirectoryPanelBase : NSWindowController
|
|
||||||
{
|
|
||||||
IBOutlet NSPopUpButton *addButtonPopUp;
|
|
||||||
IBOutlet OutlineView *directories;
|
|
||||||
IBOutlet NSButton *removeButton;
|
|
||||||
|
|
||||||
PyDupeGuruBase *_py;
|
|
||||||
RecentDirectories *_recentDirectories;
|
|
||||||
}
|
|
||||||
- (id)initWithParentApp:(id)aParentApp;
|
|
||||||
|
|
||||||
- (IBAction)askForDirectory:(id)sender;
|
|
||||||
- (IBAction)changeDirectoryState:(id)sender;
|
|
||||||
- (IBAction)popupAddDirectoryMenu:(id)sender;
|
|
||||||
- (IBAction)removeSelectedDirectory:(id)sender;
|
|
||||||
- (IBAction)toggleVisible:(id)sender;
|
|
||||||
|
|
||||||
- (void)addDirectory:(NSString *)directory;
|
|
||||||
- (void)refreshRemoveButtonText;
|
|
||||||
@end
|
|
||||||
@@ -1,187 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
#import "Dialogs.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
|
|
||||||
@implementation DirectoryPanelBase
|
|
||||||
- (id)initWithParentApp:(id)aParentApp
|
|
||||||
{
|
|
||||||
self = [super initWithWindowNibName:@"Directories"];
|
|
||||||
[self window];
|
|
||||||
AppDelegateBase *app = aParentApp;
|
|
||||||
_py = [app py];
|
|
||||||
_recentDirectories = [app recentDirectories];
|
|
||||||
[directories setPy:_py];
|
|
||||||
NSPopUpButtonCell *cell = [[directories tableColumnWithIdentifier:@"1"] dataCell];
|
|
||||||
[cell addItemWithTitle:@"Normal"];
|
|
||||||
[cell addItemWithTitle:@"Reference"];
|
|
||||||
[cell addItemWithTitle:@"Excluded"];
|
|
||||||
for (int i=0;i<[[cell itemArray] count];i++)
|
|
||||||
{
|
|
||||||
NSMenuItem *mi = [[cell itemArray] objectAtIndex:i];
|
|
||||||
[mi setTarget:self];
|
|
||||||
[mi setAction:@selector(changeDirectoryState:)];
|
|
||||||
[mi setTag:i];
|
|
||||||
}
|
|
||||||
[self refreshRemoveButtonText];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:) name:NSOutlineViewSelectionDidChangeNotification object:directories];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
|
|
||||||
- (IBAction)askForDirectory:(id)sender
|
|
||||||
{
|
|
||||||
NSOpenPanel *op = [NSOpenPanel openPanel];
|
|
||||||
[op setCanChooseFiles:YES];
|
|
||||||
[op setCanChooseDirectories:YES];
|
|
||||||
[op setAllowsMultipleSelection:NO];
|
|
||||||
[op setTitle:@"Select a directory to add to the scanning list"];
|
|
||||||
[op setDelegate:self];
|
|
||||||
if ([op runModalForTypes:nil] == NSOKButton)
|
|
||||||
{
|
|
||||||
NSString *directory = [[op filenames] objectAtIndex:0];
|
|
||||||
[self addDirectory:directory];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)changeDirectoryState:(id)sender
|
|
||||||
{
|
|
||||||
OVNode *node = [directories itemAtRow:[directories clickedRow]];
|
|
||||||
[_py setDirectory:p2a([node indexPath]) state:i2n([sender tag])];
|
|
||||||
[node resetAllBuffers];
|
|
||||||
[directories reloadItem:node reloadChildren:YES];
|
|
||||||
[directories display];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)popupAddDirectoryMenu:(id)sender
|
|
||||||
{
|
|
||||||
if ([[_recentDirectories directories] count] == 0)
|
|
||||||
{
|
|
||||||
[self askForDirectory:sender];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NSMenu *m = [addButtonPopUp menu];
|
|
||||||
while ([m numberOfItems] > 0)
|
|
||||||
[m removeItemAtIndex:0];
|
|
||||||
NSMenuItem *mi = [m addItemWithTitle:@"Add New Directory..." action:@selector(askForDirectory:) keyEquivalent:@""];
|
|
||||||
[mi setTarget:self];
|
|
||||||
[m addItem:[NSMenuItem separatorItem]];
|
|
||||||
[_recentDirectories fillMenu:m];
|
|
||||||
[addButtonPopUp selectItem:nil];
|
|
||||||
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)removeSelectedDirectory:(id)sender
|
|
||||||
{
|
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
|
||||||
if ([directories selectedRow] < 0)
|
|
||||||
return;
|
|
||||||
OVNode *node = [directories itemAtRow:[directories selectedRow]];
|
|
||||||
if ([node level] == 1)
|
|
||||||
{
|
|
||||||
[_py removeDirectory:i2n([node index])];
|
|
||||||
[directories reloadData];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int state = n2i([[node buffer] objectAtIndex:1]);
|
|
||||||
int newState = state == 2 ? 0 : 2; // If excluded, put it back
|
|
||||||
[_py setDirectory:p2a([node indexPath]) state:i2n(newState)];
|
|
||||||
[node resetAllBuffers];
|
|
||||||
[directories display];
|
|
||||||
}
|
|
||||||
[self refreshRemoveButtonText];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleVisible:(id)sender
|
|
||||||
{
|
|
||||||
if ([[self window] isVisible])
|
|
||||||
[[self window] close];
|
|
||||||
else
|
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Public */
|
|
||||||
|
|
||||||
- (void)addDirectory:(NSString *)directory
|
|
||||||
{
|
|
||||||
int r = [[_py addDirectory:directory] intValue];
|
|
||||||
if (r)
|
|
||||||
{
|
|
||||||
NSString *m;
|
|
||||||
switch (r)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
{
|
|
||||||
m = @"This directory already is in the list.";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
{
|
|
||||||
m = @"This directory does not exist.";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[Dialogs showMessage:m];
|
|
||||||
}
|
|
||||||
[directories reloadData];
|
|
||||||
[_recentDirectories addDirectory:directory];
|
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)refreshRemoveButtonText
|
|
||||||
{
|
|
||||||
if ([directories selectedRow] < 0)
|
|
||||||
{
|
|
||||||
[removeButton setEnabled:NO];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[removeButton setEnabled:YES];
|
|
||||||
OVNode *node = [directories itemAtRow:[directories selectedRow]];
|
|
||||||
int state = n2i([[node buffer] objectAtIndex:1]);
|
|
||||||
NSString *buttonText = state == 2 ? @"Put Back" : @"Remove";
|
|
||||||
[removeButton setTitle:buttonText];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delegate */
|
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
|
||||||
{
|
|
||||||
OVNode *node = item;
|
|
||||||
int state = n2i([[node buffer] objectAtIndex:1]);
|
|
||||||
if ([cell isKindOfClass:[NSTextFieldCell class]])
|
|
||||||
{
|
|
||||||
NSTextFieldCell *textCell = cell;
|
|
||||||
if (state == 1)
|
|
||||||
[textCell setTextColor:[NSColor blueColor]];
|
|
||||||
else if (state == 2)
|
|
||||||
[textCell setTextColor:[NSColor redColor]];
|
|
||||||
else
|
|
||||||
[textCell setTextColor:[NSColor blackColor]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
|
|
||||||
{
|
|
||||||
BOOL isdir;
|
|
||||||
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isdir];
|
|
||||||
return isdir;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
|
|
||||||
- (void)directorySelectionChanged:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[self refreshRemoveButtonText];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "PyApp.h"
|
|
||||||
|
|
||||||
@interface PyDupeGuruBase : PyApp
|
|
||||||
//Actions
|
|
||||||
- (NSNumber *)addDirectory:(NSString *)name;
|
|
||||||
- (void)removeDirectory:(NSNumber *)index;
|
|
||||||
- (void)setDirectory:(NSArray *)indexPath state:(NSNumber *)state;
|
|
||||||
- (void)loadResults;
|
|
||||||
- (void)saveResults;
|
|
||||||
- (void)loadIgnoreList;
|
|
||||||
- (void)saveIgnoreList;
|
|
||||||
- (void)clearIgnoreList;
|
|
||||||
- (void)purgeIgnoreList;
|
|
||||||
- (NSString *)exportToXHTMLwithColumns:(NSArray *)aColIds;
|
|
||||||
|
|
||||||
- (NSNumber *)doScan;
|
|
||||||
|
|
||||||
- (NSArray *)selectedPowerMarkerNodePaths;
|
|
||||||
- (void)selectPowerMarkerNodePaths:(NSArray *)aIndexPaths;
|
|
||||||
- (NSArray *)selectedResultNodePaths;
|
|
||||||
- (void)selectResultNodePaths:(NSArray *)aIndexPaths;
|
|
||||||
|
|
||||||
- (void)toggleSelectedMark;
|
|
||||||
- (void)markAll;
|
|
||||||
- (void)markInvert;
|
|
||||||
- (void)markNone;
|
|
||||||
|
|
||||||
- (void)addSelectedToIgnoreList;
|
|
||||||
- (void)refreshDetailsWithSelected;
|
|
||||||
- (void)removeSelected;
|
|
||||||
- (void)openSelected;
|
|
||||||
- (NSNumber *)renameSelected:(NSString *)aNewName;
|
|
||||||
- (void)revealSelected;
|
|
||||||
- (void)makeSelectedReference;
|
|
||||||
- (void)applyFilter:(NSString *)filter;
|
|
||||||
|
|
||||||
- (void)sortGroupsBy:(NSNumber *)aIdentifier ascending:(NSNumber *)aAscending;
|
|
||||||
- (void)sortDupesBy:(NSNumber *)aIdentifier ascending:(NSNumber *)aAscending;
|
|
||||||
|
|
||||||
- (void)copyOrMove:(NSNumber *)aCopy markedTo:(NSString *)destination recreatePath:(NSNumber *)aRecreateType;
|
|
||||||
- (void)deleteMarked;
|
|
||||||
- (void)removeMarked;
|
|
||||||
|
|
||||||
//Data
|
|
||||||
- (NSNumber *)getIgnoreListCount;
|
|
||||||
- (NSNumber *)getMarkCount;
|
|
||||||
- (NSString *)getStatLine;
|
|
||||||
- (NSNumber *)getOperationalErrorCount;
|
|
||||||
|
|
||||||
//Scanning options
|
|
||||||
- (void)setMinMatchPercentage:(NSNumber *)percentage;
|
|
||||||
- (void)setMixFileKind:(NSNumber *)mix_file_kind;
|
|
||||||
- (void)setDisplayDeltaValues:(NSNumber *)display_delta_values;
|
|
||||||
- (void)setEscapeFilterRegexp:(NSNumber *)escape_filter_regexp;
|
|
||||||
- (void)setRemoveEmptyFolders:(NSNumber *)remove_empty_folders;
|
|
||||||
- (void)setSizeThreshold:(int)size_threshold;
|
|
||||||
@end
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "Outline.h"
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface MatchesView : OutlineView
|
|
||||||
- (void)keyDown:(NSEvent *)theEvent;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface ResultWindowBase : NSWindowController
|
|
||||||
{
|
|
||||||
@protected
|
|
||||||
IBOutlet PyDupeGuruBase *py;
|
|
||||||
IBOutlet id app;
|
|
||||||
IBOutlet NSView *actionMenuView;
|
|
||||||
IBOutlet NSSegmentedControl *deltaSwitch;
|
|
||||||
IBOutlet NSView *deltaSwitchView;
|
|
||||||
IBOutlet NSView *filterFieldView;
|
|
||||||
IBOutlet MatchesView *matches;
|
|
||||||
IBOutlet NSSegmentedControl *pmSwitch;
|
|
||||||
IBOutlet NSView *pmSwitchView;
|
|
||||||
IBOutlet NSTextField *stats;
|
|
||||||
|
|
||||||
BOOL _powerMode;
|
|
||||||
BOOL _displayDelta;
|
|
||||||
}
|
|
||||||
/* Override */
|
|
||||||
- (NSString *)logoImageName;
|
|
||||||
|
|
||||||
/* Helpers */
|
|
||||||
- (NSArray *)getColumnsOrder;
|
|
||||||
- (NSDictionary *)getColumnsWidth;
|
|
||||||
- (NSArray *)getSelected:(BOOL)aDupesOnly;
|
|
||||||
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly;
|
|
||||||
- (void)updatePySelection;
|
|
||||||
- (void)performPySelection:(NSArray *)aIndexPaths;
|
|
||||||
- (void)refreshStats;
|
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
- (IBAction)changeDelta:(id)sender;
|
|
||||||
- (IBAction)changePowerMarker:(id)sender;
|
|
||||||
- (IBAction)copyMarked:(id)sender;
|
|
||||||
- (IBAction)deleteMarked:(id)sender;
|
|
||||||
- (IBAction)expandAll:(id)sender;
|
|
||||||
- (IBAction)exportToXHTML:(id)sender;
|
|
||||||
- (IBAction)moveMarked:(id)sender;
|
|
||||||
- (IBAction)switchSelected:(id)sender;
|
|
||||||
- (IBAction)togglePowerMarker:(id)sender;
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)jobCompleted:(NSNotification *)aNotification;
|
|
||||||
@end
|
|
||||||
@@ -1,468 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "Dialogs.h"
|
|
||||||
#import "ProgressController.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "RegistrationInterface.h"
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
#define tbbDirectories @"tbbDirectories"
|
|
||||||
#define tbbDetails @"tbbDetail"
|
|
||||||
#define tbbPreferences @"tbbPreferences"
|
|
||||||
#define tbbPowerMarker @"tbbPowerMarker"
|
|
||||||
#define tbbScan @"tbbScan"
|
|
||||||
#define tbbAction @"tbbAction"
|
|
||||||
#define tbbDelta @"tbbDelta"
|
|
||||||
#define tbbFilter @"tbbFilter"
|
|
||||||
|
|
||||||
@implementation MatchesView
|
|
||||||
- (void)keyDown:(NSEvent *)theEvent
|
|
||||||
{
|
|
||||||
unichar key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];
|
|
||||||
// get flags and strip the lower 16 (device dependant) bits
|
|
||||||
unsigned int flags = ( [theEvent modifierFlags] & 0x00FF );
|
|
||||||
if (((key == NSDeleteFunctionKey) || (key == NSDeleteCharacter)) && (flags == 0))
|
|
||||||
[self sendAction:@selector(removeSelected:) to:[self delegate]];
|
|
||||||
else
|
|
||||||
if ((key == 0x20) && (flags == 0)) // Space
|
|
||||||
[self sendAction:@selector(markSelected:) to:[self delegate]];
|
|
||||||
else
|
|
||||||
[super keyDown:theEvent];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
|
|
||||||
{
|
|
||||||
if (![[tableColumn identifier] isEqual:@"0"])
|
|
||||||
return; //We only want to cover renames.
|
|
||||||
OVNode *node = item;
|
|
||||||
NSString *oldName = [[node buffer] objectAtIndex:0];
|
|
||||||
NSString *newName = object;
|
|
||||||
if (![newName isEqual:oldName])
|
|
||||||
{
|
|
||||||
BOOL renamed = n2b([(PyDupeGuruBase *)py renameSelected:newName]);
|
|
||||||
if (renamed)
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
else
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"The name '%@' already exists.",newName]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation ResultWindowBase
|
|
||||||
- (void)awakeFromNib
|
|
||||||
{
|
|
||||||
[self window];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationRequired:) name:RegistrationRequired object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobInProgress:) name:JobInProgress object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsChanged:) name:ResultsChangedNotification object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsUpdated:) name:ResultsUpdatedNotification object:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Virtual */
|
|
||||||
- (NSString *)logoImageName
|
|
||||||
{
|
|
||||||
return @"dg_logo32";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Helpers */
|
|
||||||
//Returns an array of identifiers, in order.
|
|
||||||
- (NSArray *)getColumnsOrder
|
|
||||||
{
|
|
||||||
NSTableColumn *col;
|
|
||||||
NSString *colId;
|
|
||||||
NSMutableArray *result = [NSMutableArray array];
|
|
||||||
NSEnumerator *e = [[matches tableColumns] objectEnumerator];
|
|
||||||
while (col = [e nextObject])
|
|
||||||
{
|
|
||||||
colId = [col identifier];
|
|
||||||
[result addObject:colId];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary *)getColumnsWidth
|
|
||||||
{
|
|
||||||
NSMutableDictionary *result = [NSMutableDictionary dictionary];
|
|
||||||
NSTableColumn *col;
|
|
||||||
NSString *colId;
|
|
||||||
NSNumber *width;
|
|
||||||
NSEnumerator *e = [[matches tableColumns] objectEnumerator];
|
|
||||||
while (col = [e nextObject])
|
|
||||||
{
|
|
||||||
colId = [col identifier];
|
|
||||||
width = [NSNumber numberWithFloat:[col width]];
|
|
||||||
[result setObject:width forKey:colId];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray *)getSelected:(BOOL)aDupesOnly
|
|
||||||
{
|
|
||||||
if (_powerMode)
|
|
||||||
aDupesOnly = NO;
|
|
||||||
NSIndexSet *indexes = [matches selectedRowIndexes];
|
|
||||||
NSMutableArray *nodeList = [NSMutableArray array];
|
|
||||||
OVNode *node;
|
|
||||||
int i = [indexes firstIndex];
|
|
||||||
while (i != NSNotFound)
|
|
||||||
{
|
|
||||||
node = [matches itemAtRow:i];
|
|
||||||
if (!aDupesOnly || ([node level] > 1))
|
|
||||||
[nodeList addObject:node];
|
|
||||||
i = [indexes indexGreaterThanIndex:i];
|
|
||||||
}
|
|
||||||
return nodeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly
|
|
||||||
{
|
|
||||||
NSMutableArray *r = [NSMutableArray array];
|
|
||||||
NSArray *selected = [self getSelected:aDupesOnly];
|
|
||||||
NSEnumerator *e = [selected objectEnumerator];
|
|
||||||
OVNode *node;
|
|
||||||
while (node = [e nextObject])
|
|
||||||
[r addObject:p2a([node indexPath])];
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updatePySelection
|
|
||||||
{
|
|
||||||
NSArray *selection;
|
|
||||||
if (_powerMode)
|
|
||||||
selection = [py selectedPowerMarkerNodePaths];
|
|
||||||
else
|
|
||||||
selection = [py selectedResultNodePaths];
|
|
||||||
[matches selectNodePaths:selection];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)performPySelection:(NSArray *)aIndexPaths
|
|
||||||
{
|
|
||||||
if (_powerMode)
|
|
||||||
[py selectPowerMarkerNodePaths:aIndexPaths];
|
|
||||||
else
|
|
||||||
[py selectResultNodePaths:aIndexPaths];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)refreshStats
|
|
||||||
{
|
|
||||||
[stats setStringValue:[py getStatLine]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
- (IBAction)changeDelta:(id)sender
|
|
||||||
{
|
|
||||||
_displayDelta = [deltaSwitch selectedSegment] == 1;
|
|
||||||
[py setDisplayDeltaValues:b2n(_displayDelta)];
|
|
||||||
[matches reloadData];
|
|
||||||
[self expandAll:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)changePowerMarker:(id)sender
|
|
||||||
{
|
|
||||||
_powerMode = [pmSwitch selectedSegment] == 1;
|
|
||||||
if (_powerMode)
|
|
||||||
[matches setTag:2];
|
|
||||||
else
|
|
||||||
[matches setTag:0];
|
|
||||||
[self expandAll:nil];
|
|
||||||
[self outlineView:matches didClickTableColumn:nil];
|
|
||||||
[self updatePySelection];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)copyMarked:(id)sender
|
|
||||||
{
|
|
||||||
int mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count)
|
|
||||||
return;
|
|
||||||
NSOpenPanel *op = [NSOpenPanel openPanel];
|
|
||||||
[op setCanChooseFiles:NO];
|
|
||||||
[op setCanChooseDirectories:YES];
|
|
||||||
[op setCanCreateDirectories:YES];
|
|
||||||
[op setAllowsMultipleSelection:NO];
|
|
||||||
[op setTitle:@"Select a directory to copy marked files to"];
|
|
||||||
if ([op runModalForTypes:nil] == NSOKButton)
|
|
||||||
{
|
|
||||||
NSString *directory = [[op filenames] objectAtIndex:0];
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py copyOrMove:b2n(YES) markedTo:directory recreatePath:[ud objectForKey:@"recreatePathType"]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)deleteMarked:(id)sender
|
|
||||||
{
|
|
||||||
int mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count)
|
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to send %d files to Trash. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py setRemoveEmptyFolders:[ud objectForKey:@"removeEmptyFolders"]];
|
|
||||||
[py deleteMarked];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)expandAll:(id)sender
|
|
||||||
{
|
|
||||||
for (int i=0;i < [matches numberOfRows];i++)
|
|
||||||
[matches expandItem:[matches itemAtRow:i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)exportToXHTML:(id)sender
|
|
||||||
{
|
|
||||||
NSString *exported = [py exportToXHTMLwithColumns:[self getColumnsOrder]];
|
|
||||||
[[NSWorkspace sharedWorkspace] openFile:exported];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)moveMarked:(id)sender
|
|
||||||
{
|
|
||||||
int mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count)
|
|
||||||
return;
|
|
||||||
NSOpenPanel *op = [NSOpenPanel openPanel];
|
|
||||||
[op setCanChooseFiles:NO];
|
|
||||||
[op setCanChooseDirectories:YES];
|
|
||||||
[op setCanCreateDirectories:YES];
|
|
||||||
[op setAllowsMultipleSelection:NO];
|
|
||||||
[op setTitle:@"Select a directory to move marked files to"];
|
|
||||||
if ([op runModalForTypes:nil] == NSOKButton)
|
|
||||||
{
|
|
||||||
NSString *directory = [[op filenames] objectAtIndex:0];
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py setRemoveEmptyFolders:[ud objectForKey:@"removeEmptyFolders"]];
|
|
||||||
[py copyOrMove:b2n(NO) markedTo:directory recreatePath:[ud objectForKey:@"recreatePathType"]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)switchSelected:(id)sender
|
|
||||||
{
|
|
||||||
// It might look like a complicated way to get the length of the current dupe list on the py side
|
|
||||||
// but after a lot of fussing around, believe it or not, it actually is.
|
|
||||||
int matchesTag = _powerMode ? 2 : 0;
|
|
||||||
int startLen = [[py getOutlineView:matchesTag childCountsForPath:[NSArray array]] count];
|
|
||||||
[self performPySelection:[self getSelectedPaths:YES]];
|
|
||||||
[py makeSelectedReference];
|
|
||||||
// In some cases (when in a filtered view in Power Marker mode, it's possible that the demoted
|
|
||||||
// ref is not a part of the filter, making the table smaller. In those cases, we want to do a
|
|
||||||
// complete reload of the table to avoid a crash.
|
|
||||||
if ([[py getOutlineView:matchesTag childCountsForPath:[NSArray array]] count] == startLen)
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsUpdatedNotification object:self];
|
|
||||||
else
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)togglePowerMarker:(id)sender
|
|
||||||
{
|
|
||||||
if ([pmSwitch selectedSegment] == 1)
|
|
||||||
[pmSwitch setSelectedSegment:0];
|
|
||||||
else
|
|
||||||
[pmSwitch setSelectedSegment:1];
|
|
||||||
[self changePowerMarker:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delegate */
|
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView didClickTableColumn:(NSTableColumn *)tableColumn
|
|
||||||
{
|
|
||||||
if ([[outlineView sortDescriptors] count] < 1)
|
|
||||||
return;
|
|
||||||
NSSortDescriptor *sd = [[outlineView sortDescriptors] objectAtIndex:0];
|
|
||||||
if (_powerMode)
|
|
||||||
[py sortDupesBy:i2n([[sd key] intValue]) ascending:b2n([sd ascending])];
|
|
||||||
else
|
|
||||||
[py sortGroupsBy:i2n([[sd key] intValue]) ascending:b2n([sd ascending])];
|
|
||||||
[matches reloadData];
|
|
||||||
[self expandAll:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)windowWillClose:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[NSApp hide:NSApp];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)jobCompleted:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
int r = n2i([py getOperationalErrorCount]);
|
|
||||||
id lastAction = [[ProgressController mainProgressController] jobId];
|
|
||||||
if ([lastAction isEqualTo:jobCopy])
|
|
||||||
{
|
|
||||||
if (r > 0)
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be copied.",r]];
|
|
||||||
else
|
|
||||||
[Dialogs showMessage:@"All marked files were copied sucessfully."];
|
|
||||||
}
|
|
||||||
if ([lastAction isEqualTo:jobMove])
|
|
||||||
{
|
|
||||||
if (r > 0)
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be moved. They were kept in the results, and still are marked.",r]];
|
|
||||||
else
|
|
||||||
[Dialogs showMessage:@"All marked files were moved sucessfully."];
|
|
||||||
}
|
|
||||||
if ([lastAction isEqualTo:jobDelete])
|
|
||||||
{
|
|
||||||
if (r > 0)
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be sent to Trash. They were kept in the results, and still are marked.",r]];
|
|
||||||
else
|
|
||||||
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
|
|
||||||
}
|
|
||||||
// Re-activate toolbar items right after the progress bar stops showing instead of waiting until
|
|
||||||
// a mouse-over is performed
|
|
||||||
[[[self window] toolbar] validateVisibleItems];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)jobInProgress:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[Dialogs showMessage:@"A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)jobStarted:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSDictionary *ui = [aNotification userInfo];
|
|
||||||
NSString *desc = [ui valueForKey:@"desc"];
|
|
||||||
[[ProgressController mainProgressController] setJobDesc:desc];
|
|
||||||
NSString *jobid = [ui valueForKey:@"jobid"];
|
|
||||||
// NSLog(jobid);
|
|
||||||
[[ProgressController mainProgressController] setJobId:jobid];
|
|
||||||
[[ProgressController mainProgressController] showSheetForParent:[self window]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)registrationRequired:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSString *msg = @"This is a demo version, which only allows you 10 delete/copy/move actions per session. You cannot continue.";
|
|
||||||
[Dialogs showMessage:msg];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)resultsChanged:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[matches reloadData];
|
|
||||||
[self expandAll:nil];
|
|
||||||
[self outlineViewSelectionDidChange:nil];
|
|
||||||
[self refreshStats];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)resultsUpdated:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[matches invalidateBuffers];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toolbar */
|
|
||||||
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)flag
|
|
||||||
{
|
|
||||||
NSToolbarItem *tbi = [[[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier] autorelease];
|
|
||||||
if ([itemIdentifier isEqualTo:tbbDirectories])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Directories"];
|
|
||||||
[tbi setToolTip: @"Show/Hide the directories panel."];
|
|
||||||
[tbi setImage: [NSImage imageNamed: @"folder32"]];
|
|
||||||
[tbi setTarget: app];
|
|
||||||
[tbi setAction: @selector(toggleDirectories:)];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbDetails])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Details"];
|
|
||||||
[tbi setToolTip: @"Show/Hide the details panel."];
|
|
||||||
[tbi setImage: [NSImage imageNamed: @"details32"]];
|
|
||||||
[tbi setTarget: self];
|
|
||||||
[tbi setAction: @selector(toggleDetailsPanel:)];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbPreferences])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Preferences"];
|
|
||||||
[tbi setToolTip: @"Show the preferences panel."];
|
|
||||||
[tbi setImage: [NSImage imageNamed: @"preferences32"]];
|
|
||||||
[tbi setTarget: self];
|
|
||||||
[tbi setAction: @selector(showPreferencesPanel:)];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbPowerMarker])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Power Marker"];
|
|
||||||
[tbi setToolTip: @"When enabled, only the duplicates are shown, not the references."];
|
|
||||||
[tbi setView:pmSwitchView];
|
|
||||||
[tbi setMinSize:[pmSwitchView frame].size];
|
|
||||||
[tbi setMaxSize:[pmSwitchView frame].size];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbScan])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Start Scanning"];
|
|
||||||
[tbi setToolTip: @"Start scanning for duplicates in the selected directories."];
|
|
||||||
[tbi setImage: [NSImage imageNamed:[self logoImageName]]];
|
|
||||||
[tbi setTarget: self];
|
|
||||||
[tbi setAction: @selector(startDuplicateScan:)];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbAction])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Action"];
|
|
||||||
[tbi setView:actionMenuView];
|
|
||||||
[tbi setMinSize:[actionMenuView frame].size];
|
|
||||||
[tbi setMaxSize:[actionMenuView frame].size];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbDelta])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Delta Values"];
|
|
||||||
[tbi setToolTip: @"When enabled, this option makes dupeGuru display, where applicable, delta values instead of absolute values."];
|
|
||||||
[tbi setView:deltaSwitchView];
|
|
||||||
[tbi setMinSize:[deltaSwitchView frame].size];
|
|
||||||
[tbi setMaxSize:[deltaSwitchView frame].size];
|
|
||||||
}
|
|
||||||
else if ([itemIdentifier isEqualTo:tbbFilter])
|
|
||||||
{
|
|
||||||
[tbi setLabel: @"Filter"];
|
|
||||||
[tbi setToolTip: @"Filters the results using regular expression."];
|
|
||||||
[tbi setView:filterFieldView];
|
|
||||||
[tbi setMinSize:[filterFieldView frame].size];
|
|
||||||
[tbi setMaxSize:NSMakeSize(1000, [filterFieldView frame].size.height)];
|
|
||||||
}
|
|
||||||
[tbi setPaletteLabel: [tbi label]];
|
|
||||||
return tbi;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
|
|
||||||
{
|
|
||||||
return [NSArray arrayWithObjects:
|
|
||||||
tbbDirectories,
|
|
||||||
tbbDetails,
|
|
||||||
tbbPreferences,
|
|
||||||
tbbPowerMarker,
|
|
||||||
tbbScan,
|
|
||||||
tbbAction,
|
|
||||||
tbbDelta,
|
|
||||||
tbbFilter,
|
|
||||||
NSToolbarSeparatorItemIdentifier,
|
|
||||||
NSToolbarSpaceItemIdentifier,
|
|
||||||
NSToolbarFlexibleSpaceItemIdentifier,
|
|
||||||
nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
|
|
||||||
{
|
|
||||||
return [NSArray arrayWithObjects:
|
|
||||||
tbbScan,
|
|
||||||
tbbAction,
|
|
||||||
tbbDirectories,
|
|
||||||
tbbDetails,
|
|
||||||
tbbPowerMarker,
|
|
||||||
tbbDelta,
|
|
||||||
tbbFilter,
|
|
||||||
nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
|
|
||||||
{
|
|
||||||
return ![[ProgressController mainProgressController] isShown];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)validateMenuItem:(NSMenuItem *)item
|
|
||||||
{
|
|
||||||
return ![[ProgressController mainProgressController] isShown];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
251
base/py/app.py
251
base/py/app.py
@@ -1,251 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2006/11/11
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import os
|
|
||||||
import os.path as op
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from hsutil import io, files
|
|
||||||
from hsutil.path import Path
|
|
||||||
from hsutil.reg import RegistrableApplication, RegistrationRequired
|
|
||||||
from hsutil.misc import flatten, first
|
|
||||||
from hsutil.str import escape
|
|
||||||
|
|
||||||
from . import directories, results, scanner, export, fs
|
|
||||||
|
|
||||||
JOB_SCAN = 'job_scan'
|
|
||||||
JOB_LOAD = 'job_load'
|
|
||||||
JOB_MOVE = 'job_move'
|
|
||||||
JOB_COPY = 'job_copy'
|
|
||||||
JOB_DELETE = 'job_delete'
|
|
||||||
|
|
||||||
class NoScannableFileError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class AllFilesAreRefError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class DupeGuru(RegistrableApplication):
|
|
||||||
def __init__(self, data_module, appdata, appid):
|
|
||||||
RegistrableApplication.__init__(self, appid)
|
|
||||||
self.appdata = appdata
|
|
||||||
if not op.exists(self.appdata):
|
|
||||||
os.makedirs(self.appdata)
|
|
||||||
self.data = data_module
|
|
||||||
self.directories = directories.Directories()
|
|
||||||
self.results = results.Results(data_module)
|
|
||||||
self.scanner = scanner.Scanner()
|
|
||||||
self.action_count = 0
|
|
||||||
self.last_op_error_count = 0
|
|
||||||
self.options = {
|
|
||||||
'escape_filter_regexp': True,
|
|
||||||
'clean_empty_dirs': False,
|
|
||||||
}
|
|
||||||
|
|
||||||
def _demo_check(self):
|
|
||||||
if self.registered:
|
|
||||||
return
|
|
||||||
count = self.results.mark_count
|
|
||||||
if count + self.action_count > 10:
|
|
||||||
raise RegistrationRequired()
|
|
||||||
else:
|
|
||||||
self.action_count += count
|
|
||||||
|
|
||||||
def _do_delete(self, j):
|
|
||||||
def op(dupe):
|
|
||||||
j.add_progress()
|
|
||||||
return self._do_delete_dupe(dupe)
|
|
||||||
|
|
||||||
j.start_job(self.results.mark_count)
|
|
||||||
self.last_op_error_count = self.results.perform_on_marked(op, True)
|
|
||||||
|
|
||||||
def _do_delete_dupe(self, dupe):
|
|
||||||
if not io.exists(dupe.path):
|
|
||||||
dupe.parent = None
|
|
||||||
return True
|
|
||||||
self._recycle_dupe(dupe)
|
|
||||||
self.clean_empty_dirs(dupe.path[:-1])
|
|
||||||
if not io.exists(dupe.path):
|
|
||||||
dupe.parent = None
|
|
||||||
return True
|
|
||||||
logging.warning("Could not send {0} to trash.".format(unicode(dupe.path)))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _do_load(self, j):
|
|
||||||
self.directories.load_from_file(op.join(self.appdata, 'last_directories.xml'))
|
|
||||||
j = j.start_subjob([1, 9])
|
|
||||||
self.results.load_from_xml(op.join(self.appdata, 'last_results.xml'), self._get_file, j)
|
|
||||||
files = flatten(g[:] for g in self.results.groups)
|
|
||||||
for file in j.iter_with_progress(files, 'Reading metadata %d/%d'):
|
|
||||||
file._read_all_info(attrnames=self.data.METADATA_TO_READ)
|
|
||||||
|
|
||||||
def _get_display_info(self, dupe, group, delta=False):
|
|
||||||
if (dupe is None) or (group is None):
|
|
||||||
return ['---'] * len(self.data.COLUMNS)
|
|
||||||
try:
|
|
||||||
return self.data.GetDisplayInfo(dupe, group, delta)
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning("Exception on GetDisplayInfo for %s: %s", unicode(dupe.path), unicode(e))
|
|
||||||
return ['---'] * len(self.data.COLUMNS)
|
|
||||||
|
|
||||||
def _get_file(self, str_path):
|
|
||||||
path = Path(str_path)
|
|
||||||
return fs.get_file(path, self.directories.fileclasses)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _recycle_dupe(dupe):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _start_job(self, jobid, func):
|
|
||||||
# func(j)
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def add_directory(self, d):
|
|
||||||
try:
|
|
||||||
self.directories.add_path(Path(d))
|
|
||||||
return 0
|
|
||||||
except directories.AlreadyThereError:
|
|
||||||
return 1
|
|
||||||
except directories.InvalidPathError:
|
|
||||||
return 2
|
|
||||||
|
|
||||||
def add_to_ignore_list(self, dupe):
|
|
||||||
g = self.results.get_group_of_duplicate(dupe)
|
|
||||||
for other in g:
|
|
||||||
if other is not dupe:
|
|
||||||
self.scanner.ignore_list.Ignore(unicode(other.path), unicode(dupe.path))
|
|
||||||
|
|
||||||
def apply_filter(self, filter):
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
if self.options['escape_filter_regexp']:
|
|
||||||
filter = escape(filter, '()[]\\.|+?^')
|
|
||||||
filter = escape(filter, '*', '.')
|
|
||||||
self.results.apply_filter(filter)
|
|
||||||
|
|
||||||
def clean_empty_dirs(self, path):
|
|
||||||
if self.options['clean_empty_dirs']:
|
|
||||||
while files.delete_if_empty(path, ['.DS_Store']):
|
|
||||||
path = path[:-1]
|
|
||||||
|
|
||||||
def copy_or_move(self, dupe, copy, destination, dest_type):
|
|
||||||
"""
|
|
||||||
copy: True = Copy False = Move
|
|
||||||
destination: string.
|
|
||||||
dest_type: 0 = right in destination.
|
|
||||||
1 = relative re-creation.
|
|
||||||
2 = absolute re-creation.
|
|
||||||
"""
|
|
||||||
source_path = dupe.path
|
|
||||||
location_path = first(p for p in self.directories if dupe.path in p)
|
|
||||||
dest_path = Path(destination)
|
|
||||||
if dest_type == 2:
|
|
||||||
dest_path = dest_path + source_path[1:-1] #Remove drive letter and filename
|
|
||||||
elif dest_type == 1:
|
|
||||||
dest_path = dest_path + source_path[location_path:-1]
|
|
||||||
try:
|
|
||||||
if not io.exists(dest_path):
|
|
||||||
io.makedirs(dest_path)
|
|
||||||
if copy:
|
|
||||||
files.copy(source_path, dest_path)
|
|
||||||
else:
|
|
||||||
files.move(source_path, dest_path)
|
|
||||||
self.clean_empty_dirs(source_path[:-1])
|
|
||||||
except EnvironmentError as e:
|
|
||||||
operation = 'Copy' if copy else 'Move'
|
|
||||||
logging.warning('%s operation failed on %s. Error: %s' % (operation, unicode(dupe.path), unicode(e)))
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def copy_or_move_marked(self, copy, destination, recreate_path):
|
|
||||||
def do(j):
|
|
||||||
def op(dupe):
|
|
||||||
j.add_progress()
|
|
||||||
return self.copy_or_move(dupe, copy, destination, recreate_path)
|
|
||||||
|
|
||||||
j.start_job(self.results.mark_count)
|
|
||||||
self.last_op_error_count = self.results.perform_on_marked(op, not copy)
|
|
||||||
|
|
||||||
self._demo_check()
|
|
||||||
jobid = JOB_COPY if copy else JOB_MOVE
|
|
||||||
self._start_job(jobid, do)
|
|
||||||
|
|
||||||
def delete_marked(self):
|
|
||||||
self._demo_check()
|
|
||||||
self._start_job(JOB_DELETE, self._do_delete)
|
|
||||||
|
|
||||||
def export_to_xhtml(self, column_ids):
|
|
||||||
column_ids = [colid for colid in column_ids if colid.isdigit()]
|
|
||||||
column_ids = map(int, column_ids)
|
|
||||||
column_ids.sort()
|
|
||||||
colnames = [col['display'] for i, col in enumerate(self.data.COLUMNS) if i in column_ids]
|
|
||||||
rows = []
|
|
||||||
for group in self.results.groups:
|
|
||||||
for dupe in group:
|
|
||||||
data = self._get_display_info(dupe, group)
|
|
||||||
row = [data[colid] for colid in column_ids]
|
|
||||||
row.insert(0, dupe is not group.ref)
|
|
||||||
rows.append(row)
|
|
||||||
return export.export_to_xhtml(colnames, rows)
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
self._start_job(JOB_LOAD, self._do_load)
|
|
||||||
self.load_ignore_list()
|
|
||||||
|
|
||||||
def load_ignore_list(self):
|
|
||||||
p = op.join(self.appdata, 'ignore_list.xml')
|
|
||||||
self.scanner.ignore_list.load_from_xml(p)
|
|
||||||
|
|
||||||
def make_reference(self, duplicates):
|
|
||||||
changed_groups = set()
|
|
||||||
for dupe in duplicates:
|
|
||||||
g = self.results.get_group_of_duplicate(dupe)
|
|
||||||
if g not in changed_groups:
|
|
||||||
self.results.make_ref(dupe)
|
|
||||||
changed_groups.add(g)
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
try:
|
|
||||||
self.directories.save_to_file(op.join(self.appdata, 'last_directories.xml'))
|
|
||||||
self.results.save_to_xml(op.join(self.appdata, 'last_results.xml'))
|
|
||||||
except LookupError:
|
|
||||||
# This is that weird issue from #39 that sometimes happens when auto-updating with
|
|
||||||
# Sparkle. Just ignore it.
|
|
||||||
pass
|
|
||||||
|
|
||||||
def save_ignore_list(self):
|
|
||||||
p = op.join(self.appdata, 'ignore_list.xml')
|
|
||||||
self.scanner.ignore_list.save_to_xml(p)
|
|
||||||
|
|
||||||
def start_scanning(self):
|
|
||||||
def do(j):
|
|
||||||
j.set_progress(0, 'Collecting files to scan')
|
|
||||||
files = list(self.directories.get_files())
|
|
||||||
logging.info('Scanning %d files' % len(files))
|
|
||||||
self.results.groups = self.scanner.GetDupeGroups(files, j)
|
|
||||||
|
|
||||||
files = self.directories.get_files()
|
|
||||||
first_file = first(files)
|
|
||||||
if first_file is None:
|
|
||||||
raise NoScannableFileError()
|
|
||||||
if first_file.is_ref and all(f.is_ref for f in files):
|
|
||||||
raise AllFilesAreRefError()
|
|
||||||
self.results.groups = []
|
|
||||||
self._start_job(JOB_SCAN, do)
|
|
||||||
|
|
||||||
#--- Properties
|
|
||||||
@property
|
|
||||||
def stat_line(self):
|
|
||||||
result = self.results.stat_line
|
|
||||||
if self.scanner.discarded_file_count:
|
|
||||||
result = '%s (%d discarded)' % (result, self.scanner.discarded_file_count)
|
|
||||||
return result
|
|
||||||
|
|
||||||
@@ -1,310 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2006/11/11
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from Foundation import *
|
|
||||||
from AppKit import *
|
|
||||||
import logging
|
|
||||||
import os.path as op
|
|
||||||
|
|
||||||
from hsutil import io, cocoa, job
|
|
||||||
from hsutil.cocoa import install_exception_hook
|
|
||||||
from hsutil.misc import stripnone
|
|
||||||
from hsutil.reg import RegistrationRequired
|
|
||||||
|
|
||||||
from . import app, fs
|
|
||||||
|
|
||||||
JOBID2TITLE = {
|
|
||||||
app.JOB_SCAN: "Scanning for duplicates",
|
|
||||||
app.JOB_LOAD: "Loading",
|
|
||||||
app.JOB_MOVE: "Moving",
|
|
||||||
app.JOB_COPY: "Copying",
|
|
||||||
app.JOB_DELETE: "Sending to Trash",
|
|
||||||
}
|
|
||||||
|
|
||||||
def demo_method(method):
|
|
||||||
def wrapper(self, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
return method(self, *args, **kwargs)
|
|
||||||
except RegistrationRequired:
|
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName_object_('RegistrationRequired', self)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
class DupeGuru(app.DupeGuru):
|
|
||||||
def __init__(self, data_module, appdata_subdir, appid):
|
|
||||||
LOGGING_LEVEL = logging.DEBUG if NSUserDefaults.standardUserDefaults().boolForKey_('debug') else logging.WARNING
|
|
||||||
logging.basicConfig(level=LOGGING_LEVEL, format='%(levelname)s %(message)s')
|
|
||||||
logging.debug('started in debug mode')
|
|
||||||
install_exception_hook()
|
|
||||||
appsupport = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, True)[0]
|
|
||||||
appdata = op.join(appsupport, appdata_subdir)
|
|
||||||
app.DupeGuru.__init__(self, data_module, appdata, appid)
|
|
||||||
self.progress = cocoa.ThreadedJobPerformer()
|
|
||||||
self.display_delta_values = False
|
|
||||||
self.selected_dupes = []
|
|
||||||
self.RefreshDetailsTable(None,None)
|
|
||||||
|
|
||||||
#--- Override
|
|
||||||
@staticmethod
|
|
||||||
def _recycle_dupe(dupe):
|
|
||||||
if not io.exists(dupe.path):
|
|
||||||
dupe.parent = None
|
|
||||||
return True
|
|
||||||
directory = unicode(dupe.parent.path)
|
|
||||||
filename = dupe.name
|
|
||||||
result, tag = NSWorkspace.sharedWorkspace().performFileOperation_source_destination_files_tag_(
|
|
||||||
NSWorkspaceRecycleOperation, directory, '', [filename])
|
|
||||||
if not io.exists(dupe.path):
|
|
||||||
dupe.parent = None
|
|
||||||
return True
|
|
||||||
logging.warning('Could not send %s to trash. tag: %d' % (unicode(dupe.path), tag))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _start_job(self, jobid, func):
|
|
||||||
try:
|
|
||||||
j = self.progress.create_job()
|
|
||||||
self.progress.run_threaded(func, args=(j, ))
|
|
||||||
except job.JobInProgressError:
|
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName_object_('JobInProgress', self)
|
|
||||||
else:
|
|
||||||
ud = {'desc': JOBID2TITLE[jobid], 'jobid':jobid}
|
|
||||||
NSNotificationCenter.defaultCenter().postNotificationName_object_userInfo_('JobStarted', self, ud)
|
|
||||||
|
|
||||||
#---Helpers
|
|
||||||
def GetObjects(self,node_path):
|
|
||||||
#returns a tuple g,d
|
|
||||||
try:
|
|
||||||
g = self.results.groups[node_path[0]]
|
|
||||||
if len(node_path) == 2:
|
|
||||||
return (g,self.results.groups[node_path[0]].dupes[node_path[1]])
|
|
||||||
else:
|
|
||||||
return (g,None)
|
|
||||||
except IndexError:
|
|
||||||
return (None,None)
|
|
||||||
|
|
||||||
def get_folder_path(self, node_path, curr_path=None):
|
|
||||||
if not node_path:
|
|
||||||
return curr_path
|
|
||||||
current_index = node_path[0]
|
|
||||||
if curr_path is None:
|
|
||||||
curr_path = self.directories[current_index]
|
|
||||||
else:
|
|
||||||
curr_path = self.directories.get_subfolders(curr_path)[current_index]
|
|
||||||
return self.get_folder_path(node_path[1:], curr_path)
|
|
||||||
|
|
||||||
def RefreshDetailsTable(self,dupe,group):
|
|
||||||
l1 = self._get_display_info(dupe, group, False)
|
|
||||||
# we don't want the two sides of the table to display the stats for the same file
|
|
||||||
ref = group.ref if group is not None and group.ref is not dupe else None
|
|
||||||
l2 = self._get_display_info(ref, group, False)
|
|
||||||
names = [c['display'] for c in self.data.COLUMNS]
|
|
||||||
self.details_table = zip(names,l1,l2)
|
|
||||||
|
|
||||||
#---Public
|
|
||||||
def AddSelectedToIgnoreList(self):
|
|
||||||
for dupe in self.selected_dupes:
|
|
||||||
self.add_to_ignore_list(dupe)
|
|
||||||
|
|
||||||
copy_or_move_marked = demo_method(app.DupeGuru.copy_or_move_marked)
|
|
||||||
delete_marked = demo_method(app.DupeGuru.delete_marked)
|
|
||||||
|
|
||||||
def MakeSelectedReference(self):
|
|
||||||
self.make_reference(self.selected_dupes)
|
|
||||||
|
|
||||||
def OpenSelected(self):
|
|
||||||
if self.selected_dupes:
|
|
||||||
path = unicode(self.selected_dupes[0].path)
|
|
||||||
NSWorkspace.sharedWorkspace().openFile_(path)
|
|
||||||
|
|
||||||
def PurgeIgnoreList(self):
|
|
||||||
self.scanner.ignore_list.Filter(lambda f,s:op.exists(f) and op.exists(s))
|
|
||||||
|
|
||||||
def RefreshDetailsWithSelected(self):
|
|
||||||
if self.selected_dupes:
|
|
||||||
self.RefreshDetailsTable(
|
|
||||||
self.selected_dupes[0],
|
|
||||||
self.results.get_group_of_duplicate(self.selected_dupes[0])
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.RefreshDetailsTable(None,None)
|
|
||||||
|
|
||||||
def RemoveDirectory(self,index):
|
|
||||||
try:
|
|
||||||
del self.directories[index]
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def RemoveSelected(self):
|
|
||||||
self.results.remove_duplicates(self.selected_dupes)
|
|
||||||
|
|
||||||
def RenameSelected(self, newname):
|
|
||||||
try:
|
|
||||||
d = self.selected_dupes[0]
|
|
||||||
d.rename(newname)
|
|
||||||
return True
|
|
||||||
except (IndexError, fs.FSError) as e:
|
|
||||||
logging.warning("dupeGuru Warning: %s" % unicode(e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def RevealSelected(self):
|
|
||||||
if self.selected_dupes:
|
|
||||||
path = unicode(self.selected_dupes[0].path)
|
|
||||||
NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_(path,'')
|
|
||||||
|
|
||||||
def start_scanning(self):
|
|
||||||
self.RefreshDetailsTable(None, None)
|
|
||||||
try:
|
|
||||||
app.DupeGuru.start_scanning(self)
|
|
||||||
return 0
|
|
||||||
except app.NoScannableFileError:
|
|
||||||
return 3
|
|
||||||
except app.AllFilesAreRefError:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def selected_result_node_paths(self):
|
|
||||||
def get_path(dupe):
|
|
||||||
try:
|
|
||||||
group = self.results.get_group_of_duplicate(dupe)
|
|
||||||
groupindex = self.results.groups.index(group)
|
|
||||||
if dupe is group.ref:
|
|
||||||
return [groupindex]
|
|
||||||
dupeindex = group.dupes.index(dupe)
|
|
||||||
return [groupindex, dupeindex]
|
|
||||||
except ValueError: # dupe not in there
|
|
||||||
return None
|
|
||||||
|
|
||||||
dupes = self.selected_dupes
|
|
||||||
return stripnone(get_path(dupe) for dupe in dupes)
|
|
||||||
|
|
||||||
def selected_powermarker_node_paths(self):
|
|
||||||
def get_path(dupe):
|
|
||||||
try:
|
|
||||||
dupeindex = self.results.dupes.index(dupe)
|
|
||||||
return [dupeindex]
|
|
||||||
except ValueError: # dupe not in there
|
|
||||||
return None
|
|
||||||
|
|
||||||
dupes = self.selected_dupes
|
|
||||||
return stripnone(get_path(dupe) for dupe in dupes)
|
|
||||||
|
|
||||||
def SelectResultNodePaths(self,node_paths):
|
|
||||||
def extract_dupe(t):
|
|
||||||
g,d = t
|
|
||||||
if d is not None:
|
|
||||||
return d
|
|
||||||
else:
|
|
||||||
if g is not None:
|
|
||||||
return g.ref
|
|
||||||
|
|
||||||
selected = [extract_dupe(self.GetObjects(p)) for p in node_paths]
|
|
||||||
self.selected_dupes = [dupe for dupe in selected if dupe is not None]
|
|
||||||
|
|
||||||
def SelectPowerMarkerNodePaths(self,node_paths):
|
|
||||||
rows = [p[0] for p in node_paths]
|
|
||||||
self.selected_dupes = [
|
|
||||||
self.results.dupes[row] for row in rows if row in xrange(len(self.results.dupes))
|
|
||||||
]
|
|
||||||
|
|
||||||
def SetDirectoryState(self, node_path, state):
|
|
||||||
p = self.get_folder_path(node_path)
|
|
||||||
self.directories.set_state(p, state)
|
|
||||||
|
|
||||||
def sort_dupes(self,key,asc):
|
|
||||||
self.results.sort_dupes(key,asc,self.display_delta_values)
|
|
||||||
|
|
||||||
def sort_groups(self,key,asc):
|
|
||||||
self.results.sort_groups(key,asc)
|
|
||||||
|
|
||||||
def ToggleSelectedMarkState(self):
|
|
||||||
for dupe in self.selected_dupes:
|
|
||||||
self.results.mark_toggle(dupe)
|
|
||||||
|
|
||||||
#---Data
|
|
||||||
def GetOutlineViewMaxLevel(self, tag):
|
|
||||||
if tag == 0:
|
|
||||||
return 2
|
|
||||||
elif tag == 1:
|
|
||||||
return 0
|
|
||||||
elif tag == 2:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def GetOutlineViewChildCounts(self, tag, node_path):
|
|
||||||
if self.progress._job_running:
|
|
||||||
return []
|
|
||||||
if tag == 0: #Normal results
|
|
||||||
assert not node_path # no other value is possible
|
|
||||||
return [len(g.dupes) for g in self.results.groups]
|
|
||||||
elif tag == 1: #Directories
|
|
||||||
try:
|
|
||||||
if node_path:
|
|
||||||
path = self.get_folder_path(node_path)
|
|
||||||
subfolders = self.directories.get_subfolders(path)
|
|
||||||
else:
|
|
||||||
subfolders = self.directories
|
|
||||||
return [len(self.directories.get_subfolders(path)) for path in subfolders]
|
|
||||||
except IndexError: # node_path out of range
|
|
||||||
return []
|
|
||||||
else: #Power Marker
|
|
||||||
assert not node_path # no other value is possible
|
|
||||||
return [0 for d in self.results.dupes]
|
|
||||||
|
|
||||||
def GetOutlineViewValues(self, tag, node_path):
|
|
||||||
if self.progress._job_running:
|
|
||||||
return
|
|
||||||
if not node_path:
|
|
||||||
return
|
|
||||||
if tag in (0,2): #Normal results / Power Marker
|
|
||||||
if tag == 0:
|
|
||||||
g, d = self.GetObjects(node_path)
|
|
||||||
if d is None:
|
|
||||||
d = g.ref
|
|
||||||
else:
|
|
||||||
d = self.results.dupes[node_path[0]]
|
|
||||||
g = self.results.get_group_of_duplicate(d)
|
|
||||||
result = self._get_display_info(d, g, self.display_delta_values)
|
|
||||||
return result
|
|
||||||
elif tag == 1: #Directories
|
|
||||||
try:
|
|
||||||
path = self.get_folder_path(node_path)
|
|
||||||
name = unicode(path) if len(node_path) == 1 else path[-1]
|
|
||||||
return [name, self.directories.get_state(path)]
|
|
||||||
except IndexError: # node_path out of range
|
|
||||||
return []
|
|
||||||
|
|
||||||
def GetOutlineViewMarked(self, tag, node_path):
|
|
||||||
# 0=unmarked 1=marked 2=unmarkable
|
|
||||||
if self.progress._job_running:
|
|
||||||
return
|
|
||||||
if not node_path:
|
|
||||||
return 2
|
|
||||||
if tag == 1: #Directories
|
|
||||||
return 2
|
|
||||||
if tag == 0: #Normal results
|
|
||||||
g, d = self.GetObjects(node_path)
|
|
||||||
else: #Power Marker
|
|
||||||
d = self.results.dupes[node_path[0]]
|
|
||||||
if (d is None) or (not self.results.is_markable(d)):
|
|
||||||
return 2
|
|
||||||
elif self.results.is_marked(d):
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def GetTableViewCount(self, tag):
|
|
||||||
if self.progress._job_running:
|
|
||||||
return 0
|
|
||||||
return len(self.details_table)
|
|
||||||
|
|
||||||
def GetTableViewMarkedIndexes(self,tag):
|
|
||||||
return []
|
|
||||||
|
|
||||||
def GetTableViewValues(self,tag,row):
|
|
||||||
return self.details_table[row]
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2006/03/15
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from hsutil.str import format_time, FT_DECIMAL, format_size
|
|
||||||
|
|
||||||
import time
|
|
||||||
|
|
||||||
def format_path(p):
|
|
||||||
return unicode(p[:-1])
|
|
||||||
|
|
||||||
def format_timestamp(t, delta):
|
|
||||||
if delta:
|
|
||||||
return format_time(t, FT_DECIMAL)
|
|
||||||
else:
|
|
||||||
if t > 0:
|
|
||||||
return time.strftime('%Y/%m/%d %H:%M:%S', time.localtime(t))
|
|
||||||
else:
|
|
||||||
return '---'
|
|
||||||
|
|
||||||
def format_words(w):
|
|
||||||
def do_format(w):
|
|
||||||
if isinstance(w, list):
|
|
||||||
return '(%s)' % ', '.join(do_format(item) for item in w)
|
|
||||||
else:
|
|
||||||
return w.replace('\n', ' ')
|
|
||||||
|
|
||||||
return ', '.join(do_format(item) for item in w)
|
|
||||||
|
|
||||||
def format_perc(p):
|
|
||||||
return "%0.0f" % p
|
|
||||||
|
|
||||||
def format_dupe_count(c):
|
|
||||||
return str(c) if c else '---'
|
|
||||||
|
|
||||||
def cmp_value(value):
|
|
||||||
return value.lower() if isinstance(value, basestring) else value
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2006/03/03
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
|
|
||||||
from hsutil import job
|
|
||||||
from hsutil.misc import dedupe
|
|
||||||
from hsutil.str import get_file_ext, rem_file_ext
|
|
||||||
|
|
||||||
from . import engine
|
|
||||||
from .ignore import IgnoreList
|
|
||||||
|
|
||||||
(SCAN_TYPE_FILENAME,
|
|
||||||
SCAN_TYPE_FIELDS,
|
|
||||||
SCAN_TYPE_FIELDS_NO_ORDER,
|
|
||||||
SCAN_TYPE_TAG,
|
|
||||||
UNUSED, # Must not be removed. Constants here are what scan_type in the prefs are.
|
|
||||||
SCAN_TYPE_CONTENT,
|
|
||||||
SCAN_TYPE_CONTENT_AUDIO) = range(7)
|
|
||||||
|
|
||||||
SCANNABLE_TAGS = ['track', 'artist', 'album', 'title', 'genre', 'year']
|
|
||||||
|
|
||||||
class Scanner(object):
|
|
||||||
def __init__(self):
|
|
||||||
self.ignore_list = IgnoreList()
|
|
||||||
self.discarded_file_count = 0
|
|
||||||
|
|
||||||
def _getmatches(self, files, j):
|
|
||||||
if self.size_threshold:
|
|
||||||
j = j.start_subjob([2, 8])
|
|
||||||
for f in j.iter_with_progress(files, 'Read size of %d/%d files'):
|
|
||||||
f.size # pre-read, makes a smoother progress if read here (especially for bundles)
|
|
||||||
files = [f for f in files if f.size >= self.size_threshold]
|
|
||||||
if self.scan_type in (SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO):
|
|
||||||
sizeattr = 'size' if self.scan_type == SCAN_TYPE_CONTENT else 'audiosize'
|
|
||||||
return engine.getmatches_by_contents(files, sizeattr, partial=self.scan_type==SCAN_TYPE_CONTENT_AUDIO, j=j)
|
|
||||||
else:
|
|
||||||
j = j.start_subjob([2, 8])
|
|
||||||
kw = {}
|
|
||||||
kw['match_similar_words'] = self.match_similar_words
|
|
||||||
kw['weight_words'] = self.word_weighting
|
|
||||||
kw['min_match_percentage'] = self.min_match_percentage
|
|
||||||
if self.scan_type == SCAN_TYPE_FIELDS_NO_ORDER:
|
|
||||||
self.scan_type = SCAN_TYPE_FIELDS
|
|
||||||
kw['no_field_order'] = True
|
|
||||||
func = {
|
|
||||||
SCAN_TYPE_FILENAME: lambda f: engine.getwords(rem_file_ext(f.name)),
|
|
||||||
SCAN_TYPE_FIELDS: lambda f: engine.getfields(rem_file_ext(f.name)),
|
|
||||||
SCAN_TYPE_TAG: lambda f: [engine.getwords(unicode(getattr(f, attrname))) for attrname in SCANNABLE_TAGS if attrname in self.scanned_tags],
|
|
||||||
}[self.scan_type]
|
|
||||||
for f in j.iter_with_progress(files, 'Read metadata of %d/%d files'):
|
|
||||||
f.words = func(f)
|
|
||||||
return engine.getmatches(files, j=j, **kw)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _key_func(dupe):
|
|
||||||
return (not dupe.is_ref, -dupe.size)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _tie_breaker(ref, dupe):
|
|
||||||
refname = rem_file_ext(ref.name).lower()
|
|
||||||
dupename = rem_file_ext(dupe.name).lower()
|
|
||||||
if 'copy' in refname and 'copy' not in dupename:
|
|
||||||
return True
|
|
||||||
if refname.startswith(dupename) and (refname[len(dupename):].strip().isdigit()):
|
|
||||||
return True
|
|
||||||
return len(dupe.path) > len(ref.path)
|
|
||||||
|
|
||||||
def GetDupeGroups(self, files, j=job.nulljob):
|
|
||||||
j = j.start_subjob([8, 2])
|
|
||||||
for f in [f for f in files if not hasattr(f, 'is_ref')]:
|
|
||||||
f.is_ref = False
|
|
||||||
logging.info('Getting matches')
|
|
||||||
matches = self._getmatches(files, j)
|
|
||||||
logging.info('Found %d matches' % len(matches))
|
|
||||||
if not self.mix_file_kind:
|
|
||||||
j.set_progress(100, 'Removing false matches')
|
|
||||||
matches = [m for m in matches if get_file_ext(m.first.name) == get_file_ext(m.second.name)]
|
|
||||||
if self.ignore_list:
|
|
||||||
j = j.start_subjob(2)
|
|
||||||
iter_matches = j.iter_with_progress(matches, 'Processed %d/%d matches against the ignore list')
|
|
||||||
matches = [m for m in iter_matches
|
|
||||||
if not self.ignore_list.AreIgnored(unicode(m.first.path), unicode(m.second.path))]
|
|
||||||
logging.info('Grouping matches')
|
|
||||||
groups = engine.get_groups(matches, j)
|
|
||||||
matched_files = dedupe([m.first for m in matches] + [m.second for m in matches])
|
|
||||||
self.discarded_file_count = len(matched_files) - sum(len(g) for g in groups)
|
|
||||||
groups = [g for g in groups if any(not f.is_ref for f in g)]
|
|
||||||
logging.info('Created %d groups' % len(groups))
|
|
||||||
j.set_progress(100, 'Doing group prioritization')
|
|
||||||
for g in groups:
|
|
||||||
g.prioritize(self._key_func, self._tie_breaker)
|
|
||||||
return groups
|
|
||||||
|
|
||||||
match_similar_words = False
|
|
||||||
min_match_percentage = 80
|
|
||||||
mix_file_kind = True
|
|
||||||
scan_type = SCAN_TYPE_FILENAME
|
|
||||||
scanned_tags = set(['artist', 'title'])
|
|
||||||
size_threshold = 0
|
|
||||||
word_weighting = False
|
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2006/11/11
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
import tempfile
|
|
||||||
import shutil
|
|
||||||
import logging
|
|
||||||
import os.path as op
|
|
||||||
|
|
||||||
from nose.tools import eq_
|
|
||||||
|
|
||||||
from hsutil.path import Path
|
|
||||||
from hsutil.testcase import TestCase
|
|
||||||
from hsutil.decorators import log_calls
|
|
||||||
from hsutil import io
|
|
||||||
|
|
||||||
from . import data
|
|
||||||
from .results_test import GetTestGroups
|
|
||||||
from .. import engine, fs
|
|
||||||
try:
|
|
||||||
from ..app_cocoa import DupeGuru as DupeGuruBase
|
|
||||||
except ImportError:
|
|
||||||
from nose.plugins.skip import SkipTest
|
|
||||||
raise SkipTest("These tests can only be run on OS X")
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
|
||||||
def __init__(self):
|
|
||||||
DupeGuruBase.__init__(self, data, '/tmp', appid=4)
|
|
||||||
|
|
||||||
def _start_job(self, jobid, func):
|
|
||||||
func(nulljob)
|
|
||||||
|
|
||||||
def r2np(rows):
|
|
||||||
#Transforms a list of rows [1,2,3] into a list of node paths [[1],[2],[3]]
|
|
||||||
return [[i] for i in rows]
|
|
||||||
|
|
||||||
class TCDupeGuru(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.app = DupeGuru()
|
|
||||||
self.objects,self.matches,self.groups = GetTestGroups()
|
|
||||||
self.app.results.groups = self.groups
|
|
||||||
tmppath = self.tmppath()
|
|
||||||
io.mkdir(tmppath + 'foo')
|
|
||||||
io.mkdir(tmppath + 'bar')
|
|
||||||
self.app.directories.add_path(tmppath)
|
|
||||||
|
|
||||||
def test_GetObjects(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
groups = self.groups
|
|
||||||
g,d = app.GetObjects([0])
|
|
||||||
self.assert_(g is groups[0])
|
|
||||||
self.assert_(d is None)
|
|
||||||
g,d = app.GetObjects([0,0])
|
|
||||||
self.assert_(g is groups[0])
|
|
||||||
self.assert_(d is objects[1])
|
|
||||||
g,d = app.GetObjects([1,0])
|
|
||||||
self.assert_(g is groups[1])
|
|
||||||
self.assert_(d is objects[4])
|
|
||||||
|
|
||||||
def test_GetObjects_after_sort(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
groups = self.groups[:] #To keep the old order in memory
|
|
||||||
app.sort_groups(0,False) #0 = Filename
|
|
||||||
#Now, the group order is supposed to be reversed
|
|
||||||
g,d = app.GetObjects([0,0])
|
|
||||||
self.assert_(g is groups[1])
|
|
||||||
self.assert_(d is objects[4])
|
|
||||||
|
|
||||||
def test_GetObjects_out_of_range(self):
|
|
||||||
app = self.app
|
|
||||||
self.assertEqual((None,None),app.GetObjects([2]))
|
|
||||||
self.assertEqual((None,None),app.GetObjects([]))
|
|
||||||
self.assertEqual((None,None),app.GetObjects([1,2]))
|
|
||||||
|
|
||||||
def test_selected_result_node_paths(self):
|
|
||||||
# app.selected_dupes is correctly converted into node paths
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
paths = [[0, 0], [0, 1], [1]]
|
|
||||||
app.SelectResultNodePaths(paths)
|
|
||||||
eq_(app.selected_result_node_paths(), paths)
|
|
||||||
|
|
||||||
def test_selected_result_node_paths_after_deletion(self):
|
|
||||||
# cases where the selected dupes aren't there are correctly handled
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
paths = [[0, 0], [0, 1], [1]]
|
|
||||||
app.SelectResultNodePaths(paths)
|
|
||||||
app.RemoveSelected()
|
|
||||||
# The first 2 dupes have been removed. The 3rd one is a ref. it stays there, in first pos.
|
|
||||||
eq_(app.selected_result_node_paths(), [[0]]) # no exception
|
|
||||||
|
|
||||||
def test_selectResultNodePaths(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
app.SelectResultNodePaths([[0,0],[0,1]])
|
|
||||||
self.assertEqual(2,len(app.selected_dupes))
|
|
||||||
self.assert_(app.selected_dupes[0] is objects[1])
|
|
||||||
self.assert_(app.selected_dupes[1] is objects[2])
|
|
||||||
|
|
||||||
def test_selectResultNodePaths_with_ref(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
app.SelectResultNodePaths([[0,0],[0,1],[1]])
|
|
||||||
self.assertEqual(3,len(app.selected_dupes))
|
|
||||||
self.assert_(app.selected_dupes[0] is objects[1])
|
|
||||||
self.assert_(app.selected_dupes[1] is objects[2])
|
|
||||||
self.assert_(app.selected_dupes[2] is self.groups[1].ref)
|
|
||||||
|
|
||||||
def test_selectResultNodePaths_empty(self):
|
|
||||||
self.app.SelectResultNodePaths([])
|
|
||||||
self.assertEqual(0,len(self.app.selected_dupes))
|
|
||||||
|
|
||||||
def test_selectResultNodePaths_after_sort(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
groups = self.groups[:] #To keep the old order in memory
|
|
||||||
app.sort_groups(0,False) #0 = Filename
|
|
||||||
#Now, the group order is supposed to be reversed
|
|
||||||
app.SelectResultNodePaths([[0,0],[1],[1,0]])
|
|
||||||
self.assertEqual(3,len(app.selected_dupes))
|
|
||||||
self.assert_(app.selected_dupes[0] is objects[4])
|
|
||||||
self.assert_(app.selected_dupes[1] is groups[0].ref)
|
|
||||||
self.assert_(app.selected_dupes[2] is objects[1])
|
|
||||||
|
|
||||||
def test_selectResultNodePaths_out_of_range(self):
|
|
||||||
app = self.app
|
|
||||||
app.SelectResultNodePaths([[0,0],[0,1],[1],[1,1],[2]])
|
|
||||||
self.assertEqual(3,len(app.selected_dupes))
|
|
||||||
|
|
||||||
def test_selected_powermarker_node_paths(self):
|
|
||||||
# app.selected_dupes is correctly converted into paths
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
paths = r2np([0, 1, 2])
|
|
||||||
app.SelectPowerMarkerNodePaths(paths)
|
|
||||||
eq_(app.selected_powermarker_node_paths(), paths)
|
|
||||||
|
|
||||||
def test_selected_powermarker_node_paths_after_deletion(self):
|
|
||||||
# cases where the selected dupes aren't there are correctly handled
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
paths = r2np([0, 1, 2])
|
|
||||||
app.SelectPowerMarkerNodePaths(paths)
|
|
||||||
app.RemoveSelected()
|
|
||||||
eq_(app.selected_powermarker_node_paths(), []) # no exception
|
|
||||||
|
|
||||||
def test_selectPowerMarkerRows(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0,1,2]))
|
|
||||||
self.assertEqual(3,len(app.selected_dupes))
|
|
||||||
self.assert_(app.selected_dupes[0] is objects[1])
|
|
||||||
self.assert_(app.selected_dupes[1] is objects[2])
|
|
||||||
self.assert_(app.selected_dupes[2] is objects[4])
|
|
||||||
|
|
||||||
def test_selectPowerMarkerRows_empty(self):
|
|
||||||
self.app.SelectPowerMarkerNodePaths([])
|
|
||||||
self.assertEqual(0,len(self.app.selected_dupes))
|
|
||||||
|
|
||||||
def test_selectPowerMarkerRows_after_sort(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
app.sort_dupes(0,False) #0 = Filename
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0,1,2]))
|
|
||||||
self.assertEqual(3,len(app.selected_dupes))
|
|
||||||
self.assert_(app.selected_dupes[0] is objects[4])
|
|
||||||
self.assert_(app.selected_dupes[1] is objects[2])
|
|
||||||
self.assert_(app.selected_dupes[2] is objects[1])
|
|
||||||
|
|
||||||
def test_selectPowerMarkerRows_out_of_range(self):
|
|
||||||
app = self.app
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0,1,2,3]))
|
|
||||||
self.assertEqual(3,len(app.selected_dupes))
|
|
||||||
|
|
||||||
def test_toggleSelectedMark(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
app.ToggleSelectedMarkState()
|
|
||||||
self.assertEqual(0,app.results.mark_count)
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0,2]))
|
|
||||||
app.ToggleSelectedMarkState()
|
|
||||||
self.assertEqual(2,app.results.mark_count)
|
|
||||||
self.assert_(not app.results.is_marked(objects[0]))
|
|
||||||
self.assert_(app.results.is_marked(objects[1]))
|
|
||||||
self.assert_(not app.results.is_marked(objects[2]))
|
|
||||||
self.assert_(not app.results.is_marked(objects[3]))
|
|
||||||
self.assert_(app.results.is_marked(objects[4]))
|
|
||||||
|
|
||||||
def test_refreshDetailsWithSelected(self):
|
|
||||||
def mock_refresh(dupe,group):
|
|
||||||
self.called = True
|
|
||||||
if self.app.selected_dupes:
|
|
||||||
self.assert_(dupe is self.app.selected_dupes[0])
|
|
||||||
self.assert_(group is self.app.results.get_group_of_duplicate(dupe))
|
|
||||||
else:
|
|
||||||
self.assert_(dupe is None)
|
|
||||||
self.assert_(group is None)
|
|
||||||
|
|
||||||
self.app.RefreshDetailsTable = mock_refresh
|
|
||||||
self.called = False
|
|
||||||
self.app.SelectPowerMarkerNodePaths(r2np([0,2]))
|
|
||||||
self.app.RefreshDetailsWithSelected()
|
|
||||||
self.assert_(self.called)
|
|
||||||
self.called = False
|
|
||||||
self.app.SelectPowerMarkerNodePaths([])
|
|
||||||
self.app.RefreshDetailsWithSelected()
|
|
||||||
self.assert_(self.called)
|
|
||||||
|
|
||||||
def test_makeSelectedReference(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
groups = self.groups
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0,2]))
|
|
||||||
app.MakeSelectedReference()
|
|
||||||
self.assert_(groups[0].ref is objects[1])
|
|
||||||
self.assert_(groups[1].ref is objects[4])
|
|
||||||
|
|
||||||
def test_makeSelectedReference_by_selecting_two_dupes_in_the_same_group(self):
|
|
||||||
app = self.app
|
|
||||||
objects = self.objects
|
|
||||||
groups = self.groups
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0,1,2]))
|
|
||||||
#Only 0 and 2 must go ref, not 1 because it is a part of the same group
|
|
||||||
app.MakeSelectedReference()
|
|
||||||
self.assert_(groups[0].ref is objects[1])
|
|
||||||
self.assert_(groups[1].ref is objects[4])
|
|
||||||
|
|
||||||
def test_removeSelected(self):
|
|
||||||
app = self.app
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0,2]))
|
|
||||||
app.RemoveSelected()
|
|
||||||
self.assertEqual(1,len(app.results.dupes))
|
|
||||||
app.RemoveSelected()
|
|
||||||
self.assertEqual(1,len(app.results.dupes))
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0,2]))
|
|
||||||
app.RemoveSelected()
|
|
||||||
self.assertEqual(0,len(app.results.dupes))
|
|
||||||
|
|
||||||
def test_addDirectory_simple(self):
|
|
||||||
# There's already a directory in self.app, so adding another once makes 2 of em
|
|
||||||
app = self.app
|
|
||||||
eq_(app.add_directory(self.datadirpath()), 0)
|
|
||||||
eq_(len(app.directories), 2)
|
|
||||||
|
|
||||||
def test_addDirectory_already_there(self):
|
|
||||||
app = self.app
|
|
||||||
self.assertEqual(0,app.add_directory(self.datadirpath()))
|
|
||||||
self.assertEqual(1,app.add_directory(self.datadirpath()))
|
|
||||||
|
|
||||||
def test_addDirectory_does_not_exist(self):
|
|
||||||
app = self.app
|
|
||||||
self.assertEqual(2,app.add_directory('/does_not_exist'))
|
|
||||||
|
|
||||||
def test_ignore(self):
|
|
||||||
app = self.app
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([2])) #The dupe of the second, 2 sized group
|
|
||||||
app.AddSelectedToIgnoreList()
|
|
||||||
self.assertEqual(1,len(app.scanner.ignore_list))
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0])) #first dupe of the 3 dupes group
|
|
||||||
app.AddSelectedToIgnoreList()
|
|
||||||
#BOTH the ref and the other dupe should have been added
|
|
||||||
self.assertEqual(3,len(app.scanner.ignore_list))
|
|
||||||
|
|
||||||
def test_purgeIgnoreList(self):
|
|
||||||
app = self.app
|
|
||||||
p1 = self.filepath('zerofile')
|
|
||||||
p2 = self.filepath('zerofill')
|
|
||||||
dne = '/does_not_exist'
|
|
||||||
app.scanner.ignore_list.Ignore(dne,p1)
|
|
||||||
app.scanner.ignore_list.Ignore(p2,dne)
|
|
||||||
app.scanner.ignore_list.Ignore(p1,p2)
|
|
||||||
app.PurgeIgnoreList()
|
|
||||||
self.assertEqual(1,len(app.scanner.ignore_list))
|
|
||||||
self.assert_(app.scanner.ignore_list.AreIgnored(p1,p2))
|
|
||||||
self.assert_(not app.scanner.ignore_list.AreIgnored(dne,p1))
|
|
||||||
|
|
||||||
def test_only_unicode_is_added_to_ignore_list(self):
|
|
||||||
def FakeIgnore(first,second):
|
|
||||||
if not isinstance(first,unicode):
|
|
||||||
self.fail()
|
|
||||||
if not isinstance(second,unicode):
|
|
||||||
self.fail()
|
|
||||||
|
|
||||||
app = self.app
|
|
||||||
app.scanner.ignore_list.Ignore = FakeIgnore
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([2])) #The dupe of the second, 2 sized group
|
|
||||||
app.AddSelectedToIgnoreList()
|
|
||||||
|
|
||||||
def test_GetOutlineViewChildCounts_out_of_range(self):
|
|
||||||
# Out of range requests don't crash and return an empty value
|
|
||||||
app = self.app
|
|
||||||
# [0, 2] is out of range
|
|
||||||
eq_(app.GetOutlineViewChildCounts(1, [0, 2]), []) # no crash
|
|
||||||
|
|
||||||
def test_GetOutlineViewValues_out_of_range(self):
|
|
||||||
# Out of range requests don't crash and return an empty value
|
|
||||||
app = self.app
|
|
||||||
# [0, 2] is out of range
|
|
||||||
eq_(app.GetOutlineViewValues(1, [0, 2]), []) # no crash
|
|
||||||
|
|
||||||
|
|
||||||
class TCDupeGuru_renameSelected(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
p = self.tmppath()
|
|
||||||
fp = open(unicode(p + 'foo bar 1'),mode='w')
|
|
||||||
fp.close()
|
|
||||||
fp = open(unicode(p + 'foo bar 2'),mode='w')
|
|
||||||
fp.close()
|
|
||||||
fp = open(unicode(p + 'foo bar 3'),mode='w')
|
|
||||||
fp.close()
|
|
||||||
files = fs.get_files(p)
|
|
||||||
matches = engine.getmatches(files)
|
|
||||||
groups = engine.get_groups(matches)
|
|
||||||
g = groups[0]
|
|
||||||
g.prioritize(lambda x:x.name)
|
|
||||||
app = DupeGuru()
|
|
||||||
app.results.groups = groups
|
|
||||||
self.app = app
|
|
||||||
self.groups = groups
|
|
||||||
self.p = p
|
|
||||||
self.files = files
|
|
||||||
|
|
||||||
def test_simple(self):
|
|
||||||
app = self.app
|
|
||||||
g = self.groups[0]
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0]))
|
|
||||||
assert app.RenameSelected('renamed')
|
|
||||||
names = io.listdir(self.p)
|
|
||||||
assert 'renamed' in names
|
|
||||||
assert 'foo bar 2' not in names
|
|
||||||
eq_(g.dupes[0].name, 'renamed')
|
|
||||||
|
|
||||||
def test_none_selected(self):
|
|
||||||
app = self.app
|
|
||||||
g = self.groups[0]
|
|
||||||
app.SelectPowerMarkerNodePaths([])
|
|
||||||
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
|
||||||
assert not app.RenameSelected('renamed')
|
|
||||||
msg = logging.warning.calls[0]['msg']
|
|
||||||
eq_('dupeGuru Warning: list index out of range', msg)
|
|
||||||
names = io.listdir(self.p)
|
|
||||||
assert 'renamed' not in names
|
|
||||||
assert 'foo bar 2' in names
|
|
||||||
eq_(g.dupes[0].name, 'foo bar 2')
|
|
||||||
|
|
||||||
def test_name_already_exists(self):
|
|
||||||
app = self.app
|
|
||||||
g = self.groups[0]
|
|
||||||
app.SelectPowerMarkerNodePaths(r2np([0]))
|
|
||||||
self.mock(logging, 'warning', log_calls(lambda msg: None))
|
|
||||||
assert not app.RenameSelected('foo bar 1')
|
|
||||||
msg = logging.warning.calls[0]['msg']
|
|
||||||
assert msg.startswith('dupeGuru Warning: \'foo bar 1\' already exists in')
|
|
||||||
names = io.listdir(self.p)
|
|
||||||
assert 'foo bar 1' in names
|
|
||||||
assert 'foo bar 2' in names
|
|
||||||
eq_(g.dupes[0].name, 'foo bar 2')
|
|
||||||
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2007-06-23
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from hsutil.testcase import TestCase
|
|
||||||
from hsutil import io
|
|
||||||
from hsutil.path import Path
|
|
||||||
from hsutil.decorators import log_calls
|
|
||||||
import hsutil.files
|
|
||||||
from hsutil.job import nulljob
|
|
||||||
|
|
||||||
from . import data
|
|
||||||
from .. import app, fs
|
|
||||||
from ..app import DupeGuru as DupeGuruBase
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase):
|
|
||||||
def __init__(self):
|
|
||||||
DupeGuruBase.__init__(self, data, '/tmp', appid=4)
|
|
||||||
|
|
||||||
def _start_job(self, jobid, func):
|
|
||||||
func(nulljob)
|
|
||||||
|
|
||||||
|
|
||||||
class TCDupeGuru(TestCase):
|
|
||||||
cls_tested_module = app
|
|
||||||
def test_apply_filter_calls_results_apply_filter(self):
|
|
||||||
app = DupeGuru()
|
|
||||||
self.mock(app.results, 'apply_filter', log_calls(app.results.apply_filter))
|
|
||||||
app.apply_filter('foo')
|
|
||||||
self.assertEqual(2, len(app.results.apply_filter.calls))
|
|
||||||
call = app.results.apply_filter.calls[0]
|
|
||||||
self.assert_(call['filter_str'] is None)
|
|
||||||
call = app.results.apply_filter.calls[1]
|
|
||||||
self.assertEqual('foo', call['filter_str'])
|
|
||||||
|
|
||||||
def test_apply_filter_escapes_regexp(self):
|
|
||||||
app = DupeGuru()
|
|
||||||
self.mock(app.results, 'apply_filter', log_calls(app.results.apply_filter))
|
|
||||||
app.apply_filter('()[]\\.|+?^abc')
|
|
||||||
call = app.results.apply_filter.calls[1]
|
|
||||||
self.assertEqual('\\(\\)\\[\\]\\\\\\.\\|\\+\\?\\^abc', call['filter_str'])
|
|
||||||
app.apply_filter('(*)') # In "simple mode", we want the * to behave as a wilcard
|
|
||||||
call = app.results.apply_filter.calls[3]
|
|
||||||
self.assertEqual('\(.*\)', call['filter_str'])
|
|
||||||
app.options['escape_filter_regexp'] = False
|
|
||||||
app.apply_filter('(abc)')
|
|
||||||
call = app.results.apply_filter.calls[5]
|
|
||||||
self.assertEqual('(abc)', call['filter_str'])
|
|
||||||
|
|
||||||
def test_copy_or_move(self):
|
|
||||||
# The goal here is just to have a test for a previous blowup I had. I know my test coverage
|
|
||||||
# for this unit is pathetic. What's done is done. My approach now is to add tests for
|
|
||||||
# every change I want to make. The blowup was caused by a missing import.
|
|
||||||
p = self.tmppath()
|
|
||||||
io.open(p + 'foo', 'w').close()
|
|
||||||
self.mock(hsutil.files, 'copy', log_calls(lambda source_path, dest_path: None))
|
|
||||||
self.mock(os, 'makedirs', lambda path: None) # We don't want the test to create that fake directory
|
|
||||||
app = DupeGuru()
|
|
||||||
app.directories.add_path(p)
|
|
||||||
[f] = app.directories.get_files()
|
|
||||||
app.copy_or_move(f, True, 'some_destination', 0)
|
|
||||||
self.assertEqual(1, len(hsutil.files.copy.calls))
|
|
||||||
call = hsutil.files.copy.calls[0]
|
|
||||||
self.assertEqual('some_destination', call['dest_path'])
|
|
||||||
self.assertEqual(f.path, call['source_path'])
|
|
||||||
|
|
||||||
def test_copy_or_move_clean_empty_dirs(self):
|
|
||||||
tmppath = Path(self.tmpdir())
|
|
||||||
sourcepath = tmppath + 'source'
|
|
||||||
io.mkdir(sourcepath)
|
|
||||||
io.open(sourcepath + 'myfile', 'w')
|
|
||||||
app = DupeGuru()
|
|
||||||
app.directories.add_path(tmppath)
|
|
||||||
[myfile] = app.directories.get_files()
|
|
||||||
self.mock(app, 'clean_empty_dirs', log_calls(lambda path: None))
|
|
||||||
app.copy_or_move(myfile, False, tmppath + 'dest', 0)
|
|
||||||
calls = app.clean_empty_dirs.calls
|
|
||||||
self.assertEqual(1, len(calls))
|
|
||||||
self.assertEqual(sourcepath, calls[0]['path'])
|
|
||||||
|
|
||||||
def test_Scan_with_objects_evaluating_to_false(self):
|
|
||||||
class FakeFile(fs.File):
|
|
||||||
def __nonzero__(self):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
# At some point, any() was used in a wrong way that made Scan() wrongly return 1
|
|
||||||
app = DupeGuru()
|
|
||||||
f1, f2 = [FakeFile('foo') for i in range(2)]
|
|
||||||
f1.is_ref, f2.is_ref = (False, False)
|
|
||||||
assert not (bool(f1) and bool(f2))
|
|
||||||
app.directories.get_files = lambda: [f1, f2]
|
|
||||||
app.directories._dirs.append('this is just so Scan() doesnt return 3')
|
|
||||||
app.start_scanning() # no exception
|
|
||||||
|
|
||||||
|
|
||||||
class TCDupeGuru_clean_empty_dirs(TestCase):
|
|
||||||
cls_tested_module = app
|
|
||||||
def setUp(self):
|
|
||||||
self.mock(hsutil.files, 'delete_if_empty', log_calls(lambda path, files_to_delete=[]: None))
|
|
||||||
self.app = DupeGuru()
|
|
||||||
|
|
||||||
def test_option_off(self):
|
|
||||||
self.app.clean_empty_dirs(Path('/foo/bar'))
|
|
||||||
self.assertEqual(0, len(hsutil.files.delete_if_empty.calls))
|
|
||||||
|
|
||||||
def test_option_on(self):
|
|
||||||
self.app.options['clean_empty_dirs'] = True
|
|
||||||
self.app.clean_empty_dirs(Path('/foo/bar'))
|
|
||||||
calls = hsutil.files.delete_if_empty.calls
|
|
||||||
self.assertEqual(1, len(calls))
|
|
||||||
self.assertEqual(Path('/foo/bar'), calls[0]['path'])
|
|
||||||
self.assertEqual(['.DS_Store'], calls[0]['files_to_delete'])
|
|
||||||
|
|
||||||
def test_recurse_up(self):
|
|
||||||
# delete_if_empty must be recursively called up in the path until it returns False
|
|
||||||
@log_calls
|
|
||||||
def mock_delete_if_empty(path, files_to_delete=[]):
|
|
||||||
return len(path) > 1
|
|
||||||
|
|
||||||
self.mock(hsutil.files, 'delete_if_empty', mock_delete_if_empty)
|
|
||||||
self.app.options['clean_empty_dirs'] = True
|
|
||||||
self.app.clean_empty_dirs(Path('not-empty/empty/empty'))
|
|
||||||
calls = hsutil.files.delete_if_empty.calls
|
|
||||||
self.assertEqual(3, len(calls))
|
|
||||||
self.assertEqual(Path('not-empty/empty/empty'), calls[0]['path'])
|
|
||||||
self.assertEqual(Path('not-empty/empty'), calls[1]['path'])
|
|
||||||
self.assertEqual(Path('not-empty'), calls[2]['path'])
|
|
||||||
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-10-23
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
# data module for tests
|
|
||||||
|
|
||||||
from hsutil.str import format_size
|
|
||||||
from dupeguru.data import format_path, cmp_value
|
|
||||||
|
|
||||||
COLUMNS = [
|
|
||||||
{'attr':'name','display':'Filename'},
|
|
||||||
{'attr':'path','display':'Directory'},
|
|
||||||
{'attr':'size','display':'Size (KB)'},
|
|
||||||
{'attr':'extension','display':'Kind'},
|
|
||||||
]
|
|
||||||
|
|
||||||
METADATA_TO_READ = ['size']
|
|
||||||
|
|
||||||
def GetDisplayInfo(dupe, group, delta):
|
|
||||||
size = dupe.size
|
|
||||||
m = group.get_match_of(dupe)
|
|
||||||
if m and delta:
|
|
||||||
r = group.ref
|
|
||||||
size -= r.size
|
|
||||||
return [
|
|
||||||
dupe.name,
|
|
||||||
format_path(dupe.path),
|
|
||||||
format_size(size, 0, 1, False),
|
|
||||||
dupe.extension,
|
|
||||||
]
|
|
||||||
|
|
||||||
def GetDupeSortKey(dupe, get_group, key, delta):
|
|
||||||
r = cmp_value(getattr(dupe, COLUMNS[key]['attr']))
|
|
||||||
if delta and (key == 2):
|
|
||||||
r -= cmp_value(getattr(get_group().ref, COLUMNS[key]['attr']))
|
|
||||||
return r
|
|
||||||
|
|
||||||
def GetGroupSortKey(group, key):
|
|
||||||
return cmp_value(getattr(group.ref, COLUMNS[key]['attr']))
|
|
||||||
@@ -1,279 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2006/02/27
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
import os.path as op
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
|
|
||||||
from nose.tools import eq_
|
|
||||||
|
|
||||||
from hsutil import io
|
|
||||||
from hsutil.path import Path
|
|
||||||
from hsutil.testcase import TestCase
|
|
||||||
|
|
||||||
from ..directories import *
|
|
||||||
|
|
||||||
testpath = Path(TestCase.datadirpath())
|
|
||||||
|
|
||||||
def create_fake_fs(rootpath):
|
|
||||||
rootpath = rootpath + 'fs'
|
|
||||||
io.mkdir(rootpath)
|
|
||||||
io.mkdir(rootpath + 'dir1')
|
|
||||||
io.mkdir(rootpath + 'dir2')
|
|
||||||
io.mkdir(rootpath + 'dir3')
|
|
||||||
fp = io.open(rootpath + 'file1.test', 'w')
|
|
||||||
fp.write('1')
|
|
||||||
fp.close()
|
|
||||||
fp = io.open(rootpath + 'file2.test', 'w')
|
|
||||||
fp.write('12')
|
|
||||||
fp.close()
|
|
||||||
fp = io.open(rootpath + 'file3.test', 'w')
|
|
||||||
fp.write('123')
|
|
||||||
fp.close()
|
|
||||||
fp = io.open(rootpath + ('dir1', 'file1.test'), 'w')
|
|
||||||
fp.write('1')
|
|
||||||
fp.close()
|
|
||||||
fp = io.open(rootpath + ('dir2', 'file2.test'), 'w')
|
|
||||||
fp.write('12')
|
|
||||||
fp.close()
|
|
||||||
fp = io.open(rootpath + ('dir3', 'file3.test'), 'w')
|
|
||||||
fp.write('123')
|
|
||||||
fp.close()
|
|
||||||
return rootpath
|
|
||||||
|
|
||||||
class TCDirectories(TestCase):
|
|
||||||
def test_empty(self):
|
|
||||||
d = Directories()
|
|
||||||
self.assertEqual(0,len(d))
|
|
||||||
self.assert_('foobar' not in d)
|
|
||||||
|
|
||||||
def test_add_path(self):
|
|
||||||
d = Directories()
|
|
||||||
p = testpath + 'utils'
|
|
||||||
d.add_path(p)
|
|
||||||
self.assertEqual(1,len(d))
|
|
||||||
self.assert_(p in d)
|
|
||||||
self.assert_((p + 'foobar') in d)
|
|
||||||
self.assert_(p[:-1] not in d)
|
|
||||||
p = self.tmppath()
|
|
||||||
d.add_path(p)
|
|
||||||
self.assertEqual(2,len(d))
|
|
||||||
self.assert_(p in d)
|
|
||||||
|
|
||||||
def test_AddPath_when_path_is_already_there(self):
|
|
||||||
d = Directories()
|
|
||||||
p = testpath + 'utils'
|
|
||||||
d.add_path(p)
|
|
||||||
self.assertRaises(AlreadyThereError, d.add_path, p)
|
|
||||||
self.assertRaises(AlreadyThereError, d.add_path, p + 'foobar')
|
|
||||||
self.assertEqual(1, len(d))
|
|
||||||
|
|
||||||
def test_add_path_containing_paths_already_there(self):
|
|
||||||
d = Directories()
|
|
||||||
d.add_path(testpath + 'utils')
|
|
||||||
self.assertEqual(1, len(d))
|
|
||||||
d.add_path(testpath)
|
|
||||||
eq_(len(d), 1)
|
|
||||||
eq_(d[0], testpath)
|
|
||||||
|
|
||||||
def test_AddPath_non_latin(self):
|
|
||||||
p = Path(self.tmpdir())
|
|
||||||
to_add = p + u'unicode\u201a'
|
|
||||||
os.mkdir(unicode(to_add))
|
|
||||||
d = Directories()
|
|
||||||
try:
|
|
||||||
d.add_path(to_add)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
self.fail()
|
|
||||||
|
|
||||||
def test_del(self):
|
|
||||||
d = Directories()
|
|
||||||
d.add_path(testpath + 'utils')
|
|
||||||
try:
|
|
||||||
del d[1]
|
|
||||||
self.fail()
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
d.add_path(self.tmppath())
|
|
||||||
del d[1]
|
|
||||||
self.assertEqual(1, len(d))
|
|
||||||
|
|
||||||
def test_states(self):
|
|
||||||
d = Directories()
|
|
||||||
p = testpath + 'utils'
|
|
||||||
d.add_path(p)
|
|
||||||
self.assertEqual(STATE_NORMAL,d.get_state(p))
|
|
||||||
d.set_state(p,STATE_REFERENCE)
|
|
||||||
self.assertEqual(STATE_REFERENCE,d.get_state(p))
|
|
||||||
self.assertEqual(STATE_REFERENCE,d.get_state(p + 'dir1'))
|
|
||||||
self.assertEqual(1,len(d.states))
|
|
||||||
self.assertEqual(p,d.states.keys()[0])
|
|
||||||
self.assertEqual(STATE_REFERENCE,d.states[p])
|
|
||||||
|
|
||||||
def test_get_state_with_path_not_there(self):
|
|
||||||
# When the path's not there, just return STATE_NORMAL
|
|
||||||
d = Directories()
|
|
||||||
d.add_path(testpath + 'utils')
|
|
||||||
eq_(d.get_state(testpath), STATE_NORMAL)
|
|
||||||
|
|
||||||
def test_states_remain_when_larger_directory_eat_smaller_ones(self):
|
|
||||||
d = Directories()
|
|
||||||
p = testpath + 'utils'
|
|
||||||
d.add_path(p)
|
|
||||||
d.set_state(p,STATE_EXCLUDED)
|
|
||||||
d.add_path(testpath)
|
|
||||||
d.set_state(testpath,STATE_REFERENCE)
|
|
||||||
self.assertEqual(STATE_EXCLUDED,d.get_state(p))
|
|
||||||
self.assertEqual(STATE_EXCLUDED,d.get_state(p + 'dir1'))
|
|
||||||
self.assertEqual(STATE_REFERENCE,d.get_state(testpath))
|
|
||||||
|
|
||||||
def test_set_state_keep_state_dict_size_to_minimum(self):
|
|
||||||
d = Directories()
|
|
||||||
p = create_fake_fs(self.tmppath())
|
|
||||||
d.add_path(p)
|
|
||||||
d.set_state(p,STATE_REFERENCE)
|
|
||||||
d.set_state(p + 'dir1',STATE_REFERENCE)
|
|
||||||
self.assertEqual(1,len(d.states))
|
|
||||||
self.assertEqual(STATE_REFERENCE,d.get_state(p + 'dir1'))
|
|
||||||
d.set_state(p + 'dir1',STATE_NORMAL)
|
|
||||||
self.assertEqual(2,len(d.states))
|
|
||||||
self.assertEqual(STATE_NORMAL,d.get_state(p + 'dir1'))
|
|
||||||
d.set_state(p + 'dir1',STATE_REFERENCE)
|
|
||||||
self.assertEqual(1,len(d.states))
|
|
||||||
self.assertEqual(STATE_REFERENCE,d.get_state(p + 'dir1'))
|
|
||||||
|
|
||||||
def test_get_files(self):
|
|
||||||
d = Directories()
|
|
||||||
p = create_fake_fs(self.tmppath())
|
|
||||||
d.add_path(p)
|
|
||||||
d.set_state(p + 'dir1',STATE_REFERENCE)
|
|
||||||
d.set_state(p + 'dir2',STATE_EXCLUDED)
|
|
||||||
files = d.get_files()
|
|
||||||
self.assertEqual(5, len(list(files)))
|
|
||||||
for f in files:
|
|
||||||
if f.parent.path == p + 'dir1':
|
|
||||||
self.assert_(f.is_ref)
|
|
||||||
else:
|
|
||||||
self.assert_(not f.is_ref)
|
|
||||||
|
|
||||||
def test_get_files_with_inherited_exclusion(self):
|
|
||||||
d = Directories()
|
|
||||||
p = testpath + 'utils'
|
|
||||||
d.add_path(p)
|
|
||||||
d.set_state(p,STATE_EXCLUDED)
|
|
||||||
self.assertEqual([], list(d.get_files()))
|
|
||||||
|
|
||||||
def test_save_and_load(self):
|
|
||||||
d1 = Directories()
|
|
||||||
d2 = Directories()
|
|
||||||
p1 = self.tmppath()
|
|
||||||
p2 = self.tmppath()
|
|
||||||
d1.add_path(p1)
|
|
||||||
d1.add_path(p2)
|
|
||||||
d1.set_state(p1, STATE_REFERENCE)
|
|
||||||
d1.set_state(p1 + 'dir1',STATE_EXCLUDED)
|
|
||||||
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
|
||||||
d1.save_to_file(tmpxml)
|
|
||||||
d2.load_from_file(tmpxml)
|
|
||||||
self.assertEqual(2, len(d2))
|
|
||||||
self.assertEqual(STATE_REFERENCE,d2.get_state(p1))
|
|
||||||
self.assertEqual(STATE_EXCLUDED,d2.get_state(p1 + 'dir1'))
|
|
||||||
|
|
||||||
def test_invalid_path(self):
|
|
||||||
d = Directories()
|
|
||||||
p = Path('does_not_exist')
|
|
||||||
self.assertRaises(InvalidPathError, d.add_path, p)
|
|
||||||
self.assertEqual(0, len(d))
|
|
||||||
|
|
||||||
def test_set_state_on_invalid_path(self):
|
|
||||||
d = Directories()
|
|
||||||
try:
|
|
||||||
d.set_state(Path('foobar',),STATE_NORMAL)
|
|
||||||
except LookupError:
|
|
||||||
self.fail()
|
|
||||||
|
|
||||||
def test_load_from_file_with_invalid_path(self):
|
|
||||||
#This test simulates a load from file resulting in a
|
|
||||||
#InvalidPath raise. Other directories must be loaded.
|
|
||||||
d1 = Directories()
|
|
||||||
d1.add_path(testpath + 'utils')
|
|
||||||
#Will raise InvalidPath upon loading
|
|
||||||
p = self.tmppath()
|
|
||||||
d1.add_path(p)
|
|
||||||
io.rmdir(p)
|
|
||||||
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
|
||||||
d1.save_to_file(tmpxml)
|
|
||||||
d2 = Directories()
|
|
||||||
d2.load_from_file(tmpxml)
|
|
||||||
self.assertEqual(1, len(d2))
|
|
||||||
|
|
||||||
def test_unicode_save(self):
|
|
||||||
d = Directories()
|
|
||||||
p1 = self.tmppath() + u'hello\xe9'
|
|
||||||
io.mkdir(p1)
|
|
||||||
io.mkdir(p1 + u'foo\xe9')
|
|
||||||
d.add_path(p1)
|
|
||||||
d.set_state(p1 + u'foo\xe9', STATE_EXCLUDED)
|
|
||||||
tmpxml = op.join(self.tmpdir(), 'directories_testunit.xml')
|
|
||||||
try:
|
|
||||||
d.save_to_file(tmpxml)
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
self.fail()
|
|
||||||
|
|
||||||
def test_get_files_refreshes_its_directories(self):
|
|
||||||
d = Directories()
|
|
||||||
p = create_fake_fs(self.tmppath())
|
|
||||||
d.add_path(p)
|
|
||||||
files = d.get_files()
|
|
||||||
self.assertEqual(6, len(list(files)))
|
|
||||||
time.sleep(1)
|
|
||||||
os.remove(str(p + ('dir1','file1.test')))
|
|
||||||
files = d.get_files()
|
|
||||||
self.assertEqual(5, len(list(files)))
|
|
||||||
|
|
||||||
def test_get_files_does_not_choke_on_non_existing_directories(self):
|
|
||||||
d = Directories()
|
|
||||||
p = Path(self.tmpdir())
|
|
||||||
d.add_path(p)
|
|
||||||
io.rmtree(p)
|
|
||||||
self.assertEqual([], list(d.get_files()))
|
|
||||||
|
|
||||||
def test_get_state_returns_excluded_by_default_for_hidden_directories(self):
|
|
||||||
d = Directories()
|
|
||||||
p = Path(self.tmpdir())
|
|
||||||
hidden_dir_path = p + '.foo'
|
|
||||||
io.mkdir(p + '.foo')
|
|
||||||
d.add_path(p)
|
|
||||||
self.assertEqual(d.get_state(hidden_dir_path), STATE_EXCLUDED)
|
|
||||||
# But it can be overriden
|
|
||||||
d.set_state(hidden_dir_path, STATE_NORMAL)
|
|
||||||
self.assertEqual(d.get_state(hidden_dir_path), STATE_NORMAL)
|
|
||||||
|
|
||||||
def test_default_path_state_override(self):
|
|
||||||
# It's possible for a subclass to override the default state of a path
|
|
||||||
class MyDirectories(Directories):
|
|
||||||
def _default_state_for_path(self, path):
|
|
||||||
if 'foobar' in path:
|
|
||||||
return STATE_EXCLUDED
|
|
||||||
|
|
||||||
d = MyDirectories()
|
|
||||||
p1 = self.tmppath()
|
|
||||||
io.mkdir(p1 + 'foobar')
|
|
||||||
io.open(p1 + 'foobar/somefile', 'w').close()
|
|
||||||
io.mkdir(p1 + 'foobaz')
|
|
||||||
io.open(p1 + 'foobaz/somefile', 'w').close()
|
|
||||||
d.add_path(p1)
|
|
||||||
eq_(d.get_state(p1 + 'foobaz'), STATE_NORMAL)
|
|
||||||
eq_(d.get_state(p1 + 'foobar'), STATE_EXCLUDED)
|
|
||||||
eq_(len(list(d.get_files())), 1) # only the 'foobaz' file is there
|
|
||||||
# However, the default state can be changed
|
|
||||||
d.set_state(p1 + 'foobar', STATE_NORMAL)
|
|
||||||
eq_(d.get_state(p1 + 'foobar'), STATE_NORMAL)
|
|
||||||
eq_(len(list(d.get_files())), 2)
|
|
||||||
|
|
||||||
@@ -1,718 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2006/02/23
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import StringIO
|
|
||||||
import xml.dom.minidom
|
|
||||||
import os.path as op
|
|
||||||
|
|
||||||
from hsutil.path import Path
|
|
||||||
from hsutil.testcase import TestCase
|
|
||||||
from hsutil.misc import first
|
|
||||||
|
|
||||||
from . import engine_test, data
|
|
||||||
from .. import engine
|
|
||||||
from ..results import *
|
|
||||||
|
|
||||||
class NamedObject(engine_test.NamedObject):
|
|
||||||
size = 1
|
|
||||||
path = property(lambda x:Path('basepath') + x.name)
|
|
||||||
is_ref = False
|
|
||||||
|
|
||||||
def __nonzero__(self):
|
|
||||||
return False #Make sure that operations are made correctly when the bool value of files is false.
|
|
||||||
|
|
||||||
# Returns a group set that looks like that:
|
|
||||||
# "foo bar" (1)
|
|
||||||
# "bar bleh" (1024)
|
|
||||||
# "foo bleh" (1)
|
|
||||||
# "ibabtu" (1)
|
|
||||||
# "ibabtu" (1)
|
|
||||||
def GetTestGroups():
|
|
||||||
objects = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("foo bleh"),NamedObject("ibabtu"),NamedObject("ibabtu")]
|
|
||||||
objects[1].size = 1024
|
|
||||||
matches = engine.getmatches(objects) #we should have 5 matches
|
|
||||||
groups = engine.get_groups(matches) #We should have 2 groups
|
|
||||||
for g in groups:
|
|
||||||
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
|
|
||||||
groups.sort(key=len, reverse=True) # We want the group with 3 members to be first.
|
|
||||||
return (objects,matches,groups)
|
|
||||||
|
|
||||||
class TCResultsEmpty(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.results = Results(data)
|
|
||||||
|
|
||||||
def test_apply_invalid_filter(self):
|
|
||||||
# If the applied filter is an invalid regexp, just ignore the filter.
|
|
||||||
self.results.apply_filter('[') # invalid
|
|
||||||
self.test_stat_line() # make sure that the stats line isn't saying we applied a '[' filter
|
|
||||||
|
|
||||||
def test_stat_line(self):
|
|
||||||
self.assertEqual("0 / 0 (0.00 B / 0.00 B) duplicates marked.",self.results.stat_line)
|
|
||||||
|
|
||||||
def test_groups(self):
|
|
||||||
self.assertEqual(0,len(self.results.groups))
|
|
||||||
|
|
||||||
def test_get_group_of_duplicate(self):
|
|
||||||
self.assert_(self.results.get_group_of_duplicate('foo') is None)
|
|
||||||
|
|
||||||
def test_save_to_xml(self):
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
self.results.save_to_xml(f)
|
|
||||||
f.seek(0)
|
|
||||||
doc = xml.dom.minidom.parse(f)
|
|
||||||
root = doc.documentElement
|
|
||||||
self.assertEqual('results',root.nodeName)
|
|
||||||
|
|
||||||
|
|
||||||
class TCResultsWithSomeGroups(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.results = Results(data)
|
|
||||||
self.objects,self.matches,self.groups = GetTestGroups()
|
|
||||||
self.results.groups = self.groups
|
|
||||||
|
|
||||||
def test_stat_line(self):
|
|
||||||
self.assertEqual("0 / 3 (0.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
|
|
||||||
def test_groups(self):
|
|
||||||
self.assertEqual(2,len(self.results.groups))
|
|
||||||
|
|
||||||
def test_get_group_of_duplicate(self):
|
|
||||||
for o in self.objects:
|
|
||||||
g = self.results.get_group_of_duplicate(o)
|
|
||||||
self.assert_(isinstance(g, engine.Group))
|
|
||||||
self.assert_(o in g)
|
|
||||||
self.assert_(self.results.get_group_of_duplicate(self.groups[0]) is None)
|
|
||||||
|
|
||||||
def test_remove_duplicates(self):
|
|
||||||
g1,g2 = self.results.groups
|
|
||||||
self.results.remove_duplicates([g1.dupes[0]])
|
|
||||||
self.assertEqual(2,len(g1))
|
|
||||||
self.assert_(g1 in self.results.groups)
|
|
||||||
self.results.remove_duplicates([g1.ref])
|
|
||||||
self.assertEqual(2,len(g1))
|
|
||||||
self.assert_(g1 in self.results.groups)
|
|
||||||
self.results.remove_duplicates([g1.dupes[0]])
|
|
||||||
self.assertEqual(0,len(g1))
|
|
||||||
self.assert_(g1 not in self.results.groups)
|
|
||||||
self.results.remove_duplicates([g2.dupes[0]])
|
|
||||||
self.assertEqual(0,len(g2))
|
|
||||||
self.assert_(g2 not in self.results.groups)
|
|
||||||
self.assertEqual(0,len(self.results.groups))
|
|
||||||
|
|
||||||
def test_remove_duplicates_with_ref_files(self):
|
|
||||||
g1,g2 = self.results.groups
|
|
||||||
self.objects[0].is_ref = True
|
|
||||||
self.objects[1].is_ref = True
|
|
||||||
self.results.remove_duplicates([self.objects[2]])
|
|
||||||
self.assertEqual(0,len(g1))
|
|
||||||
self.assert_(g1 not in self.results.groups)
|
|
||||||
|
|
||||||
def test_make_ref(self):
|
|
||||||
g = self.results.groups[0]
|
|
||||||
d = g.dupes[0]
|
|
||||||
self.results.make_ref(d)
|
|
||||||
self.assert_(d is g.ref)
|
|
||||||
|
|
||||||
def test_sort_groups(self):
|
|
||||||
self.results.make_ref(self.objects[1]) #We want to make the 1024 sized object to go ref.
|
|
||||||
g1,g2 = self.groups
|
|
||||||
self.results.sort_groups(2) #2 is the key for size
|
|
||||||
self.assert_(self.results.groups[0] is g2)
|
|
||||||
self.assert_(self.results.groups[1] is g1)
|
|
||||||
self.results.sort_groups(2,False)
|
|
||||||
self.assert_(self.results.groups[0] is g1)
|
|
||||||
self.assert_(self.results.groups[1] is g2)
|
|
||||||
|
|
||||||
def test_set_groups_when_sorted(self):
|
|
||||||
self.results.make_ref(self.objects[1]) #We want to make the 1024 sized object to go ref.
|
|
||||||
self.results.sort_groups(2)
|
|
||||||
objects,matches,groups = GetTestGroups()
|
|
||||||
g1,g2 = groups
|
|
||||||
g1.switch_ref(objects[1])
|
|
||||||
self.results.groups = groups
|
|
||||||
self.assert_(self.results.groups[0] is g2)
|
|
||||||
self.assert_(self.results.groups[1] is g1)
|
|
||||||
|
|
||||||
def test_get_dupe_list(self):
|
|
||||||
self.assertEqual([self.objects[1],self.objects[2],self.objects[4]],self.results.dupes)
|
|
||||||
|
|
||||||
def test_dupe_list_is_cached(self):
|
|
||||||
self.assert_(self.results.dupes is self.results.dupes)
|
|
||||||
|
|
||||||
def test_dupe_list_cache_is_invalidated_when_needed(self):
|
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
|
||||||
self.assertEqual([o2,o3,o5],self.results.dupes)
|
|
||||||
self.results.make_ref(o2)
|
|
||||||
self.assertEqual([o1,o3,o5],self.results.dupes)
|
|
||||||
objects,matches,groups = GetTestGroups()
|
|
||||||
o1,o2,o3,o4,o5 = objects
|
|
||||||
self.results.groups = groups
|
|
||||||
self.assertEqual([o2,o3,o5],self.results.dupes)
|
|
||||||
|
|
||||||
def test_dupe_list_sort(self):
|
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
|
||||||
o1.size = 5
|
|
||||||
o2.size = 4
|
|
||||||
o3.size = 3
|
|
||||||
o4.size = 2
|
|
||||||
o5.size = 1
|
|
||||||
self.results.sort_dupes(2)
|
|
||||||
self.assertEqual([o5,o3,o2],self.results.dupes)
|
|
||||||
self.results.sort_dupes(2,False)
|
|
||||||
self.assertEqual([o2,o3,o5],self.results.dupes)
|
|
||||||
|
|
||||||
def test_dupe_list_remember_sort(self):
|
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
|
||||||
o1.size = 5
|
|
||||||
o2.size = 4
|
|
||||||
o3.size = 3
|
|
||||||
o4.size = 2
|
|
||||||
o5.size = 1
|
|
||||||
self.results.sort_dupes(2)
|
|
||||||
self.results.make_ref(o2)
|
|
||||||
self.assertEqual([o5,o3,o1],self.results.dupes)
|
|
||||||
|
|
||||||
def test_dupe_list_sort_delta_values(self):
|
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
|
||||||
o1.size = 10
|
|
||||||
o2.size = 2 #-8
|
|
||||||
o3.size = 3 #-7
|
|
||||||
o4.size = 20
|
|
||||||
o5.size = 1 #-19
|
|
||||||
self.results.sort_dupes(2,delta=True)
|
|
||||||
self.assertEqual([o5,o2,o3],self.results.dupes)
|
|
||||||
|
|
||||||
def test_sort_empty_list(self):
|
|
||||||
#There was an infinite loop when sorting an empty list.
|
|
||||||
r = Results(data)
|
|
||||||
r.sort_dupes(0)
|
|
||||||
self.assertEqual([],r.dupes)
|
|
||||||
|
|
||||||
def test_dupe_list_update_on_remove_duplicates(self):
|
|
||||||
o1,o2,o3,o4,o5 = self.objects
|
|
||||||
self.assertEqual(3,len(self.results.dupes))
|
|
||||||
self.results.remove_duplicates([o2])
|
|
||||||
self.assertEqual(2,len(self.results.dupes))
|
|
||||||
|
|
||||||
|
|
||||||
class TCResultsMarkings(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.results = Results(data)
|
|
||||||
self.objects,self.matches,self.groups = GetTestGroups()
|
|
||||||
self.results.groups = self.groups
|
|
||||||
|
|
||||||
def test_stat_line(self):
|
|
||||||
self.assertEqual("0 / 3 (0.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
self.results.mark(self.objects[1])
|
|
||||||
self.assertEqual("1 / 3 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
self.results.mark_invert()
|
|
||||||
self.assertEqual("2 / 3 (2.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
self.results.mark_invert()
|
|
||||||
self.results.unmark(self.objects[1])
|
|
||||||
self.results.mark(self.objects[2])
|
|
||||||
self.results.mark(self.objects[4])
|
|
||||||
self.assertEqual("2 / 3 (2.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
self.results.mark(self.objects[0]) #this is a ref, it can't be counted
|
|
||||||
self.assertEqual("2 / 3 (2.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
self.results.groups = self.groups
|
|
||||||
self.assertEqual("0 / 3 (0.00 B / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
|
|
||||||
def test_with_ref_duplicate(self):
|
|
||||||
self.objects[1].is_ref = True
|
|
||||||
self.results.groups = self.groups
|
|
||||||
self.assert_(not self.results.mark(self.objects[1]))
|
|
||||||
self.results.mark(self.objects[2])
|
|
||||||
self.assertEqual("1 / 2 (1.00 B / 2.00 B) duplicates marked.",self.results.stat_line)
|
|
||||||
|
|
||||||
def test_perform_on_marked(self):
|
|
||||||
def log_object(o):
|
|
||||||
log.append(o)
|
|
||||||
return True
|
|
||||||
|
|
||||||
log = []
|
|
||||||
self.results.mark_all()
|
|
||||||
self.results.perform_on_marked(log_object,False)
|
|
||||||
self.assert_(self.objects[1] in log)
|
|
||||||
self.assert_(self.objects[2] in log)
|
|
||||||
self.assert_(self.objects[4] in log)
|
|
||||||
self.assertEqual(3,len(log))
|
|
||||||
log = []
|
|
||||||
self.results.mark_none()
|
|
||||||
self.results.mark(self.objects[4])
|
|
||||||
self.results.perform_on_marked(log_object,True)
|
|
||||||
self.assertEqual(1,len(log))
|
|
||||||
self.assert_(self.objects[4] in log)
|
|
||||||
self.assertEqual(1,len(self.results.groups))
|
|
||||||
|
|
||||||
def test_perform_on_marked_with_problems(self):
|
|
||||||
def log_object(o):
|
|
||||||
log.append(o)
|
|
||||||
return o is not self.objects[1]
|
|
||||||
|
|
||||||
log = []
|
|
||||||
self.results.mark_all()
|
|
||||||
self.assert_(self.results.is_marked(self.objects[1]))
|
|
||||||
self.assertEqual(1,self.results.perform_on_marked(log_object, True))
|
|
||||||
self.assertEqual(3,len(log))
|
|
||||||
self.assertEqual(1,len(self.results.groups))
|
|
||||||
self.assertEqual(2,len(self.results.groups[0]))
|
|
||||||
self.assert_(self.objects[1] in self.results.groups[0])
|
|
||||||
self.assert_(not self.results.is_marked(self.objects[2]))
|
|
||||||
self.assert_(self.results.is_marked(self.objects[1]))
|
|
||||||
|
|
||||||
def test_perform_on_marked_with_ref(self):
|
|
||||||
def log_object(o):
|
|
||||||
log.append(o)
|
|
||||||
return True
|
|
||||||
|
|
||||||
log = []
|
|
||||||
self.objects[0].is_ref = True
|
|
||||||
self.objects[1].is_ref = True
|
|
||||||
self.results.mark_all()
|
|
||||||
self.results.perform_on_marked(log_object,True)
|
|
||||||
self.assert_(self.objects[1] not in log)
|
|
||||||
self.assert_(self.objects[2] in log)
|
|
||||||
self.assert_(self.objects[4] in log)
|
|
||||||
self.assertEqual(2,len(log))
|
|
||||||
self.assertEqual(0,len(self.results.groups))
|
|
||||||
|
|
||||||
def test_perform_on_marked_remove_objects_only_at_the_end(self):
|
|
||||||
def check_groups(o):
|
|
||||||
self.assertEqual(3,len(g1))
|
|
||||||
self.assertEqual(2,len(g2))
|
|
||||||
return True
|
|
||||||
|
|
||||||
g1,g2 = self.results.groups
|
|
||||||
self.results.mark_all()
|
|
||||||
self.results.perform_on_marked(check_groups,True)
|
|
||||||
self.assertEqual(0,len(g1))
|
|
||||||
self.assertEqual(0,len(g2))
|
|
||||||
self.assertEqual(0,len(self.results.groups))
|
|
||||||
|
|
||||||
def test_remove_duplicates(self):
|
|
||||||
g1 = self.results.groups[0]
|
|
||||||
g2 = self.results.groups[1]
|
|
||||||
self.results.mark(g1.dupes[0])
|
|
||||||
self.assertEqual("1 / 3 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
self.results.remove_duplicates([g1.dupes[1]])
|
|
||||||
self.assertEqual("1 / 2 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
self.results.remove_duplicates([g1.dupes[0]])
|
|
||||||
self.assertEqual("0 / 1 (0.00 B / 1.00 B) duplicates marked.",self.results.stat_line)
|
|
||||||
|
|
||||||
def test_make_ref(self):
|
|
||||||
g = self.results.groups[0]
|
|
||||||
d = g.dupes[0]
|
|
||||||
self.results.mark(d)
|
|
||||||
self.assertEqual("1 / 3 (1.00 KB / 1.01 KB) duplicates marked.",self.results.stat_line)
|
|
||||||
self.results.make_ref(d)
|
|
||||||
self.assertEqual("0 / 3 (0.00 B / 3.00 B) duplicates marked.",self.results.stat_line)
|
|
||||||
self.results.make_ref(d)
|
|
||||||
self.assertEqual("0 / 3 (0.00 B / 3.00 B) duplicates marked.",self.results.stat_line)
|
|
||||||
|
|
||||||
def test_SaveXML(self):
|
|
||||||
self.results.mark(self.objects[1])
|
|
||||||
self.results.mark_invert()
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
self.results.save_to_xml(f)
|
|
||||||
f.seek(0)
|
|
||||||
doc = xml.dom.minidom.parse(f)
|
|
||||||
root = doc.documentElement
|
|
||||||
g1,g2 = root.getElementsByTagName('group')
|
|
||||||
d1,d2,d3 = g1.getElementsByTagName('file')
|
|
||||||
self.assertEqual('n',d1.getAttributeNode('marked').nodeValue)
|
|
||||||
self.assertEqual('n',d2.getAttributeNode('marked').nodeValue)
|
|
||||||
self.assertEqual('y',d3.getAttributeNode('marked').nodeValue)
|
|
||||||
d1,d2 = g2.getElementsByTagName('file')
|
|
||||||
self.assertEqual('n',d1.getAttributeNode('marked').nodeValue)
|
|
||||||
self.assertEqual('y',d2.getAttributeNode('marked').nodeValue)
|
|
||||||
|
|
||||||
def test_LoadXML(self):
|
|
||||||
def get_file(path):
|
|
||||||
return [f for f in self.objects if str(f.path) == path][0]
|
|
||||||
|
|
||||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
|
||||||
self.results.mark(self.objects[1])
|
|
||||||
self.results.mark_invert()
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
self.results.save_to_xml(f)
|
|
||||||
f.seek(0)
|
|
||||||
r = Results(data)
|
|
||||||
r.load_from_xml(f,get_file)
|
|
||||||
self.assert_(not r.is_marked(self.objects[0]))
|
|
||||||
self.assert_(not r.is_marked(self.objects[1]))
|
|
||||||
self.assert_(r.is_marked(self.objects[2]))
|
|
||||||
self.assert_(not r.is_marked(self.objects[3]))
|
|
||||||
self.assert_(r.is_marked(self.objects[4]))
|
|
||||||
|
|
||||||
|
|
||||||
class TCResultsXML(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.results = Results(data)
|
|
||||||
self.objects, self.matches, self.groups = GetTestGroups()
|
|
||||||
self.results.groups = self.groups
|
|
||||||
|
|
||||||
def get_file(self, path): # use this as a callback for load_from_xml
|
|
||||||
return [o for o in self.objects if o.path == path][0]
|
|
||||||
|
|
||||||
def test_save_to_xml(self):
|
|
||||||
self.objects[0].is_ref = True
|
|
||||||
self.objects[0].words = [['foo','bar']]
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
self.results.save_to_xml(f)
|
|
||||||
f.seek(0)
|
|
||||||
doc = xml.dom.minidom.parse(f)
|
|
||||||
root = doc.documentElement
|
|
||||||
self.assertEqual('results',root.nodeName)
|
|
||||||
children = [c for c in root.childNodes if c.localName]
|
|
||||||
self.assertEqual(2,len(children))
|
|
||||||
self.assertEqual(2,len([c for c in children if c.nodeName == 'group']))
|
|
||||||
g1,g2 = children
|
|
||||||
children = [c for c in g1.childNodes if c.localName]
|
|
||||||
self.assertEqual(6,len(children))
|
|
||||||
self.assertEqual(3,len([c for c in children if c.nodeName == 'file']))
|
|
||||||
self.assertEqual(3,len([c for c in children if c.nodeName == 'match']))
|
|
||||||
d1,d2,d3 = [c for c in children if c.nodeName == 'file']
|
|
||||||
self.assertEqual(op.join('basepath','foo bar'),d1.getAttributeNode('path').nodeValue)
|
|
||||||
self.assertEqual(op.join('basepath','bar bleh'),d2.getAttributeNode('path').nodeValue)
|
|
||||||
self.assertEqual(op.join('basepath','foo bleh'),d3.getAttributeNode('path').nodeValue)
|
|
||||||
self.assertEqual('y',d1.getAttributeNode('is_ref').nodeValue)
|
|
||||||
self.assertEqual('n',d2.getAttributeNode('is_ref').nodeValue)
|
|
||||||
self.assertEqual('n',d3.getAttributeNode('is_ref').nodeValue)
|
|
||||||
self.assertEqual('foo,bar',d1.getAttributeNode('words').nodeValue)
|
|
||||||
self.assertEqual('bar,bleh',d2.getAttributeNode('words').nodeValue)
|
|
||||||
self.assertEqual('foo,bleh',d3.getAttributeNode('words').nodeValue)
|
|
||||||
children = [c for c in g2.childNodes if c.localName]
|
|
||||||
self.assertEqual(3,len(children))
|
|
||||||
self.assertEqual(2,len([c for c in children if c.nodeName == 'file']))
|
|
||||||
self.assertEqual(1,len([c for c in children if c.nodeName == 'match']))
|
|
||||||
d1,d2 = [c for c in children if c.nodeName == 'file']
|
|
||||||
self.assertEqual(op.join('basepath','ibabtu'),d1.getAttributeNode('path').nodeValue)
|
|
||||||
self.assertEqual(op.join('basepath','ibabtu'),d2.getAttributeNode('path').nodeValue)
|
|
||||||
self.assertEqual('n',d1.getAttributeNode('is_ref').nodeValue)
|
|
||||||
self.assertEqual('n',d2.getAttributeNode('is_ref').nodeValue)
|
|
||||||
self.assertEqual('ibabtu',d1.getAttributeNode('words').nodeValue)
|
|
||||||
self.assertEqual('ibabtu',d2.getAttributeNode('words').nodeValue)
|
|
||||||
|
|
||||||
def test_LoadXML(self):
|
|
||||||
def get_file(path):
|
|
||||||
return [f for f in self.objects if str(f.path) == path][0]
|
|
||||||
|
|
||||||
self.objects[0].is_ref = True
|
|
||||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
self.results.save_to_xml(f)
|
|
||||||
f.seek(0)
|
|
||||||
r = Results(data)
|
|
||||||
r.load_from_xml(f,get_file)
|
|
||||||
self.assertEqual(2,len(r.groups))
|
|
||||||
g1,g2 = r.groups
|
|
||||||
self.assertEqual(3,len(g1))
|
|
||||||
self.assert_(g1[0].is_ref)
|
|
||||||
self.assert_(not g1[1].is_ref)
|
|
||||||
self.assert_(not g1[2].is_ref)
|
|
||||||
self.assert_(g1[0] is self.objects[0])
|
|
||||||
self.assert_(g1[1] is self.objects[1])
|
|
||||||
self.assert_(g1[2] is self.objects[2])
|
|
||||||
self.assertEqual(['foo','bar'],g1[0].words)
|
|
||||||
self.assertEqual(['bar','bleh'],g1[1].words)
|
|
||||||
self.assertEqual(['foo','bleh'],g1[2].words)
|
|
||||||
self.assertEqual(2,len(g2))
|
|
||||||
self.assert_(not g2[0].is_ref)
|
|
||||||
self.assert_(not g2[1].is_ref)
|
|
||||||
self.assert_(g2[0] is self.objects[3])
|
|
||||||
self.assert_(g2[1] is self.objects[4])
|
|
||||||
self.assertEqual(['ibabtu'],g2[0].words)
|
|
||||||
self.assertEqual(['ibabtu'],g2[1].words)
|
|
||||||
|
|
||||||
def test_LoadXML_with_filename(self):
|
|
||||||
def get_file(path):
|
|
||||||
return [f for f in self.objects if str(f.path) == path][0]
|
|
||||||
|
|
||||||
filename = op.join(self.tmpdir(), 'dupeguru_results.xml')
|
|
||||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
|
||||||
self.results.save_to_xml(filename)
|
|
||||||
r = Results(data)
|
|
||||||
r.load_from_xml(filename,get_file)
|
|
||||||
self.assertEqual(2,len(r.groups))
|
|
||||||
|
|
||||||
def test_LoadXML_with_some_files_that_dont_exist_anymore(self):
|
|
||||||
def get_file(path):
|
|
||||||
if path.endswith('ibabtu 2'):
|
|
||||||
return None
|
|
||||||
return [f for f in self.objects if str(f.path) == path][0]
|
|
||||||
|
|
||||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
self.results.save_to_xml(f)
|
|
||||||
f.seek(0)
|
|
||||||
r = Results(data)
|
|
||||||
r.load_from_xml(f,get_file)
|
|
||||||
self.assertEqual(1,len(r.groups))
|
|
||||||
self.assertEqual(3,len(r.groups[0]))
|
|
||||||
|
|
||||||
def test_LoadXML_missing_attributes_and_bogus_elements(self):
|
|
||||||
def get_file(path):
|
|
||||||
return [f for f in self.objects if str(f.path) == path][0]
|
|
||||||
|
|
||||||
doc = xml.dom.minidom.Document()
|
|
||||||
root = doc.appendChild(doc.createElement('foobar')) #The root element shouldn't matter, really.
|
|
||||||
group_node = root.appendChild(doc.createElement('group'))
|
|
||||||
dupe_node = group_node.appendChild(doc.createElement('file')) #Perfectly correct file
|
|
||||||
dupe_node.setAttribute('path',op.join('basepath','foo bar'))
|
|
||||||
dupe_node.setAttribute('is_ref','y')
|
|
||||||
dupe_node.setAttribute('words','foo,bar')
|
|
||||||
dupe_node = group_node.appendChild(doc.createElement('file')) #is_ref missing, default to 'n'
|
|
||||||
dupe_node.setAttribute('path',op.join('basepath','foo bleh'))
|
|
||||||
dupe_node.setAttribute('words','foo,bleh')
|
|
||||||
dupe_node = group_node.appendChild(doc.createElement('file')) #words are missing, invalid.
|
|
||||||
dupe_node.setAttribute('path',op.join('basepath','bar bleh'))
|
|
||||||
dupe_node = group_node.appendChild(doc.createElement('file')) #path is missing, invalid.
|
|
||||||
dupe_node.setAttribute('words','foo,bleh')
|
|
||||||
dupe_node = group_node.appendChild(doc.createElement('foobar')) #Invalid element name
|
|
||||||
dupe_node.setAttribute('path',op.join('basepath','bar bleh'))
|
|
||||||
dupe_node.setAttribute('is_ref','y')
|
|
||||||
dupe_node.setAttribute('words','bar,bleh')
|
|
||||||
match_node = group_node.appendChild(doc.createElement('match')) # match pointing to a bad index
|
|
||||||
match_node.setAttribute('first', '42')
|
|
||||||
match_node.setAttribute('second', '45')
|
|
||||||
match_node = group_node.appendChild(doc.createElement('match')) # match with missing attrs
|
|
||||||
match_node = group_node.appendChild(doc.createElement('match')) # match with non-int values
|
|
||||||
match_node.setAttribute('first', 'foo')
|
|
||||||
match_node.setAttribute('second', 'bar')
|
|
||||||
match_node.setAttribute('percentage', 'baz')
|
|
||||||
group_node = root.appendChild(doc.createElement('foobar')) #invalid group
|
|
||||||
group_node = root.appendChild(doc.createElement('group')) #empty group
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
doc.writexml(f,'\t','\t','\n',encoding='utf-8')
|
|
||||||
f.seek(0)
|
|
||||||
r = Results(data)
|
|
||||||
r.load_from_xml(f,get_file)
|
|
||||||
self.assertEqual(1,len(r.groups))
|
|
||||||
self.assertEqual(2,len(r.groups[0]))
|
|
||||||
|
|
||||||
def test_xml_non_ascii(self):
|
|
||||||
def get_file(path):
|
|
||||||
if path == op.join('basepath',u'\xe9foo bar'):
|
|
||||||
return objects[0]
|
|
||||||
if path == op.join('basepath',u'bar bleh'):
|
|
||||||
return objects[1]
|
|
||||||
|
|
||||||
objects = [NamedObject(u"\xe9foo bar",True),NamedObject("bar bleh",True)]
|
|
||||||
matches = engine.getmatches(objects) #we should have 5 matches
|
|
||||||
groups = engine.get_groups(matches) #We should have 2 groups
|
|
||||||
for g in groups:
|
|
||||||
g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is
|
|
||||||
results = Results(data)
|
|
||||||
results.groups = groups
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
results.save_to_xml(f)
|
|
||||||
f.seek(0)
|
|
||||||
r = Results(data)
|
|
||||||
r.load_from_xml(f,get_file)
|
|
||||||
g = r.groups[0]
|
|
||||||
self.assertEqual(u"\xe9foo bar",g[0].name)
|
|
||||||
self.assertEqual(['efoo','bar'],g[0].words)
|
|
||||||
|
|
||||||
def test_load_invalid_xml(self):
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
f.write('<this is invalid')
|
|
||||||
f.seek(0)
|
|
||||||
r = Results(data)
|
|
||||||
r.load_from_xml(f,None)
|
|
||||||
self.assertEqual(0,len(r.groups))
|
|
||||||
|
|
||||||
def test_load_non_existant_xml(self):
|
|
||||||
r = Results(data)
|
|
||||||
try:
|
|
||||||
r.load_from_xml('does_not_exist.xml', None)
|
|
||||||
except IOError:
|
|
||||||
self.fail()
|
|
||||||
self.assertEqual(0,len(r.groups))
|
|
||||||
|
|
||||||
def test_remember_match_percentage(self):
|
|
||||||
group = self.groups[0]
|
|
||||||
d1, d2, d3 = group
|
|
||||||
fake_matches = set()
|
|
||||||
fake_matches.add(engine.Match(d1, d2, 42))
|
|
||||||
fake_matches.add(engine.Match(d1, d3, 43))
|
|
||||||
fake_matches.add(engine.Match(d2, d3, 46))
|
|
||||||
group.matches = fake_matches
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
results = self.results
|
|
||||||
results.save_to_xml(f)
|
|
||||||
f.seek(0)
|
|
||||||
results = Results(data)
|
|
||||||
results.load_from_xml(f, self.get_file)
|
|
||||||
group = results.groups[0]
|
|
||||||
d1, d2, d3 = group
|
|
||||||
match = group.get_match_of(d2) #d1 - d2
|
|
||||||
self.assertEqual(42, match[2])
|
|
||||||
match = group.get_match_of(d3) #d1 - d3
|
|
||||||
self.assertEqual(43, match[2])
|
|
||||||
group.switch_ref(d2)
|
|
||||||
match = group.get_match_of(d3) #d2 - d3
|
|
||||||
self.assertEqual(46, match[2])
|
|
||||||
|
|
||||||
def test_save_and_load(self):
|
|
||||||
# previously, when reloading matches, they wouldn't be reloaded as namedtuples
|
|
||||||
f = StringIO.StringIO()
|
|
||||||
self.results.save_to_xml(f)
|
|
||||||
f.seek(0)
|
|
||||||
self.results.load_from_xml(f, self.get_file)
|
|
||||||
first(self.results.groups[0].matches).percentage
|
|
||||||
|
|
||||||
|
|
||||||
class TCResultsFilter(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.results = Results(data)
|
|
||||||
self.objects, self.matches, self.groups = GetTestGroups()
|
|
||||||
self.results.groups = self.groups
|
|
||||||
self.results.apply_filter(r'foo')
|
|
||||||
|
|
||||||
def test_groups(self):
|
|
||||||
self.assertEqual(1, len(self.results.groups))
|
|
||||||
self.assert_(self.results.groups[0] is self.groups[0])
|
|
||||||
|
|
||||||
def test_dupes(self):
|
|
||||||
# There are 2 objects matching. The first one is ref. Only the 3rd one is supposed to be in dupes.
|
|
||||||
self.assertEqual(1, len(self.results.dupes))
|
|
||||||
self.assert_(self.results.dupes[0] is self.objects[2])
|
|
||||||
|
|
||||||
def test_cancel_filter(self):
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
self.assertEqual(3, len(self.results.dupes))
|
|
||||||
self.assertEqual(2, len(self.results.groups))
|
|
||||||
|
|
||||||
def test_dupes_reconstructed_filtered(self):
|
|
||||||
# make_ref resets self.__dupes to None. When it's reconstructed, we want it filtered
|
|
||||||
dupe = self.results.dupes[0] #3rd object
|
|
||||||
self.results.make_ref(dupe)
|
|
||||||
self.assertEqual(1, len(self.results.dupes))
|
|
||||||
self.assert_(self.results.dupes[0] is self.objects[0])
|
|
||||||
|
|
||||||
def test_include_ref_dupes_in_filter(self):
|
|
||||||
# When only the ref of a group match the filter, include it in the group
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
self.results.apply_filter(r'foo bar')
|
|
||||||
self.assertEqual(1, len(self.results.groups))
|
|
||||||
self.assertEqual(0, len(self.results.dupes))
|
|
||||||
|
|
||||||
def test_filters_build_on_one_another(self):
|
|
||||||
self.results.apply_filter(r'bar')
|
|
||||||
self.assertEqual(1, len(self.results.groups))
|
|
||||||
self.assertEqual(0, len(self.results.dupes))
|
|
||||||
|
|
||||||
def test_stat_line(self):
|
|
||||||
expected = '0 / 1 (0.00 B / 1.00 B) duplicates marked. filter: foo'
|
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
|
||||||
self.results.apply_filter(r'bar')
|
|
||||||
expected = '0 / 0 (0.00 B / 0.00 B) duplicates marked. filter: foo --> bar'
|
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
expected = '0 / 3 (0.00 B / 1.01 KB) duplicates marked.'
|
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
|
||||||
|
|
||||||
def test_mark_count_is_filtered_as_well(self):
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
# We don't want to perform mark_all() because we want the mark list to contain objects
|
|
||||||
for dupe in self.results.dupes:
|
|
||||||
self.results.mark(dupe)
|
|
||||||
self.results.apply_filter(r'foo')
|
|
||||||
expected = '1 / 1 (1.00 B / 1.00 B) duplicates marked. filter: foo'
|
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
|
||||||
|
|
||||||
def test_sort_groups(self):
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
self.results.make_ref(self.objects[1]) # to have the 1024 b obkect as ref
|
|
||||||
g1,g2 = self.groups
|
|
||||||
self.results.apply_filter('a') # Matches both group
|
|
||||||
self.results.sort_groups(2) #2 is the key for size
|
|
||||||
self.assert_(self.results.groups[0] is g2)
|
|
||||||
self.assert_(self.results.groups[1] is g1)
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
self.assert_(self.results.groups[0] is g2)
|
|
||||||
self.assert_(self.results.groups[1] is g1)
|
|
||||||
self.results.sort_groups(2, False)
|
|
||||||
self.results.apply_filter('a')
|
|
||||||
self.assert_(self.results.groups[1] is g2)
|
|
||||||
self.assert_(self.results.groups[0] is g1)
|
|
||||||
|
|
||||||
def test_set_group(self):
|
|
||||||
#We want the new group to be filtered
|
|
||||||
self.objects, self.matches, self.groups = GetTestGroups()
|
|
||||||
self.results.groups = self.groups
|
|
||||||
self.assertEqual(1, len(self.results.groups))
|
|
||||||
self.assert_(self.results.groups[0] is self.groups[0])
|
|
||||||
|
|
||||||
def test_load_cancels_filter(self):
|
|
||||||
def get_file(path):
|
|
||||||
return [f for f in self.objects if str(f.path) == path][0]
|
|
||||||
|
|
||||||
filename = op.join(self.tmpdir(), 'dupeguru_results.xml')
|
|
||||||
self.objects[4].name = 'ibabtu 2' #we can't have 2 files with the same path
|
|
||||||
self.results.save_to_xml(filename)
|
|
||||||
r = Results(data)
|
|
||||||
r.apply_filter('foo')
|
|
||||||
r.load_from_xml(filename,get_file)
|
|
||||||
self.assertEqual(2,len(r.groups))
|
|
||||||
|
|
||||||
def test_remove_dupe(self):
|
|
||||||
self.results.remove_duplicates([self.results.dupes[0]])
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
self.assertEqual(2,len(self.results.groups))
|
|
||||||
self.assertEqual(2,len(self.results.dupes))
|
|
||||||
self.results.apply_filter('ibabtu')
|
|
||||||
self.results.remove_duplicates([self.results.dupes[0]])
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
self.assertEqual(1,len(self.results.groups))
|
|
||||||
self.assertEqual(1,len(self.results.dupes))
|
|
||||||
|
|
||||||
def test_filter_is_case_insensitive(self):
|
|
||||||
self.results.apply_filter(None)
|
|
||||||
self.results.apply_filter('FOO')
|
|
||||||
self.assertEqual(1, len(self.results.dupes))
|
|
||||||
|
|
||||||
def test_make_ref_on_filtered_out_doesnt_mess_stats(self):
|
|
||||||
# When filtered, a group containing filtered out dupes will display them as being reference.
|
|
||||||
# When calling make_ref on such a dupe, the total size and dupecount stats gets messed up
|
|
||||||
# because they are *not* counted in the stats in the first place.
|
|
||||||
g1, g2 = self.groups
|
|
||||||
bar_bleh = g1[1] # The "bar bleh" dupe is filtered out
|
|
||||||
self.results.make_ref(bar_bleh)
|
|
||||||
# Now the stats should display *2* markable dupes (instead of 1)
|
|
||||||
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked. filter: foo'
|
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
|
||||||
self.results.apply_filter(None) # Now let's make sure our unfiltered results aren't fucked up
|
|
||||||
expected = '0 / 3 (0.00 B / 3.00 B) duplicates marked.'
|
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
|
||||||
|
|
||||||
|
|
||||||
class TCResultsRefFile(TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.results = Results(data)
|
|
||||||
self.objects, self.matches, self.groups = GetTestGroups()
|
|
||||||
self.objects[0].is_ref = True
|
|
||||||
self.objects[1].is_ref = True
|
|
||||||
self.results.groups = self.groups
|
|
||||||
|
|
||||||
def test_stat_line(self):
|
|
||||||
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked.'
|
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
|
||||||
|
|
||||||
def test_make_ref(self):
|
|
||||||
d = self.results.groups[0].dupes[1] #non-ref
|
|
||||||
r = self.results.groups[0].ref
|
|
||||||
self.results.make_ref(d)
|
|
||||||
expected = '0 / 1 (0.00 B / 1.00 B) duplicates marked.'
|
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
|
||||||
self.results.make_ref(r)
|
|
||||||
expected = '0 / 2 (0.00 B / 2.00 B) duplicates marked.'
|
|
||||||
self.assertEqual(expected, self.results.stat_line)
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
WARNING ABOUT THE HS LICENSE AND PyQt
|
|
||||||
|
|
||||||
Although Qt is now LGPL licensed, PyQt still is dual licensed. Until Nokia buys Riverbank and
|
|
||||||
releases PyQt as LGPL, users of this part of the code (The PyQt-based GUI code) have to use the
|
|
||||||
GPL version of PyQt, unless they possess a commercial license to it.
|
|
||||||
|
|
||||||
There is no problem to this AS LONG AS YOU DON'T REDISTRIBUTE HS LICENSED CODE. The GPL license, from the point of view of the user, is very permissive. You can do WHATEVER you want with the GPLed version of PyQt, as long as you don't redistribute any of the code, or code dependent on it. When you do, the code you distribute has to be GPL compliant. The HS license is NOT, I repeat, NOT compliant with the GPL.
|
|
||||||
|
|
||||||
So, what does it all mean? You have no restriction on the usage of the PyQt-dependent-HS-licensed code, but unless you possess a commercial PyQt license, Hardcoded Software (or anyone) cannot accept any contribution from you for this part of the code.
|
|
||||||
|
|
||||||
Note that this only affects the PyQt dependent code, and not any other part of HS licensed code (if it has "import PyQt4" in it, it's PyQt dependent code). For the rest of the code, the only restrictions that apply are the ones from the HS license.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-09
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from PyQt4.QtCore import Qt, QCoreApplication, SIGNAL
|
|
||||||
from PyQt4.QtGui import QDialog, QDialogButtonBox, QPixmap
|
|
||||||
|
|
||||||
from about_box_ui import Ui_AboutBox
|
|
||||||
|
|
||||||
class AboutBox(QDialog, Ui_AboutBox):
|
|
||||||
def __init__(self, parent, app):
|
|
||||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.MSWindowsFixedSizeDialogHint
|
|
||||||
QDialog.__init__(self, parent, flags)
|
|
||||||
self.app = app
|
|
||||||
self._setupUi()
|
|
||||||
|
|
||||||
self.connect(self.buttonBox, SIGNAL('clicked(QAbstractButton*)'), self.buttonClicked)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
|
||||||
self.setupUi(self)
|
|
||||||
# Stuff that can't be done in the Designer
|
|
||||||
self.setWindowTitle(u"About %s" % QCoreApplication.instance().applicationName())
|
|
||||||
self.nameLabel.setText(QCoreApplication.instance().applicationName())
|
|
||||||
self.versionLabel.setText('Version ' + QCoreApplication.instance().applicationVersion())
|
|
||||||
self.logoLabel.setPixmap(QPixmap(':/%s_big' % self.app.LOGO_NAME))
|
|
||||||
self.registerButton = self.buttonBox.addButton("Register", QDialogButtonBox.ActionRole)
|
|
||||||
|
|
||||||
#--- Events
|
|
||||||
def buttonClicked(self, button):
|
|
||||||
if button is self.registerButton:
|
|
||||||
self.app.ask_for_reg_code()
|
|
||||||
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>AboutBox</class>
|
|
||||||
<widget class="QDialog" name="AboutBox">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>400</width>
|
|
||||||
<height>190</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>About dupeGuru</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="logoLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string/>
|
|
||||||
</property>
|
|
||||||
<property name="pixmap">
|
|
||||||
<pixmap resource="dg.qrc">:/logo_me_big</pixmap>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="nameLabel">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>dupeGuru</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="versionLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Version</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>Copyright Hardcoded Software 2009</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Registered To:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="registeredEmailLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>UNREGISTERED</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QDialogButtonBox" name="buttonBox">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="standardButtons">
|
|
||||||
<set>QDialogButtonBox::Ok</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources>
|
|
||||||
<include location="dg.qrc"/>
|
|
||||||
</resources>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>accepted()</signal>
|
|
||||||
<receiver>AboutBox</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>248</x>
|
|
||||||
<y>254</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>157</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>buttonBox</sender>
|
|
||||||
<signal>rejected()</signal>
|
|
||||||
<receiver>AboutBox</receiver>
|
|
||||||
<slot>reject()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>316</x>
|
|
||||||
<y>260</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>286</x>
|
|
||||||
<y>274</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
||||||
259
base/qt/app.py
259
base/qt/app.py
@@ -1,259 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-04-25
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import os.path as op
|
|
||||||
|
|
||||||
from PyQt4.QtCore import Qt, QTimer, QObject, QCoreApplication, QUrl, SIGNAL
|
|
||||||
from PyQt4.QtGui import QProgressDialog, QDesktopServices, QFileDialog, QDialog, QMessageBox
|
|
||||||
|
|
||||||
from hsutil import job
|
|
||||||
from hsutil.reg import RegistrationRequired
|
|
||||||
|
|
||||||
from dupeguru import fs
|
|
||||||
from dupeguru.app import (DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY,
|
|
||||||
JOB_DELETE)
|
|
||||||
|
|
||||||
from qtlib.progress import Progress
|
|
||||||
|
|
||||||
from . import platform
|
|
||||||
|
|
||||||
from .main_window import MainWindow
|
|
||||||
from .directories_dialog import DirectoriesDialog
|
|
||||||
from .about_box import AboutBox
|
|
||||||
from .reg import Registration
|
|
||||||
|
|
||||||
JOBID2TITLE = {
|
|
||||||
JOB_SCAN: "Scanning for duplicates",
|
|
||||||
JOB_LOAD: "Loading",
|
|
||||||
JOB_MOVE: "Moving",
|
|
||||||
JOB_COPY: "Copying",
|
|
||||||
JOB_DELETE: "Sending files to the recycle bin",
|
|
||||||
}
|
|
||||||
|
|
||||||
def demo_method(method):
|
|
||||||
def wrapper(self, *args, **kwargs):
|
|
||||||
try:
|
|
||||||
return method(self, *args, **kwargs)
|
|
||||||
except RegistrationRequired:
|
|
||||||
msg = "The demo version of dupeGuru only allows 10 actions (delete/move/copy) per session."
|
|
||||||
QMessageBox.information(self.main_window, 'Demo', msg)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
class DupeGuru(DupeGuruBase, QObject):
|
|
||||||
LOGO_NAME = '<replace this>'
|
|
||||||
NAME = '<replace this>'
|
|
||||||
DELTA_COLUMNS = frozenset()
|
|
||||||
|
|
||||||
def __init__(self, data_module, appid):
|
|
||||||
appdata = unicode(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
|
|
||||||
if not op.exists(appdata):
|
|
||||||
os.makedirs(appdata)
|
|
||||||
# For basicConfig() to work, we have to be sure that no logging has taken place before this call.
|
|
||||||
logging.basicConfig(filename=op.join(appdata, 'debug.log'), level=logging.WARNING)
|
|
||||||
DupeGuruBase.__init__(self, data_module, appdata, appid)
|
|
||||||
QObject.__init__(self)
|
|
||||||
self._setup()
|
|
||||||
|
|
||||||
#--- Private
|
|
||||||
def _setup(self):
|
|
||||||
self.selected_dupe = None
|
|
||||||
self.prefs = self._create_preferences()
|
|
||||||
self.prefs.load()
|
|
||||||
self._update_options()
|
|
||||||
self.main_window = self._create_main_window()
|
|
||||||
self._progress = Progress(self.main_window)
|
|
||||||
self.directories_dialog = DirectoriesDialog(self.main_window, self)
|
|
||||||
self.details_dialog = self._create_details_dialog(self.main_window)
|
|
||||||
self.preferences_dialog = self._create_preferences_dialog(self.main_window)
|
|
||||||
self.about_box = AboutBox(self.main_window, self)
|
|
||||||
|
|
||||||
self.reg = Registration(self)
|
|
||||||
self.set_registration(self.prefs.registration_code, self.prefs.registration_email)
|
|
||||||
if not self.registered:
|
|
||||||
# The timer scheme is because if the nag is not shown before the application is
|
|
||||||
# completely initialized, the nag will be shown before the app shows up in the task bar
|
|
||||||
# In some circumstances, the nag is hidden by other window, which may make the user think
|
|
||||||
# that the application haven't launched.
|
|
||||||
self._nagTimer = QTimer()
|
|
||||||
self.connect(self._nagTimer, SIGNAL('timeout()'), self.mustShowNag)
|
|
||||||
self._nagTimer.start(0)
|
|
||||||
self.main_window.show()
|
|
||||||
self.load()
|
|
||||||
|
|
||||||
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
|
|
||||||
self.connect(self._progress, SIGNAL('finished(QString)'), self.job_finished)
|
|
||||||
|
|
||||||
def _setup_as_registered(self):
|
|
||||||
self.prefs.registration_code = self.registration_code
|
|
||||||
self.prefs.registration_email = self.registration_email
|
|
||||||
self.main_window.actionRegister.setVisible(False)
|
|
||||||
self.about_box.registerButton.hide()
|
|
||||||
self.about_box.registeredEmailLabel.setText(self.prefs.registration_email)
|
|
||||||
|
|
||||||
def _update_options(self):
|
|
||||||
self.scanner.mix_file_kind = self.prefs.mix_file_kind
|
|
||||||
self.options['escape_filter_regexp'] = self.prefs.use_regexp
|
|
||||||
self.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
|
|
||||||
|
|
||||||
#--- Virtual
|
|
||||||
def _create_details_dialog(self, parent):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _create_main_window(self):
|
|
||||||
return MainWindow(app=self)
|
|
||||||
|
|
||||||
def _create_preferences(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def _create_preferences_dialog(self, parent):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
#--- Override
|
|
||||||
@staticmethod
|
|
||||||
def _recycle_dupe(dupe):
|
|
||||||
platform.recycle_file(dupe.path)
|
|
||||||
|
|
||||||
def _start_job(self, jobid, func):
|
|
||||||
title = JOBID2TITLE[jobid]
|
|
||||||
try:
|
|
||||||
j = self._progress.create_job()
|
|
||||||
self._progress.run(jobid, title, func, args=(j, ))
|
|
||||||
except job.JobInProgressError:
|
|
||||||
msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."
|
|
||||||
QMessageBox.information(self.main_window, 'Action in progress', msg)
|
|
||||||
|
|
||||||
#--- Public
|
|
||||||
def add_dupes_to_ignore_list(self, duplicates):
|
|
||||||
for dupe in duplicates:
|
|
||||||
self.add_to_ignore_list(dupe)
|
|
||||||
self.remove_duplicates(duplicates)
|
|
||||||
|
|
||||||
def apply_filter(self, filter):
|
|
||||||
DupeGuruBase.apply_filter(self, filter)
|
|
||||||
self.emit(SIGNAL('resultsChanged()'))
|
|
||||||
|
|
||||||
def ask_for_reg_code(self):
|
|
||||||
if self.reg.ask_for_code():
|
|
||||||
#XXX bug???
|
|
||||||
self._setup_ui_as_registered()
|
|
||||||
|
|
||||||
@demo_method
|
|
||||||
def copy_or_move_marked(self, copy):
|
|
||||||
opname = 'copy' if copy else 'move'
|
|
||||||
title = "Select a directory to {0} marked files to".format(opname)
|
|
||||||
flags = QFileDialog.ShowDirsOnly
|
|
||||||
destination = unicode(QFileDialog.getExistingDirectory(self.main_window, title, '', flags))
|
|
||||||
if not destination:
|
|
||||||
return
|
|
||||||
recreate_path = self.prefs.destination_type
|
|
||||||
DupeGuruBase.copy_or_move_marked(self, copy, destination, recreate_path)
|
|
||||||
|
|
||||||
delete_marked = demo_method(DupeGuruBase.delete_marked)
|
|
||||||
|
|
||||||
def make_reference(self, duplicates):
|
|
||||||
DupeGuruBase.make_reference(self, duplicates)
|
|
||||||
self.emit(SIGNAL('resultsChanged()'))
|
|
||||||
|
|
||||||
def mark_all(self):
|
|
||||||
self.results.mark_all()
|
|
||||||
self.emit(SIGNAL('dupeMarkingChanged()'))
|
|
||||||
|
|
||||||
def mark_invert(self):
|
|
||||||
self.results.mark_invert()
|
|
||||||
self.emit(SIGNAL('dupeMarkingChanged()'))
|
|
||||||
|
|
||||||
def mark_none(self):
|
|
||||||
self.results.mark_none()
|
|
||||||
self.emit(SIGNAL('dupeMarkingChanged()'))
|
|
||||||
|
|
||||||
def openDebugLog(self):
|
|
||||||
debugLogPath = op.join(self.appdata, 'debug.log')
|
|
||||||
url = QUrl.fromLocalFile(debugLogPath)
|
|
||||||
QDesktopServices.openUrl(url)
|
|
||||||
|
|
||||||
def open_selected(self):
|
|
||||||
if self.selected_dupe is None:
|
|
||||||
return
|
|
||||||
url = QUrl.fromLocalFile(unicode(self.selected_dupe.path))
|
|
||||||
QDesktopServices.openUrl(url)
|
|
||||||
|
|
||||||
def remove_duplicates(self, duplicates):
|
|
||||||
self.results.remove_duplicates(duplicates)
|
|
||||||
self.emit(SIGNAL('resultsChanged()'))
|
|
||||||
|
|
||||||
def remove_marked_duplicates(self):
|
|
||||||
marked = [d for d in self.results.dupes if self.results.is_marked(d)]
|
|
||||||
self.remove_duplicates(marked)
|
|
||||||
|
|
||||||
def rename_dupe(self, dupe, newname):
|
|
||||||
try:
|
|
||||||
dupe.move(dupe.parent, newname)
|
|
||||||
return True
|
|
||||||
except (IndexError, fs.FSError) as e:
|
|
||||||
logging.warning("dupeGuru Warning: %s" % unicode(e))
|
|
||||||
return False
|
|
||||||
|
|
||||||
def reveal_selected(self):
|
|
||||||
if self.selected_dupe is None:
|
|
||||||
return
|
|
||||||
url = QUrl.fromLocalFile(unicode(self.selected_dupe.path[:-1]))
|
|
||||||
QDesktopServices.openUrl(url)
|
|
||||||
|
|
||||||
def select_duplicate(self, dupe):
|
|
||||||
self.selected_dupe = dupe
|
|
||||||
self.emit(SIGNAL('duplicateSelected()'))
|
|
||||||
|
|
||||||
def show_about_box(self):
|
|
||||||
self.about_box.show()
|
|
||||||
|
|
||||||
def show_details(self):
|
|
||||||
self.details_dialog.show()
|
|
||||||
|
|
||||||
def show_directories(self):
|
|
||||||
self.directories_dialog.show()
|
|
||||||
|
|
||||||
def show_help(self):
|
|
||||||
url = QUrl.fromLocalFile(op.abspath('help/intro.htm'))
|
|
||||||
QDesktopServices.openUrl(url)
|
|
||||||
|
|
||||||
def show_preferences(self):
|
|
||||||
self.preferences_dialog.load()
|
|
||||||
result = self.preferences_dialog.exec_()
|
|
||||||
if result == QDialog.Accepted:
|
|
||||||
self.preferences_dialog.save()
|
|
||||||
self.prefs.save()
|
|
||||||
self._update_options()
|
|
||||||
|
|
||||||
def toggle_marking_for_dupes(self, dupes):
|
|
||||||
for dupe in dupes:
|
|
||||||
self.results.mark_toggle(dupe)
|
|
||||||
self.emit(SIGNAL('dupeMarkingChanged()'))
|
|
||||||
|
|
||||||
#--- Events
|
|
||||||
def application_will_terminate(self):
|
|
||||||
self.save()
|
|
||||||
self.save_ignore_list()
|
|
||||||
|
|
||||||
def mustShowNag(self):
|
|
||||||
self._nagTimer.stop() # must be shown only once
|
|
||||||
self.reg.show_nag()
|
|
||||||
|
|
||||||
def job_finished(self, jobid):
|
|
||||||
self.emit(SIGNAL('resultsChanged()'))
|
|
||||||
if jobid == JOB_LOAD:
|
|
||||||
self.emit(SIGNAL('directoriesChanged()'))
|
|
||||||
if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE) and self.last_op_error_count > 0:
|
|
||||||
msg = "{0} files could not be processed.".format(self.results.mark_count)
|
|
||||||
QMessageBox.warning(self.main_window, 'Warning', msg)
|
|
||||||
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<!DOCTYPE RCC><RCC version="1.0">
|
|
||||||
<qresource>
|
|
||||||
<file alias="details">images/details32.png</file>
|
|
||||||
<file alias="logo_pe">images/dgpe_logo_32.png</file>
|
|
||||||
<file alias="logo_pe_big">images/dgpe_logo_128.png</file>
|
|
||||||
<file alias="logo_me">images/dgme_logo_32.png</file>
|
|
||||||
<file alias="logo_me_big">images/dgme_logo_128.png</file>
|
|
||||||
<file alias="logo_se">images/dgse_logo_32.png</file>
|
|
||||||
<file alias="logo_se_big">images/dgse_logo_128.png</file>
|
|
||||||
<file alias="folder">images/folderwin32.png</file>
|
|
||||||
<file alias="gear">images/gear.png</file>
|
|
||||||
<file alias="preferences">images/preferences32.png</file>
|
|
||||||
<file alias="actions">images/actions32.png</file>
|
|
||||||
<file alias="delta">images/delta32.png</file>
|
|
||||||
<file alias="power_marker">images/power_marker32.png</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-04-25
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from PyQt4.QtCore import SIGNAL, Qt
|
|
||||||
from PyQt4.QtGui import QDialog, QFileDialog, QHeaderView
|
|
||||||
|
|
||||||
from . import platform
|
|
||||||
from .directories_dialog_ui import Ui_DirectoriesDialog
|
|
||||||
from .directories_model import DirectoriesModel, DirectoriesDelegate
|
|
||||||
|
|
||||||
class DirectoriesDialog(QDialog, Ui_DirectoriesDialog):
|
|
||||||
def __init__(self, parent, app):
|
|
||||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
|
|
||||||
QDialog.__init__(self, parent, flags)
|
|
||||||
self.app = app
|
|
||||||
self.lastAddedFolder = platform.INITIAL_FOLDER_IN_DIALOGS
|
|
||||||
self._setupUi()
|
|
||||||
self._updateRemoveButton()
|
|
||||||
|
|
||||||
self.connect(self.doneButton, SIGNAL('clicked()'), self.doneButtonClicked)
|
|
||||||
self.connect(self.addButton, SIGNAL('clicked()'), self.addButtonClicked)
|
|
||||||
self.connect(self.removeButton, SIGNAL('clicked()'), self.removeButtonClicked)
|
|
||||||
self.connect(self.treeView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
|
|
||||||
self.connect(self.app, SIGNAL('directoriesChanged()'), self.directoriesChanged)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
|
||||||
self.setupUi(self)
|
|
||||||
# Stuff that can't be done in the Designer
|
|
||||||
self.directoriesModel = DirectoriesModel(self.app)
|
|
||||||
self.directoriesDelegate = DirectoriesDelegate()
|
|
||||||
self.treeView.setItemDelegate(self.directoriesDelegate)
|
|
||||||
self.treeView.setModel(self.directoriesModel)
|
|
||||||
|
|
||||||
header = self.treeView.header()
|
|
||||||
header.setStretchLastSection(False)
|
|
||||||
header.setResizeMode(0, QHeaderView.Stretch)
|
|
||||||
header.setResizeMode(1, QHeaderView.Fixed)
|
|
||||||
header.resizeSection(1, 100)
|
|
||||||
|
|
||||||
def _updateRemoveButton(self):
|
|
||||||
indexes = self.treeView.selectedIndexes()
|
|
||||||
if not indexes:
|
|
||||||
self.removeButton.setEnabled(False)
|
|
||||||
return
|
|
||||||
self.removeButton.setEnabled(True)
|
|
||||||
index = indexes[0]
|
|
||||||
node = index.internalPointer()
|
|
||||||
# label = 'Remove' if node.parent is None else 'Exclude'
|
|
||||||
|
|
||||||
def addButtonClicked(self):
|
|
||||||
title = u"Select a directory to add to the scanning list"
|
|
||||||
flags = QFileDialog.ShowDirsOnly
|
|
||||||
dirpath = unicode(QFileDialog.getExistingDirectory(self, title, self.lastAddedFolder, flags))
|
|
||||||
if not dirpath:
|
|
||||||
return
|
|
||||||
self.lastAddedFolder = dirpath
|
|
||||||
self.app.add_directory(dirpath)
|
|
||||||
self.directoriesModel.reset()
|
|
||||||
|
|
||||||
def directoriesChanged(self):
|
|
||||||
self.directoriesModel.reset()
|
|
||||||
|
|
||||||
def doneButtonClicked(self):
|
|
||||||
self.hide()
|
|
||||||
|
|
||||||
def removeButtonClicked(self):
|
|
||||||
indexes = self.treeView.selectedIndexes()
|
|
||||||
if not indexes:
|
|
||||||
return
|
|
||||||
index = indexes[0]
|
|
||||||
node = index.internalPointer()
|
|
||||||
if node.parent is None:
|
|
||||||
row = index.row()
|
|
||||||
del self.app.directories[row]
|
|
||||||
self.directoriesModel.reset()
|
|
||||||
|
|
||||||
def selectionChanged(self, selected, deselected):
|
|
||||||
self._updateRemoveButton()
|
|
||||||
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>DirectoriesDialog</class>
|
|
||||||
<widget class="QDialog" name="DirectoriesDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>420</width>
|
|
||||||
<height>338</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Directories</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QTreeView" name="treeView">
|
|
||||||
<property name="editTriggers">
|
|
||||||
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
|
||||||
</property>
|
|
||||||
<property name="uniformRowHeights">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<attribute name="headerStretchLastSection">
|
|
||||||
<bool>false</bool>
|
|
||||||
</attribute>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="removeButton">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>91</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Remove</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="addButton">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>91</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Add</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer_2">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeType">
|
|
||||||
<enum>QSizePolicy::Fixed</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="doneButton">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>91</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="maximumSize">
|
|
||||||
<size>
|
|
||||||
<width>16777215</width>
|
|
||||||
<height>32</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Done</string>
|
|
||||||
</property>
|
|
||||||
<property name="default">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections/>
|
|
||||||
</ui>
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-04-25
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from PyQt4.QtCore import QModelIndex, Qt, QRect, QEvent, QPoint
|
|
||||||
from PyQt4.QtGui import QComboBox, QStyledItemDelegate, QMouseEvent, QApplication, QBrush
|
|
||||||
|
|
||||||
from qtlib.tree_model import TreeNode, TreeModel
|
|
||||||
|
|
||||||
HEADERS = ['Name', 'State']
|
|
||||||
STATES = ['Normal', 'Reference', 'Excluded']
|
|
||||||
|
|
||||||
class DirectoriesDelegate(QStyledItemDelegate):
|
|
||||||
def createEditor(self, parent, option, index):
|
|
||||||
editor = QComboBox(parent);
|
|
||||||
editor.addItems(STATES)
|
|
||||||
return editor
|
|
||||||
|
|
||||||
def setEditorData(self, editor, index):
|
|
||||||
value = index.model().data(index, Qt.EditRole)
|
|
||||||
editor.setCurrentIndex(value);
|
|
||||||
press = QMouseEvent(QEvent.MouseButtonPress, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
|
|
||||||
release = QMouseEvent(QEvent.MouseButtonRelease, QPoint(0, 0), Qt.LeftButton, Qt.LeftButton, Qt.NoModifier)
|
|
||||||
QApplication.sendEvent(editor, press)
|
|
||||||
QApplication.sendEvent(editor, release)
|
|
||||||
# editor.showPopup() # this causes a weird glitch. the ugly workaround is above.
|
|
||||||
|
|
||||||
def setModelData(self, editor, model, index):
|
|
||||||
value = editor.currentIndex()
|
|
||||||
model.setData(index, value, Qt.EditRole)
|
|
||||||
|
|
||||||
def updateEditorGeometry(self, editor, option, index):
|
|
||||||
editor.setGeometry(option.rect)
|
|
||||||
|
|
||||||
|
|
||||||
class DirectoryNode(TreeNode):
|
|
||||||
def __init__(self, model, parent, ref, row):
|
|
||||||
TreeNode.__init__(self, model, parent, row)
|
|
||||||
self.ref = ref
|
|
||||||
|
|
||||||
def _createNode(self, ref, row):
|
|
||||||
return DirectoryNode(self.model, self, ref, row)
|
|
||||||
|
|
||||||
def _getChildren(self):
|
|
||||||
return self.model._dirs.get_subfolders(self.ref)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
if self.parent is not None:
|
|
||||||
return self.ref[-1]
|
|
||||||
else:
|
|
||||||
return unicode(self.ref)
|
|
||||||
|
|
||||||
|
|
||||||
class DirectoriesModel(TreeModel):
|
|
||||||
def __init__(self, app):
|
|
||||||
self._dirs = app.directories
|
|
||||||
TreeModel.__init__(self)
|
|
||||||
|
|
||||||
def _createNode(self, ref, row):
|
|
||||||
return DirectoryNode(self, None, ref, row)
|
|
||||||
|
|
||||||
def _getChildren(self):
|
|
||||||
return self._dirs
|
|
||||||
|
|
||||||
def columnCount(self, parent):
|
|
||||||
return 2
|
|
||||||
|
|
||||||
def data(self, index, role):
|
|
||||||
if not index.isValid():
|
|
||||||
return None
|
|
||||||
node = index.internalPointer()
|
|
||||||
if role == Qt.DisplayRole:
|
|
||||||
if index.column() == 0:
|
|
||||||
return node.name
|
|
||||||
else:
|
|
||||||
return STATES[self._dirs.get_state(node.ref)]
|
|
||||||
elif role == Qt.EditRole and index.column() == 1:
|
|
||||||
return self._dirs.get_state(node.ref)
|
|
||||||
elif role == Qt.ForegroundRole:
|
|
||||||
state = self._dirs.get_state(node.ref)
|
|
||||||
if state == 1:
|
|
||||||
return QBrush(Qt.blue)
|
|
||||||
elif state == 2:
|
|
||||||
return QBrush(Qt.red)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def flags(self, index):
|
|
||||||
if not index.isValid():
|
|
||||||
return 0
|
|
||||||
result = Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
|
||||||
if index.column() == 1:
|
|
||||||
result |= Qt.ItemIsEditable
|
|
||||||
return result
|
|
||||||
|
|
||||||
def headerData(self, section, orientation, role):
|
|
||||||
if orientation == Qt.Horizontal:
|
|
||||||
if role == Qt.DisplayRole and section < len(HEADERS):
|
|
||||||
return HEADERS[section]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def setData(self, index, value, role):
|
|
||||||
if not index.isValid() or role != Qt.EditRole or index.column() != 1:
|
|
||||||
return False
|
|
||||||
node = index.internalPointer()
|
|
||||||
self._dirs.set_state(node.ref, value)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@@ -1,332 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-04-25
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from PyQt4.QtCore import Qt, QCoreApplication, QProcess, SIGNAL, QUrl
|
|
||||||
from PyQt4.QtGui import (QMainWindow, QMenu, QPixmap, QIcon, QToolButton, QLabel, QHeaderView,
|
|
||||||
QMessageBox, QInputDialog, QLineEdit, QItemSelectionModel, QDesktopServices)
|
|
||||||
|
|
||||||
from hsutil.misc import nonone
|
|
||||||
|
|
||||||
from dupeguru.app import NoScannableFileError, AllFilesAreRefError
|
|
||||||
|
|
||||||
import dg_rc
|
|
||||||
from main_window_ui import Ui_MainWindow
|
|
||||||
from results_model import ResultsDelegate, ResultsModel
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow, Ui_MainWindow):
|
|
||||||
def __init__(self, app):
|
|
||||||
QMainWindow.__init__(self, None)
|
|
||||||
self.app = app
|
|
||||||
self._last_filter = None
|
|
||||||
self._setupUi()
|
|
||||||
self.resultsDelegate = ResultsDelegate()
|
|
||||||
self.resultsModel = ResultsModel(self.app)
|
|
||||||
self.resultsView.setModel(self.resultsModel)
|
|
||||||
self.resultsView.setItemDelegate(self.resultsDelegate)
|
|
||||||
self._load_columns()
|
|
||||||
self._update_column_actions_status()
|
|
||||||
self.resultsView.expandAll()
|
|
||||||
self._update_status_line()
|
|
||||||
|
|
||||||
self.connect(self.app, SIGNAL('resultsChanged()'), self.resultsChanged)
|
|
||||||
self.connect(self.app, SIGNAL('dupeMarkingChanged()'), self.dupeMarkingChanged)
|
|
||||||
self.connect(self.actionQuit, SIGNAL('triggered()'), QCoreApplication.instance().quit)
|
|
||||||
self.connect(self.resultsView.selectionModel(), SIGNAL('selectionChanged(QItemSelection,QItemSelection)'), self.selectionChanged)
|
|
||||||
self.connect(self.menuColumns, SIGNAL('triggered(QAction*)'), self.columnToggled)
|
|
||||||
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
|
|
||||||
self.connect(self.resultsModel, SIGNAL('modelReset()'), self.resultsReset)
|
|
||||||
self.connect(self.resultsView, SIGNAL('doubleClicked()'), self.resultsDoubleClicked)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
|
||||||
self.setupUi(self)
|
|
||||||
# Stuff that can't be setup in the Designer
|
|
||||||
h = self.resultsView.header()
|
|
||||||
h.setHighlightSections(False)
|
|
||||||
h.setMovable(True)
|
|
||||||
h.setStretchLastSection(False)
|
|
||||||
h.setDefaultAlignment(Qt.AlignLeft)
|
|
||||||
|
|
||||||
self.setWindowTitle(QCoreApplication.instance().applicationName())
|
|
||||||
self.actionScan.setIcon(QIcon(QPixmap(':/%s' % self.app.LOGO_NAME)))
|
|
||||||
|
|
||||||
# Columns menu
|
|
||||||
menu = self.menuColumns
|
|
||||||
self._column_actions = []
|
|
||||||
for index, column in enumerate(self.app.data.COLUMNS):
|
|
||||||
action = menu.addAction(column['display'])
|
|
||||||
action.setCheckable(True)
|
|
||||||
action.column_index = index
|
|
||||||
self._column_actions.append(action)
|
|
||||||
menu.addSeparator()
|
|
||||||
action = menu.addAction("Reset to Defaults")
|
|
||||||
action.column_index = -1
|
|
||||||
|
|
||||||
# Action menu
|
|
||||||
actionMenu = QMenu('Actions', self.toolBar)
|
|
||||||
actionMenu.setIcon(QIcon(QPixmap(":/actions")))
|
|
||||||
actionMenu.addAction(self.actionDeleteMarked)
|
|
||||||
actionMenu.addAction(self.actionMoveMarked)
|
|
||||||
actionMenu.addAction(self.actionCopyMarked)
|
|
||||||
actionMenu.addAction(self.actionRemoveMarked)
|
|
||||||
actionMenu.addSeparator()
|
|
||||||
actionMenu.addAction(self.actionRemoveSelected)
|
|
||||||
actionMenu.addAction(self.actionIgnoreSelected)
|
|
||||||
actionMenu.addAction(self.actionMakeSelectedReference)
|
|
||||||
actionMenu.addSeparator()
|
|
||||||
actionMenu.addAction(self.actionOpenSelected)
|
|
||||||
actionMenu.addAction(self.actionRevealSelected)
|
|
||||||
actionMenu.addAction(self.actionRenameSelected)
|
|
||||||
self.actionActions.setMenu(actionMenu)
|
|
||||||
button = QToolButton(self.toolBar)
|
|
||||||
button.setDefaultAction(actionMenu.menuAction())
|
|
||||||
button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
|
||||||
self.actionsButton = button
|
|
||||||
self.toolBar.insertWidget(self.actionActions, button) # the action is a placeholder
|
|
||||||
self.toolBar.removeAction(self.actionActions)
|
|
||||||
|
|
||||||
self.statusLabel = QLabel(self)
|
|
||||||
self.statusbar.addPermanentWidget(self.statusLabel, 1)
|
|
||||||
|
|
||||||
#--- Private
|
|
||||||
def _confirm(self, title, msg, default_button=QMessageBox.Yes):
|
|
||||||
buttons = QMessageBox.Yes | QMessageBox.No
|
|
||||||
answer = QMessageBox.question(self, title, msg, buttons, default_button)
|
|
||||||
return answer == QMessageBox.Yes
|
|
||||||
|
|
||||||
def _load_columns(self):
|
|
||||||
h = self.resultsView.header()
|
|
||||||
h.setResizeMode(QHeaderView.Interactive)
|
|
||||||
prefs = self.app.prefs
|
|
||||||
attrs = zip(prefs.columns_width, prefs.columns_visible)
|
|
||||||
for index, (width, visible) in enumerate(attrs):
|
|
||||||
h.resizeSection(index, width)
|
|
||||||
h.setSectionHidden(index, not visible)
|
|
||||||
h.setResizeMode(0, QHeaderView.Stretch)
|
|
||||||
|
|
||||||
def _redraw_results(self):
|
|
||||||
# HACK. this is the only way I found to update the widget without reseting everything
|
|
||||||
self.resultsView.scroll(0, 1)
|
|
||||||
self.resultsView.scroll(0, -1)
|
|
||||||
|
|
||||||
def _save_columns(self):
|
|
||||||
h = self.resultsView.header()
|
|
||||||
widths = []
|
|
||||||
visible = []
|
|
||||||
for i in range(len(self.app.data.COLUMNS)):
|
|
||||||
widths.append(h.sectionSize(i))
|
|
||||||
visible.append(not h.isSectionHidden(i))
|
|
||||||
prefs = self.app.prefs
|
|
||||||
prefs.columns_width = widths
|
|
||||||
prefs.columns_visible = visible
|
|
||||||
prefs.save()
|
|
||||||
|
|
||||||
def _update_column_actions_status(self):
|
|
||||||
h = self.resultsView.header()
|
|
||||||
for action in self._column_actions:
|
|
||||||
colid = action.column_index
|
|
||||||
action.setChecked(not h.isSectionHidden(colid))
|
|
||||||
|
|
||||||
def _update_status_line(self):
|
|
||||||
self.statusLabel.setText(self.app.stat_line)
|
|
||||||
|
|
||||||
#--- Actions
|
|
||||||
def aboutTriggered(self):
|
|
||||||
self.app.show_about_box()
|
|
||||||
|
|
||||||
def actionsTriggered(self):
|
|
||||||
self.actionsButton.showMenu()
|
|
||||||
|
|
||||||
def addToIgnoreListTriggered(self):
|
|
||||||
dupes = self.resultsView.selectedDupes()
|
|
||||||
if not dupes:
|
|
||||||
return
|
|
||||||
title = "Add to Ignore List"
|
|
||||||
msg = "All selected {0} matches are going to be ignored in all subsequent scans. Continue?".format(len(dupes))
|
|
||||||
if self._confirm(title, msg):
|
|
||||||
self.app.add_dupes_to_ignore_list(dupes)
|
|
||||||
|
|
||||||
def applyFilterTriggered(self):
|
|
||||||
title = "Apply Filter"
|
|
||||||
msg = "Type the filter you want to apply on your results. See help for details."
|
|
||||||
text = nonone(self._last_filter, '[*]')
|
|
||||||
answer, ok = QInputDialog.getText(self, title, msg, QLineEdit.Normal, text)
|
|
||||||
if not ok:
|
|
||||||
return
|
|
||||||
answer = unicode(answer)
|
|
||||||
self.app.apply_filter(answer)
|
|
||||||
self._last_filter = answer
|
|
||||||
|
|
||||||
def cancelFilterTriggered(self):
|
|
||||||
self.app.apply_filter('')
|
|
||||||
|
|
||||||
def checkForUpdateTriggered(self):
|
|
||||||
QProcess.execute('updater.exe', ['/checknow'])
|
|
||||||
|
|
||||||
def clearIgnoreListTriggered(self):
|
|
||||||
title = "Clear Ignore List"
|
|
||||||
count = len(self.app.scanner.ignore_list)
|
|
||||||
if not count:
|
|
||||||
QMessageBox.information(self, title, "Nothing to clear.")
|
|
||||||
return
|
|
||||||
msg = "Do you really want to remove all {0} items from the ignore list?".format(count)
|
|
||||||
if self._confirm(title, msg, QMessageBox.No):
|
|
||||||
self.app.scanner.ignore_list.Clear()
|
|
||||||
QMessageBox.information(self, title, "Ignore list cleared.")
|
|
||||||
|
|
||||||
def copyTriggered(self):
|
|
||||||
self.app.copy_or_move_marked(True)
|
|
||||||
|
|
||||||
def deleteTriggered(self):
|
|
||||||
count = self.app.results.mark_count
|
|
||||||
if not count:
|
|
||||||
return
|
|
||||||
title = "Delete duplicates"
|
|
||||||
msg = "You are about to send {0} files to the recycle bin. Continue?".format(count)
|
|
||||||
if self._confirm(title, msg):
|
|
||||||
self.app.delete_marked()
|
|
||||||
|
|
||||||
def deltaTriggered(self):
|
|
||||||
self.resultsModel.delta = self.actionDelta.isChecked()
|
|
||||||
self._redraw_results()
|
|
||||||
|
|
||||||
def detailsTriggered(self):
|
|
||||||
self.app.show_details()
|
|
||||||
|
|
||||||
def directoriesTriggered(self):
|
|
||||||
self.app.show_directories()
|
|
||||||
|
|
||||||
def exportTriggered(self):
|
|
||||||
h = self.resultsView.header()
|
|
||||||
column_ids = []
|
|
||||||
for i in range(len(self.app.data.COLUMNS)):
|
|
||||||
if not h.isSectionHidden(i):
|
|
||||||
column_ids.append(str(i))
|
|
||||||
exported_path = self.app.export_to_xhtml(column_ids)
|
|
||||||
url = QUrl.fromLocalFile(exported_path)
|
|
||||||
QDesktopServices.openUrl(url)
|
|
||||||
|
|
||||||
def makeReferenceTriggered(self):
|
|
||||||
self.app.make_reference(self.resultsView.selectedDupes())
|
|
||||||
|
|
||||||
def markAllTriggered(self):
|
|
||||||
self.app.mark_all()
|
|
||||||
|
|
||||||
def markInvertTriggered(self):
|
|
||||||
self.app.mark_invert()
|
|
||||||
|
|
||||||
def markNoneTriggered(self):
|
|
||||||
self.app.mark_none()
|
|
||||||
|
|
||||||
def markSelectedTriggered(self):
|
|
||||||
dupes = self.resultsView.selectedDupes()
|
|
||||||
self.app.toggle_marking_for_dupes(dupes)
|
|
||||||
|
|
||||||
def moveTriggered(self):
|
|
||||||
self.app.copy_or_move_marked(False)
|
|
||||||
|
|
||||||
def openDebugLogTriggered(self):
|
|
||||||
self.app.openDebugLog()
|
|
||||||
|
|
||||||
def openTriggered(self):
|
|
||||||
self.app.open_selected()
|
|
||||||
|
|
||||||
def powerMarkerTriggered(self):
|
|
||||||
self.resultsModel.power_marker = self.actionPowerMarker.isChecked()
|
|
||||||
|
|
||||||
def preferencesTriggered(self):
|
|
||||||
self.app.show_preferences()
|
|
||||||
|
|
||||||
def registerTrigerred(self):
|
|
||||||
self.app.ask_for_reg_code()
|
|
||||||
|
|
||||||
def removeMarkedTriggered(self):
|
|
||||||
count = self.app.results.mark_count
|
|
||||||
if not count:
|
|
||||||
return
|
|
||||||
title = "Remove duplicates"
|
|
||||||
msg = "You are about to remove {0} files from results. Continue?".format(count)
|
|
||||||
if self._confirm(title, msg):
|
|
||||||
self.app.remove_marked_duplicates()
|
|
||||||
|
|
||||||
def removeSelectedTriggered(self):
|
|
||||||
dupes = self.resultsView.selectedDupes()
|
|
||||||
if not dupes:
|
|
||||||
return
|
|
||||||
title = "Remove duplicates"
|
|
||||||
msg = "You are about to remove {0} files from results. Continue?".format(len(dupes))
|
|
||||||
if self._confirm(title, msg):
|
|
||||||
self.app.remove_duplicates(dupes)
|
|
||||||
|
|
||||||
def renameTriggered(self):
|
|
||||||
self.resultsView.edit(self.resultsView.selectionModel().currentIndex())
|
|
||||||
|
|
||||||
def revealTriggered(self):
|
|
||||||
self.app.reveal_selected()
|
|
||||||
|
|
||||||
def scanTriggered(self):
|
|
||||||
title = "Start a new scan"
|
|
||||||
if len(self.app.results.groups) > 0:
|
|
||||||
msg = "Are you sure you want to start a new duplicate scan?"
|
|
||||||
if not self._confirm(title, msg):
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
self.app.start_scanning()
|
|
||||||
except NoScannableFileError:
|
|
||||||
msg = "The selected directories contain no scannable file."
|
|
||||||
QMessageBox.warning(self, title, msg)
|
|
||||||
self.app.show_directories()
|
|
||||||
except AllFilesAreRefError:
|
|
||||||
msg = "You cannot make a duplicate scan with only reference directories."
|
|
||||||
QMessageBox.warning(self, title, msg)
|
|
||||||
|
|
||||||
def showHelpTriggered(self):
|
|
||||||
self.app.show_help()
|
|
||||||
|
|
||||||
#--- Events
|
|
||||||
def application_will_terminate(self):
|
|
||||||
self._save_columns()
|
|
||||||
|
|
||||||
def columnToggled(self, action):
|
|
||||||
colid = action.column_index
|
|
||||||
if colid == -1:
|
|
||||||
self.app.prefs.reset_columns()
|
|
||||||
self._load_columns()
|
|
||||||
else:
|
|
||||||
h = self.resultsView.header()
|
|
||||||
h.setSectionHidden(colid, not h.isSectionHidden(colid))
|
|
||||||
self._update_column_actions_status()
|
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
|
||||||
self.actionActions.menu().exec_(event.globalPos())
|
|
||||||
|
|
||||||
def dupeMarkingChanged(self):
|
|
||||||
self._redraw_results()
|
|
||||||
self._update_status_line()
|
|
||||||
|
|
||||||
def resultsChanged(self):
|
|
||||||
self.resultsView.model().reset()
|
|
||||||
|
|
||||||
def resultsDoubleClicked(self):
|
|
||||||
self.app.open_selected()
|
|
||||||
|
|
||||||
def resultsReset(self):
|
|
||||||
self.resultsView.expandAll()
|
|
||||||
dupe = self.app.selected_dupe
|
|
||||||
if dupe is not None:
|
|
||||||
[modelIndex] = self.resultsModel.indexesForDupes([dupe])
|
|
||||||
if modelIndex.isValid():
|
|
||||||
flags = QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows
|
|
||||||
self.resultsView.selectionModel().setCurrentIndex(modelIndex, flags)
|
|
||||||
self._update_status_line()
|
|
||||||
|
|
||||||
def selectionChanged(self, selected, deselected):
|
|
||||||
index = self.resultsView.selectionModel().currentIndex()
|
|
||||||
dupe = index.internalPointer().dupe if index.isValid() else None
|
|
||||||
self.app.select_duplicate(dupe)
|
|
||||||
|
|
||||||
@@ -1,957 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>MainWindow</class>
|
|
||||||
<widget class="QMainWindow" name="MainWindow">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>630</width>
|
|
||||||
<height>514</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>dupeGuru</string>
|
|
||||||
</property>
|
|
||||||
<widget class="QWidget" name="centralwidget">
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
|
||||||
<property name="margin">
|
|
||||||
<number>0</number>
|
|
||||||
</property>
|
|
||||||
<item>
|
|
||||||
<widget class="ResultsView" name="resultsView">
|
|
||||||
<property name="selectionMode">
|
|
||||||
<enum>QAbstractItemView::ExtendedSelection</enum>
|
|
||||||
</property>
|
|
||||||
<property name="selectionBehavior">
|
|
||||||
<enum>QAbstractItemView::SelectRows</enum>
|
|
||||||
</property>
|
|
||||||
<property name="rootIsDecorated">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="uniformRowHeights">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="itemsExpandable">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="sortingEnabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="expandsOnDoubleClick">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<attribute name="headerStretchLastSection">
|
|
||||||
<bool>false</bool>
|
|
||||||
</attribute>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<widget class="QMenuBar" name="menubar">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>630</width>
|
|
||||||
<height>22</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<widget class="QMenu" name="menuColumns">
|
|
||||||
<property name="title">
|
|
||||||
<string>Columns</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QMenu" name="menuActions">
|
|
||||||
<property name="title">
|
|
||||||
<string>Actions</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionDeleteMarked"/>
|
|
||||||
<addaction name="actionMoveMarked"/>
|
|
||||||
<addaction name="actionCopyMarked"/>
|
|
||||||
<addaction name="actionRemoveMarked"/>
|
|
||||||
<addaction name="separator"/>
|
|
||||||
<addaction name="actionRemoveSelected"/>
|
|
||||||
<addaction name="actionIgnoreSelected"/>
|
|
||||||
<addaction name="actionMakeSelectedReference"/>
|
|
||||||
<addaction name="separator"/>
|
|
||||||
<addaction name="actionOpenSelected"/>
|
|
||||||
<addaction name="actionRevealSelected"/>
|
|
||||||
<addaction name="actionRenameSelected"/>
|
|
||||||
<addaction name="separator"/>
|
|
||||||
<addaction name="actionApplyFilter"/>
|
|
||||||
<addaction name="actionCancelFilter"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QMenu" name="menuMark">
|
|
||||||
<property name="title">
|
|
||||||
<string>Mark</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionMarkAll"/>
|
|
||||||
<addaction name="actionMarkNone"/>
|
|
||||||
<addaction name="actionInvertMarking"/>
|
|
||||||
<addaction name="actionMarkSelected"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QMenu" name="menuModes">
|
|
||||||
<property name="title">
|
|
||||||
<string>Modes</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionPowerMarker"/>
|
|
||||||
<addaction name="actionDelta"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QMenu" name="menuWindow">
|
|
||||||
<property name="title">
|
|
||||||
<string>Windows</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionDetails"/>
|
|
||||||
<addaction name="actionDirectories"/>
|
|
||||||
<addaction name="actionPreferences"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QMenu" name="menuHelp">
|
|
||||||
<property name="title">
|
|
||||||
<string>Help</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionShowHelp"/>
|
|
||||||
<addaction name="actionRegister"/>
|
|
||||||
<addaction name="actionCheckForUpdate"/>
|
|
||||||
<addaction name="actionOpenDebugLog"/>
|
|
||||||
<addaction name="actionAbout"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QMenu" name="menuFile">
|
|
||||||
<property name="title">
|
|
||||||
<string>File</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionScan"/>
|
|
||||||
<addaction name="separator"/>
|
|
||||||
<addaction name="actionExport"/>
|
|
||||||
<addaction name="actionClearIgnoreList"/>
|
|
||||||
<addaction name="separator"/>
|
|
||||||
<addaction name="actionQuit"/>
|
|
||||||
</widget>
|
|
||||||
<addaction name="menuFile"/>
|
|
||||||
<addaction name="menuMark"/>
|
|
||||||
<addaction name="menuActions"/>
|
|
||||||
<addaction name="menuColumns"/>
|
|
||||||
<addaction name="menuModes"/>
|
|
||||||
<addaction name="menuWindow"/>
|
|
||||||
<addaction name="menuHelp"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QToolBar" name="toolBar">
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>toolBar</string>
|
|
||||||
</property>
|
|
||||||
<property name="movable">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="toolButtonStyle">
|
|
||||||
<enum>Qt::ToolButtonTextUnderIcon</enum>
|
|
||||||
</property>
|
|
||||||
<property name="floatable">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<attribute name="toolBarArea">
|
|
||||||
<enum>TopToolBarArea</enum>
|
|
||||||
</attribute>
|
|
||||||
<attribute name="toolBarBreak">
|
|
||||||
<bool>false</bool>
|
|
||||||
</attribute>
|
|
||||||
<addaction name="actionScan"/>
|
|
||||||
<addaction name="actionActions"/>
|
|
||||||
<addaction name="actionDirectories"/>
|
|
||||||
<addaction name="actionDetails"/>
|
|
||||||
<addaction name="actionPreferences"/>
|
|
||||||
<addaction name="actionDelta"/>
|
|
||||||
<addaction name="actionPowerMarker"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QStatusBar" name="statusbar">
|
|
||||||
<property name="sizeGripEnabled">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<action name="actionScan">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="dg.qrc">
|
|
||||||
<normaloff>:/logo_pe</normaloff>:/logo_pe</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Start Scan</string>
|
|
||||||
</property>
|
|
||||||
<property name="toolTip">
|
|
||||||
<string>Start scanning for duplicates</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+S</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionDirectories">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="dg.qrc">
|
|
||||||
<normaloff>:/folder</normaloff>:/folder</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Directories</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+4</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionDetails">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="dg.qrc">
|
|
||||||
<normaloff>:/details</normaloff>:/details</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Details</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+3</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionActions">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="dg.qrc">
|
|
||||||
<normaloff>:/actions</normaloff>:/actions</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Actions</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionPreferences">
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="dg.qrc">
|
|
||||||
<normaloff>:/preferences</normaloff>:/preferences</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Preferences</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+5</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionDelta">
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="dg.qrc">
|
|
||||||
<normaloff>:/delta</normaloff>:/delta</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Delta Values</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+2</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionPowerMarker">
|
|
||||||
<property name="checkable">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
<property name="icon">
|
|
||||||
<iconset resource="dg.qrc">
|
|
||||||
<normaloff>:/power_marker</normaloff>:/power_marker</iconset>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Power Marker</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+1</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionDeleteMarked">
|
|
||||||
<property name="text">
|
|
||||||
<string>Send Marked to Recycle Bin</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+D</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionMoveMarked">
|
|
||||||
<property name="text">
|
|
||||||
<string>Move Marked to...</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+M</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionCopyMarked">
|
|
||||||
<property name="text">
|
|
||||||
<string>Copy Marked to...</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+Shift+M</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionRemoveMarked">
|
|
||||||
<property name="text">
|
|
||||||
<string>Remove Marked from Results</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+R</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionRemoveSelected">
|
|
||||||
<property name="text">
|
|
||||||
<string>Remove Selected from Results</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+Del</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionIgnoreSelected">
|
|
||||||
<property name="text">
|
|
||||||
<string>Add Selected to Ignore List</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+Shift+Del</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionMakeSelectedReference">
|
|
||||||
<property name="text">
|
|
||||||
<string>Make Selected Reference</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+Space</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionOpenSelected">
|
|
||||||
<property name="text">
|
|
||||||
<string>Open Selected with Default Application</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+O</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionRevealSelected">
|
|
||||||
<property name="text">
|
|
||||||
<string>Open Containing Folder of Selected</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+Shift+O</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionRenameSelected">
|
|
||||||
<property name="text">
|
|
||||||
<string>Rename Selected</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>F2</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionMarkAll">
|
|
||||||
<property name="text">
|
|
||||||
<string>Mark All</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+A</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionMarkNone">
|
|
||||||
<property name="text">
|
|
||||||
<string>Mark None</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+Shift+A</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionInvertMarking">
|
|
||||||
<property name="text">
|
|
||||||
<string>Invert Marking</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+Alt+A</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionMarkSelected">
|
|
||||||
<property name="text">
|
|
||||||
<string>Mark Selected</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionClearIgnoreList">
|
|
||||||
<property name="text">
|
|
||||||
<string>Clear Ignore List</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionQuit">
|
|
||||||
<property name="text">
|
|
||||||
<string>Quit</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+Q</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionApplyFilter">
|
|
||||||
<property name="text">
|
|
||||||
<string>Apply Filter</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+F</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionCancelFilter">
|
|
||||||
<property name="text">
|
|
||||||
<string>Cancel Filter</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+Shift+F</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionShowHelp">
|
|
||||||
<property name="text">
|
|
||||||
<string>dupeGuru Help</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>F1</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionAbout">
|
|
||||||
<property name="text">
|
|
||||||
<string>About dupeGuru</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionRegister">
|
|
||||||
<property name="text">
|
|
||||||
<string>Register dupeGuru</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionCheckForUpdate">
|
|
||||||
<property name="text">
|
|
||||||
<string>Check for Update</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionExport">
|
|
||||||
<property name="text">
|
|
||||||
<string>Export To XHTML</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionOpenDebugLog">
|
|
||||||
<property name="text">
|
|
||||||
<string>Open Debug Log</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
</widget>
|
|
||||||
<customwidgets>
|
|
||||||
<customwidget>
|
|
||||||
<class>ResultsView</class>
|
|
||||||
<extends>QTreeView</extends>
|
|
||||||
<header>results_model</header>
|
|
||||||
</customwidget>
|
|
||||||
</customwidgets>
|
|
||||||
<resources>
|
|
||||||
<include location="dg.qrc"/>
|
|
||||||
</resources>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>actionDirectories</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>directoriesTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionActions</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>actionsTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionCopyMarked</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>copyTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionDeleteMarked</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>deleteTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionDelta</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>deltaTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionDetails</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>detailsTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionIgnoreSelected</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>addToIgnoreListTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionMakeSelectedReference</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>makeReferenceTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionMoveMarked</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>moveTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionOpenSelected</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>openTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionPowerMarker</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>powerMarkerTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionPreferences</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>preferencesTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionRemoveMarked</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>removeMarkedTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionRemoveSelected</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>removeSelectedTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionRevealSelected</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>revealTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionRenameSelected</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>renameTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionScan</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>scanTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionClearIgnoreList</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>clearIgnoreListTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionMarkAll</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>markAllTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionMarkNone</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>markNoneTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionMarkSelected</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>markSelectedTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionInvertMarking</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>markInvertTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionApplyFilter</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>applyFilterTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionCancelFilter</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>cancelFilterTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionShowHelp</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>showHelpTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionAbout</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>aboutTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionRegister</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>registerTrigerred()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionCheckForUpdate</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>checkForUpdateTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionExport</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>exportTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
<connection>
|
|
||||||
<sender>actionOpenDebugLog</sender>
|
|
||||||
<signal>triggered()</signal>
|
|
||||||
<receiver>MainWindow</receiver>
|
|
||||||
<slot>openDebugLogTriggered()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>-1</x>
|
|
||||||
<y>-1</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>314</x>
|
|
||||||
<y>256</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
<slots>
|
|
||||||
<slot>directoriesTriggered()</slot>
|
|
||||||
<slot>scanTriggered()</slot>
|
|
||||||
<slot>actionsTriggered()</slot>
|
|
||||||
<slot>detailsTriggered()</slot>
|
|
||||||
<slot>preferencesTriggered()</slot>
|
|
||||||
<slot>deltaTriggered()</slot>
|
|
||||||
<slot>powerMarkerTriggered()</slot>
|
|
||||||
<slot>deleteTriggered()</slot>
|
|
||||||
<slot>moveTriggered()</slot>
|
|
||||||
<slot>copyTriggered()</slot>
|
|
||||||
<slot>removeMarkedTriggered()</slot>
|
|
||||||
<slot>removeSelectedTriggered()</slot>
|
|
||||||
<slot>addToIgnoreListTriggered()</slot>
|
|
||||||
<slot>makeReferenceTriggered()</slot>
|
|
||||||
<slot>openTriggered()</slot>
|
|
||||||
<slot>revealTriggered()</slot>
|
|
||||||
<slot>renameTriggered()</slot>
|
|
||||||
<slot>clearIgnoreListTriggered()</slot>
|
|
||||||
<slot>clearPictureCacheTriggered()</slot>
|
|
||||||
<slot>markAllTriggered()</slot>
|
|
||||||
<slot>markNoneTriggered()</slot>
|
|
||||||
<slot>markInvertTriggered()</slot>
|
|
||||||
<slot>markSelectedTriggered()</slot>
|
|
||||||
<slot>applyFilterTriggered()</slot>
|
|
||||||
<slot>cancelFilterTriggered()</slot>
|
|
||||||
<slot>showHelpTriggered()</slot>
|
|
||||||
<slot>aboutTriggered()</slot>
|
|
||||||
<slot>registerTrigerred()</slot>
|
|
||||||
<slot>checkForUpdateTriggered()</slot>
|
|
||||||
<slot>exportTriggered()</slot>
|
|
||||||
<slot>openDebugLogTriggered()</slot>
|
|
||||||
</slots>
|
|
||||||
</ui>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-09-27
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
if sys.platform == 'win32':
|
|
||||||
from platform_win import *
|
|
||||||
elif sys.platform == 'darwin':
|
|
||||||
from platform_osx import *
|
|
||||||
else:
|
|
||||||
pass # unsupported platform
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-10-14
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
# dummy unit to allow the app to run under OSX during development
|
|
||||||
|
|
||||||
INITIAL_FOLDER_IN_DIALOGS = '/'
|
|
||||||
|
|
||||||
def recycle_file(path):
|
|
||||||
pass
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-08-31
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import winshell
|
|
||||||
|
|
||||||
INITIAL_FOLDER_IN_DIALOGS = 'C:\\'
|
|
||||||
|
|
||||||
def recycle_file(path):
|
|
||||||
try:
|
|
||||||
winshell.delete_file(unicode(path), no_confirm=True, silent=True)
|
|
||||||
except winshell.x_winshell as e:
|
|
||||||
logging.warning("winshell error: %s", e)
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-03
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from PyQt4.QtCore import QSettings, QVariant
|
|
||||||
|
|
||||||
from hsutil.misc import tryint
|
|
||||||
|
|
||||||
def variant_to_py(v):
|
|
||||||
value = None
|
|
||||||
ok = False
|
|
||||||
t = v.type()
|
|
||||||
if t == QVariant.String:
|
|
||||||
value = unicode(v.toString())
|
|
||||||
ok = True # anyway
|
|
||||||
# might be bool or int, try them
|
|
||||||
if v == 'true':
|
|
||||||
value = True
|
|
||||||
elif value == 'false':
|
|
||||||
value = False
|
|
||||||
else:
|
|
||||||
value = tryint(value, value)
|
|
||||||
elif t == QVariant.Int:
|
|
||||||
value, ok = v.toInt()
|
|
||||||
elif t == QVariant.Bool:
|
|
||||||
value, ok = v.toBool(), True
|
|
||||||
elif t in (QVariant.List, QVariant.StringList):
|
|
||||||
value, ok = map(variant_to_py, v.toList()), True
|
|
||||||
if not ok:
|
|
||||||
raise TypeError(u"Can't convert {0} of type {1}".format(repr(v), v.type()))
|
|
||||||
return value
|
|
||||||
|
|
||||||
def py_to_variant(v):
|
|
||||||
if isinstance(v, (list, tuple)):
|
|
||||||
return QVariant(map(py_to_variant, v))
|
|
||||||
return QVariant(v)
|
|
||||||
|
|
||||||
class Preferences(object):
|
|
||||||
# (width, is_visible)
|
|
||||||
COLUMNS_DEFAULT_ATTRS = []
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.reset()
|
|
||||||
self.reset_columns()
|
|
||||||
|
|
||||||
def _load_specific(self, settings, get):
|
|
||||||
# load prefs specific to the dg edition
|
|
||||||
pass
|
|
||||||
|
|
||||||
def load(self):
|
|
||||||
self.reset()
|
|
||||||
settings = QSettings()
|
|
||||||
def get(name, default):
|
|
||||||
if settings.contains(name):
|
|
||||||
return variant_to_py(settings.value(name))
|
|
||||||
else:
|
|
||||||
return default
|
|
||||||
|
|
||||||
self.filter_hardness = get('FilterHardness', self.filter_hardness)
|
|
||||||
self.mix_file_kind = get('MixFileKind', self.mix_file_kind)
|
|
||||||
self.use_regexp = get('UseRegexp', self.use_regexp)
|
|
||||||
self.remove_empty_folders = get('RemoveEmptyFolders', self.remove_empty_folders)
|
|
||||||
self.destination_type = get('DestinationType', self.destination_type)
|
|
||||||
widths = get('ColumnsWidth', self.columns_width)
|
|
||||||
# only set nonzero values
|
|
||||||
for index, width in enumerate(widths[:len(self.columns_width)]):
|
|
||||||
if width > 0:
|
|
||||||
self.columns_width[index] = width
|
|
||||||
self.columns_visible = get('ColumnsVisible', self.columns_visible)
|
|
||||||
self.registration_code = get('RegistrationCode', self.registration_code)
|
|
||||||
self.registration_email = get('RegistrationEmail', self.registration_email)
|
|
||||||
self._load_specific(settings, get)
|
|
||||||
|
|
||||||
def _reset_specific(self):
|
|
||||||
# reset prefs specific to the dg edition
|
|
||||||
pass
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.filter_hardness = 95
|
|
||||||
self.mix_file_kind = True
|
|
||||||
self.use_regexp = False
|
|
||||||
self.remove_empty_folders = False
|
|
||||||
self.destination_type = 1
|
|
||||||
self.registration_code = ''
|
|
||||||
self.registration_email = ''
|
|
||||||
self._reset_specific()
|
|
||||||
|
|
||||||
def reset_columns(self):
|
|
||||||
self.columns_width = [width for width, _ in self.COLUMNS_DEFAULT_ATTRS]
|
|
||||||
self.columns_visible = [visible for _, visible in self.COLUMNS_DEFAULT_ATTRS]
|
|
||||||
|
|
||||||
def _save_specific(self, settings, set_):
|
|
||||||
# save prefs specific to the dg edition
|
|
||||||
pass
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
settings = QSettings()
|
|
||||||
def set_(name, value):
|
|
||||||
settings.setValue(name, py_to_variant(value))
|
|
||||||
|
|
||||||
set_('FilterHardness', self.filter_hardness)
|
|
||||||
set_('MixFileKind', self.mix_file_kind)
|
|
||||||
set_('UseRegexp', self.use_regexp)
|
|
||||||
set_('RemoveEmptyFolders', self.remove_empty_folders)
|
|
||||||
set_('DestinationType', self.destination_type)
|
|
||||||
set_('ColumnsWidth', self.columns_width)
|
|
||||||
set_('ColumnsVisible', self.columns_visible)
|
|
||||||
set_('RegistrationCode', self.registration_code)
|
|
||||||
set_('RegistrationEmail', self.registration_email)
|
|
||||||
self._save_specific(settings, set_)
|
|
||||||
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-09
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from hashlib import md5
|
|
||||||
|
|
||||||
from PyQt4.QtGui import QDialog
|
|
||||||
|
|
||||||
from reg_submit_dialog import RegSubmitDialog
|
|
||||||
from reg_demo_dialog import RegDemoDialog
|
|
||||||
|
|
||||||
class Registration(object):
|
|
||||||
def __init__(self, app):
|
|
||||||
self.app = app
|
|
||||||
|
|
||||||
def ask_for_code(self):
|
|
||||||
dialog = RegSubmitDialog(self.app.main_window, self.app.is_code_valid)
|
|
||||||
result = dialog.exec_()
|
|
||||||
code = unicode(dialog.codeEdit.text())
|
|
||||||
email = unicode(dialog.emailEdit.text())
|
|
||||||
dialog.setParent(None) # free it
|
|
||||||
if result == QDialog.Accepted and self.app.is_code_valid(code, email):
|
|
||||||
self.app.set_registration(code, email)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def show_nag(self):
|
|
||||||
dialog = RegDemoDialog(self.app.main_window, self)
|
|
||||||
dialog.exec_()
|
|
||||||
dialog.setParent(None) # free it
|
|
||||||
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-10
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from PyQt4.QtCore import SIGNAL, Qt, QUrl, QCoreApplication
|
|
||||||
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices
|
|
||||||
|
|
||||||
from reg_demo_dialog_ui import Ui_RegDemoDialog
|
|
||||||
|
|
||||||
class RegDemoDialog(QDialog, Ui_RegDemoDialog):
|
|
||||||
def __init__(self, parent, reg):
|
|
||||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
|
|
||||||
QDialog.__init__(self, parent, flags)
|
|
||||||
self.reg = reg
|
|
||||||
self._setupUi()
|
|
||||||
|
|
||||||
self.connect(self.enterCodeButton, SIGNAL('clicked()'), self.enterCodeClicked)
|
|
||||||
self.connect(self.purchaseButton, SIGNAL('clicked()'), self.purchaseClicked)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
|
||||||
self.setupUi(self)
|
|
||||||
# Stuff that can't be setup in the Designer
|
|
||||||
appname = QCoreApplication.instance().applicationName()
|
|
||||||
title = self.windowTitle()
|
|
||||||
title = title.replace('$appname', appname)
|
|
||||||
self.setWindowTitle(title)
|
|
||||||
title = self.titleLabel.text()
|
|
||||||
title = title.replace('$appname', appname)
|
|
||||||
self.titleLabel.setText(title)
|
|
||||||
desc = self.demoDescLabel.text()
|
|
||||||
desc = desc.replace('$appname', appname)
|
|
||||||
self.demoDescLabel.setText(desc)
|
|
||||||
|
|
||||||
#--- Events
|
|
||||||
def enterCodeClicked(self):
|
|
||||||
if self.reg.ask_for_code():
|
|
||||||
self.accept()
|
|
||||||
|
|
||||||
def purchaseClicked(self):
|
|
||||||
url = QUrl('http://www.hardcoded.net/purchase.htm')
|
|
||||||
QDesktopServices.openUrl(url)
|
|
||||||
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>RegDemoDialog</class>
|
|
||||||
<widget class="QDialog" name="RegDemoDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>387</width>
|
|
||||||
<height>161</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>$appname Demo Version</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="titleLabel">
|
|
||||||
<property name="font">
|
|
||||||
<font>
|
|
||||||
<weight>75</weight>
|
|
||||||
<bold>true</bold>
|
|
||||||
</font>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>$appname Demo Version</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="demoDescLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>You are currently running a demo version of $appname. This version has limited functionalities, and you need to buy it to have access to these functionalities.</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>In the demo version, only 10 duplicates per session can be sent to the recycle bin, moved or copied.</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="verticalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>20</width>
|
|
||||||
<height>40</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="tryButton">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>110</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Try Demo</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="enterCodeButton">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>110</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Enter Code</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="purchaseButton">
|
|
||||||
<property name="minimumSize">
|
|
||||||
<size>
|
|
||||||
<width>110</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Purchase</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>tryButton</sender>
|
|
||||||
<signal>clicked()</signal>
|
|
||||||
<receiver>RegDemoDialog</receiver>
|
|
||||||
<slot>accept()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>112</x>
|
|
||||||
<y>161</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>201</x>
|
|
||||||
<y>94</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-09
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from PyQt4.QtCore import SIGNAL, Qt, QUrl, QCoreApplication
|
|
||||||
from PyQt4.QtGui import QDialog, QMessageBox, QDesktopServices
|
|
||||||
|
|
||||||
from reg_submit_dialog_ui import Ui_RegSubmitDialog
|
|
||||||
|
|
||||||
class RegSubmitDialog(QDialog, Ui_RegSubmitDialog):
|
|
||||||
def __init__(self, parent, is_valid_func):
|
|
||||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
|
|
||||||
QDialog.__init__(self, parent, flags)
|
|
||||||
self._setupUi()
|
|
||||||
self.is_valid_func = is_valid_func
|
|
||||||
|
|
||||||
self.connect(self.submitButton, SIGNAL('clicked()'), self.submitClicked)
|
|
||||||
self.connect(self.purchaseButton, SIGNAL('clicked()'), self.purchaseClicked)
|
|
||||||
|
|
||||||
def _setupUi(self):
|
|
||||||
self.setupUi(self)
|
|
||||||
# Stuff that can't be setup in the Designer
|
|
||||||
appname = QCoreApplication.instance().applicationName()
|
|
||||||
prompt = self.promptLabel.text()
|
|
||||||
prompt = prompt.replace('$appname', appname)
|
|
||||||
self.promptLabel.setText(prompt)
|
|
||||||
|
|
||||||
#--- Events
|
|
||||||
def purchaseClicked(self):
|
|
||||||
url = QUrl('http://www.hardcoded.net/purchase.htm')
|
|
||||||
QDesktopServices.openUrl(url)
|
|
||||||
|
|
||||||
def submitClicked(self):
|
|
||||||
code = unicode(self.codeEdit.text())
|
|
||||||
email = unicode(self.emailEdit.text())
|
|
||||||
title = "Registration"
|
|
||||||
if self.is_valid_func(code, email):
|
|
||||||
msg = "This code is valid. Thanks!"
|
|
||||||
QMessageBox.information(self, title, msg)
|
|
||||||
self.accept()
|
|
||||||
else:
|
|
||||||
msg = "This code is invalid"
|
|
||||||
QMessageBox.warning(self, title, msg)
|
|
||||||
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<ui version="4.0">
|
|
||||||
<class>RegSubmitDialog</class>
|
|
||||||
<widget class="QDialog" name="RegSubmitDialog">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>365</width>
|
|
||||||
<height>134</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
<property name="windowTitle">
|
|
||||||
<string>Enter your registration code</string>
|
|
||||||
</property>
|
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="promptLabel">
|
|
||||||
<property name="text">
|
|
||||||
<string>Please enter your $appname registration code and registered e-mail (the e-mail you used for the purchase), then press "Submit".</string>
|
|
||||||
</property>
|
|
||||||
<property name="alignment">
|
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QFormLayout" name="formLayout">
|
|
||||||
<property name="sizeConstraint">
|
|
||||||
<enum>QLayout::SetNoConstraint</enum>
|
|
||||||
</property>
|
|
||||||
<property name="fieldGrowthPolicy">
|
|
||||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
|
||||||
</property>
|
|
||||||
<property name="labelAlignment">
|
|
||||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
|
||||||
</property>
|
|
||||||
<property name="formAlignment">
|
|
||||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
|
||||||
</property>
|
|
||||||
<item row="0" column="0">
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Registration code:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="0">
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="text">
|
|
||||||
<string>Registered e-mail:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="0" column="1">
|
|
||||||
<widget class="QLineEdit" name="codeEdit"/>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QLineEdit" name="emailEdit"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="purchaseButton">
|
|
||||||
<property name="text">
|
|
||||||
<string>Purchase</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>40</width>
|
|
||||||
<height>20</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="cancelButton">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Cancel</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QPushButton" name="submitButton">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Submit</string>
|
|
||||||
</property>
|
|
||||||
<property name="autoDefault">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="default">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
<resources/>
|
|
||||||
<connections>
|
|
||||||
<connection>
|
|
||||||
<sender>cancelButton</sender>
|
|
||||||
<signal>clicked()</signal>
|
|
||||||
<receiver>RegSubmitDialog</receiver>
|
|
||||||
<slot>reject()</slot>
|
|
||||||
<hints>
|
|
||||||
<hint type="sourcelabel">
|
|
||||||
<x>260</x>
|
|
||||||
<y>159</y>
|
|
||||||
</hint>
|
|
||||||
<hint type="destinationlabel">
|
|
||||||
<x>198</x>
|
|
||||||
<y>97</y>
|
|
||||||
</hint>
|
|
||||||
</hints>
|
|
||||||
</connection>
|
|
||||||
</connections>
|
|
||||||
</ui>
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-04-23
|
|
||||||
# $Id$
|
|
||||||
# Copyright 2009 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.hardcoded.net/licenses/hs_license
|
|
||||||
|
|
||||||
from PyQt4.QtCore import SIGNAL, Qt, QAbstractItemModel, QModelIndex, QRect
|
|
||||||
from PyQt4.QtGui import QBrush, QStyledItemDelegate, QFont, QTreeView, QColor
|
|
||||||
|
|
||||||
from qtlib.tree_model import TreeNode, TreeModel
|
|
||||||
|
|
||||||
class ResultNode(TreeNode):
|
|
||||||
def __init__(self, model, parent, row, dupe, group):
|
|
||||||
TreeNode.__init__(self, model, parent, row)
|
|
||||||
self.dupe = dupe
|
|
||||||
self.group = group
|
|
||||||
self._normalData = None
|
|
||||||
self._deltaData = None
|
|
||||||
|
|
||||||
def _createNode(self, ref, row):
|
|
||||||
return ResultNode(self.model, self, row, ref, self.group)
|
|
||||||
|
|
||||||
def _getChildren(self):
|
|
||||||
return self.group.dupes if self.dupe is self.group.ref else []
|
|
||||||
|
|
||||||
def invalidate(self):
|
|
||||||
self._normalData = None
|
|
||||||
self._deltaData = None
|
|
||||||
TreeNode.invalidate(self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def normalData(self):
|
|
||||||
if self._normalData is None:
|
|
||||||
self._normalData = self.model._app._get_display_info(self.dupe, self.group, delta=False)
|
|
||||||
return self._normalData
|
|
||||||
|
|
||||||
@property
|
|
||||||
def deltaData(self):
|
|
||||||
if self._deltaData is None:
|
|
||||||
self._deltaData = self.model._app._get_display_info(self.dupe, self.group, delta=True)
|
|
||||||
return self._deltaData
|
|
||||||
|
|
||||||
|
|
||||||
class ResultsDelegate(QStyledItemDelegate):
|
|
||||||
def initStyleOption(self, option, index):
|
|
||||||
QStyledItemDelegate.initStyleOption(self, option, index)
|
|
||||||
node = index.internalPointer()
|
|
||||||
if node.group.ref is node.dupe:
|
|
||||||
newfont = QFont(option.font)
|
|
||||||
newfont.setBold(True)
|
|
||||||
option.font = newfont
|
|
||||||
|
|
||||||
|
|
||||||
class ResultsModel(TreeModel):
|
|
||||||
def __init__(self, app):
|
|
||||||
self._app = app
|
|
||||||
self._results = app.results
|
|
||||||
self._data = app.data
|
|
||||||
self._delta_columns = app.DELTA_COLUMNS
|
|
||||||
self.delta = False
|
|
||||||
self._power_marker = False
|
|
||||||
TreeModel.__init__(self)
|
|
||||||
|
|
||||||
def _createNode(self, ref, row):
|
|
||||||
if self.power_marker:
|
|
||||||
# ref is a dupe
|
|
||||||
group = self._results.get_group_of_duplicate(ref)
|
|
||||||
return ResultNode(self, None, row, ref, group)
|
|
||||||
else:
|
|
||||||
# ref is a group
|
|
||||||
return ResultNode(self, None, row, ref.ref, ref)
|
|
||||||
|
|
||||||
def _getChildren(self):
|
|
||||||
return self._results.dupes if self.power_marker else self._results.groups
|
|
||||||
|
|
||||||
def columnCount(self, parent):
|
|
||||||
return len(self._data.COLUMNS)
|
|
||||||
|
|
||||||
def data(self, index, role):
|
|
||||||
if not index.isValid():
|
|
||||||
return None
|
|
||||||
node = index.internalPointer()
|
|
||||||
if role == Qt.DisplayRole:
|
|
||||||
data = node.deltaData if self.delta else node.normalData
|
|
||||||
return data[index.column()]
|
|
||||||
elif role == Qt.CheckStateRole:
|
|
||||||
if index.column() == 0 and node.dupe is not node.group.ref:
|
|
||||||
state = Qt.Checked if self._results.is_marked(node.dupe) else Qt.Unchecked
|
|
||||||
return state
|
|
||||||
elif role == Qt.ForegroundRole:
|
|
||||||
if node.dupe is node.group.ref or node.dupe.is_ref:
|
|
||||||
return QBrush(Qt.blue)
|
|
||||||
elif self.delta and index.column() in self._delta_columns:
|
|
||||||
return QBrush(QColor(255, 142, 40)) # orange
|
|
||||||
elif role == Qt.EditRole:
|
|
||||||
if index.column() == 0:
|
|
||||||
return node.normalData[index.column()]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def dupesForIndexes(self, indexes):
|
|
||||||
nodes = [index.internalPointer() for index in indexes]
|
|
||||||
return [node.dupe for node in nodes]
|
|
||||||
|
|
||||||
def indexesForDupes(self, dupes):
|
|
||||||
def index(dupe):
|
|
||||||
try:
|
|
||||||
if self.power_marker:
|
|
||||||
row = self._results.dupes.index(dupe)
|
|
||||||
node = self.subnodes[row]
|
|
||||||
assert node.dupe is dupe
|
|
||||||
return self.createIndex(row, 0, node)
|
|
||||||
else:
|
|
||||||
group = self._results.get_group_of_duplicate(dupe)
|
|
||||||
row = self._results.groups.index(group)
|
|
||||||
node = self.subnodes[row]
|
|
||||||
if dupe is group.ref:
|
|
||||||
assert node.dupe is dupe
|
|
||||||
return self.createIndex(row, 0, node)
|
|
||||||
subrow = group.dupes.index(dupe)
|
|
||||||
subnode = node.subnodes[subrow]
|
|
||||||
assert subnode.dupe is dupe
|
|
||||||
return self.createIndex(subrow, 0, subnode)
|
|
||||||
except ValueError: # the dupe is not there anymore
|
|
||||||
return QModelIndex()
|
|
||||||
|
|
||||||
return map(index, dupes)
|
|
||||||
|
|
||||||
def flags(self, index):
|
|
||||||
if not index.isValid():
|
|
||||||
return Qt.ItemIsEnabled
|
|
||||||
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
|
||||||
if index.column() == 0:
|
|
||||||
flags |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable
|
|
||||||
return flags
|
|
||||||
|
|
||||||
def headerData(self, section, orientation, role):
|
|
||||||
if orientation == Qt.Horizontal and role == Qt.DisplayRole and section < len(self._data.COLUMNS):
|
|
||||||
return self._data.COLUMNS[section]['display']
|
|
||||||
return None
|
|
||||||
|
|
||||||
def setData(self, index, value, role):
|
|
||||||
if not index.isValid():
|
|
||||||
return False
|
|
||||||
node = index.internalPointer()
|
|
||||||
if role == Qt.CheckStateRole:
|
|
||||||
if index.column() == 0:
|
|
||||||
self._app.toggle_marking_for_dupes([node.dupe])
|
|
||||||
return True
|
|
||||||
if role == Qt.EditRole:
|
|
||||||
if index.column() == 0:
|
|
||||||
value = unicode(value.toString())
|
|
||||||
if self._app.rename_dupe(node.dupe, value):
|
|
||||||
node.reset()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def sort(self, column, order):
|
|
||||||
if self.power_marker:
|
|
||||||
self._results.sort_dupes(column, order == Qt.AscendingOrder, self.delta)
|
|
||||||
else:
|
|
||||||
self._results.sort_groups(column, order == Qt.AscendingOrder)
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
def toggleMarked(self, indexes):
|
|
||||||
assert indexes
|
|
||||||
dupes = self.dupesForIndexes(indexes)
|
|
||||||
self._app.toggle_marking_for_dupes(dupes)
|
|
||||||
|
|
||||||
#--- Properties
|
|
||||||
@property
|
|
||||||
def power_marker(self):
|
|
||||||
return self._power_marker
|
|
||||||
|
|
||||||
@power_marker.setter
|
|
||||||
def power_marker(self, value):
|
|
||||||
if value == self._power_marker:
|
|
||||||
return
|
|
||||||
self._power_marker = value
|
|
||||||
self.reset()
|
|
||||||
|
|
||||||
|
|
||||||
class ResultsView(QTreeView):
|
|
||||||
#--- Override
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
if event.text() == ' ':
|
|
||||||
self.model().toggleMarked(self.selectionModel().selectedRows())
|
|
||||||
return
|
|
||||||
QTreeView.keyPressEvent(self, event)
|
|
||||||
|
|
||||||
def mouseDoubleClickEvent(self, event):
|
|
||||||
self.emit(SIGNAL('doubleClicked()'))
|
|
||||||
# We don't call the superclass' method because the default behavior is to rename the cell.
|
|
||||||
|
|
||||||
def setModel(self, model):
|
|
||||||
assert isinstance(model, ResultsModel)
|
|
||||||
QTreeView.setModel(self, model)
|
|
||||||
|
|
||||||
#--- Public
|
|
||||||
def selectedDupes(self):
|
|
||||||
return self.model().dupesForIndexes(self.selectionModel().selectedRows())
|
|
||||||
|
|
||||||
288
build.py
Normal file
288
build.py
Normal file
@@ -0,0 +1,288 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2009-12-30
|
||||||
|
# Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
# which should be included with this package. The terms are also available at
|
||||||
|
# http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import os.path as op
|
||||||
|
from optparse import OptionParser
|
||||||
|
import shutil
|
||||||
|
import json
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
from setuptools import setup, Extension
|
||||||
|
|
||||||
|
from hscommon import sphinxgen
|
||||||
|
from hscommon.build import (add_to_pythonpath, print_and_do, copy_packages, filereplace,
|
||||||
|
get_module_version, build_all_cocoa_locs, move_all, copy_sysconfig_files_for_embed, copy_all,
|
||||||
|
move)
|
||||||
|
from hscommon import loc
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
usage = "usage: %prog [options]"
|
||||||
|
parser = OptionParser(usage=usage)
|
||||||
|
parser.add_option('--clean', action='store_true', dest='clean',
|
||||||
|
help="Clean build folder before building")
|
||||||
|
parser.add_option('--doc', action='store_true', dest='doc',
|
||||||
|
help="Build only the help file")
|
||||||
|
parser.add_option('--loc', action='store_true', dest='loc',
|
||||||
|
help="Build only localization")
|
||||||
|
parser.add_option('--cocoamod', action='store_true', dest='cocoamod',
|
||||||
|
help="Build only Cocoa modules")
|
||||||
|
parser.add_option('--updatepot', action='store_true', dest='updatepot',
|
||||||
|
help="Generate .pot files from source code.")
|
||||||
|
parser.add_option('--mergepot', action='store_true', dest='mergepot',
|
||||||
|
help="Update all .po files based on .pot files.")
|
||||||
|
(options, args) = parser.parse_args()
|
||||||
|
return options
|
||||||
|
|
||||||
|
def build_cocoa(edition, dev):
|
||||||
|
build_cocoa_proxy_module()
|
||||||
|
build_cocoa_bridging_interfaces(edition)
|
||||||
|
print("Building the cocoa layer")
|
||||||
|
from pluginbuilder import copy_embeddable_python_dylib, get_python_header_folder, collect_dependencies
|
||||||
|
copy_embeddable_python_dylib('build')
|
||||||
|
if not op.exists('build/PythonHeaders'):
|
||||||
|
os.symlink(get_python_header_folder(), 'build/PythonHeaders')
|
||||||
|
if not op.exists('build/py'):
|
||||||
|
os.mkdir('build/py')
|
||||||
|
cocoa_project_path = 'cocoa/{0}'.format(edition)
|
||||||
|
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
||||||
|
specific_packages = {
|
||||||
|
'se': ['core_se'],
|
||||||
|
'me': ['core_me'],
|
||||||
|
'pe': ['core_pe'],
|
||||||
|
}[edition]
|
||||||
|
tocopy = ['core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa'] + specific_packages
|
||||||
|
copy_packages(tocopy, 'build')
|
||||||
|
sys.path.insert(0, 'build')
|
||||||
|
collect_dependencies('build/dg_cocoa.py', 'build/py', excludes=['PyQt4'])
|
||||||
|
del sys.path[0]
|
||||||
|
if dev:
|
||||||
|
copy_packages(tocopy, 'build/py', create_links=True)
|
||||||
|
# Views are not referenced by python code, so they're not found by the collector.
|
||||||
|
copy_all('build/inter/*.so', 'build/py/inter')
|
||||||
|
copy_sysconfig_files_for_embed('build/py')
|
||||||
|
os.chdir(cocoa_project_path)
|
||||||
|
print('Generating Info.plist')
|
||||||
|
app_version = get_module_version('core_{}'.format(edition))
|
||||||
|
filereplace('InfoTemplate.plist', 'Info.plist', version=app_version)
|
||||||
|
print("Building the XCode project")
|
||||||
|
args = ['-project dupeguru.xcodeproj']
|
||||||
|
if dev:
|
||||||
|
args.append('-configuration dev')
|
||||||
|
else:
|
||||||
|
args.append('-configuration release')
|
||||||
|
args = ' '.join(args)
|
||||||
|
os.system('xcodebuild {0}'.format(args))
|
||||||
|
os.chdir('../..')
|
||||||
|
print("Creating the run.py file")
|
||||||
|
app_path = {
|
||||||
|
'se': 'cocoa/se/dupeGuru.app',
|
||||||
|
'me': 'cocoa/me/dupeGuru\\ ME.app',
|
||||||
|
'pe': 'cocoa/pe/dupeGuru\\ PE.app',
|
||||||
|
}[edition]
|
||||||
|
tmpl = open('run_template_cocoa.py', 'rt').read()
|
||||||
|
run_contents = tmpl.replace('{{app_path}}', app_path)
|
||||||
|
open('run.py', 'wt').write(run_contents)
|
||||||
|
|
||||||
|
def build_qt(edition, dev):
|
||||||
|
print("Building Qt stuff")
|
||||||
|
print_and_do("pyrcc4 -py3 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
|
||||||
|
print("Creating the run.py file")
|
||||||
|
tmpl = open('run_template_qt.py', 'rt').read()
|
||||||
|
run_contents = tmpl.replace('{{edition}}', edition)
|
||||||
|
open('run.py', 'wt').write(run_contents)
|
||||||
|
|
||||||
|
def build_help(edition):
|
||||||
|
print("Generating Help")
|
||||||
|
current_path = op.abspath('.')
|
||||||
|
help_basepath = op.join(current_path, 'help', 'en')
|
||||||
|
help_destpath = op.join(current_path, 'build', 'help'.format(edition))
|
||||||
|
changelog_path = op.join(current_path, 'help', 'changelog_{}'.format(edition))
|
||||||
|
tixurl = "https://hardcoded.lighthouseapp.com/projects/31699-dupeguru/tickets/{0}"
|
||||||
|
appname = {'se': 'dupeGuru', 'me': 'dupeGuru Music Edition', 'pe': 'dupeGuru Picture Edition'}[edition]
|
||||||
|
homepage = 'http://www.hardcoded.net/dupeguru{}/'.format('_' + edition if edition != 'se' else '')
|
||||||
|
confrepl = {'edition': edition, 'appname': appname, 'homepage': homepage, 'language': 'en'}
|
||||||
|
changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl')
|
||||||
|
conftmpl = op.join(current_path, 'help', 'conf.tmpl')
|
||||||
|
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl)
|
||||||
|
|
||||||
|
def build_localizations(ui, edition):
|
||||||
|
print("Building localizations")
|
||||||
|
loc.compile_all_po('locale')
|
||||||
|
loc.compile_all_po(op.join('hscommon', 'locale'))
|
||||||
|
loc.merge_locale_dir(op.join('hscommon', 'locale'), 'locale')
|
||||||
|
if ui == 'cocoa':
|
||||||
|
print("Creating lproj folders based on .po files")
|
||||||
|
for lang in loc.get_langs('locale'):
|
||||||
|
if lang == 'en':
|
||||||
|
continue
|
||||||
|
pofile = op.join('locale', lang, 'LC_MESSAGES', 'ui.po')
|
||||||
|
for edition_folder in ['base', 'se', 'me', 'pe']:
|
||||||
|
enlproj = op.join('cocoa', edition_folder, 'en.lproj')
|
||||||
|
dest_lproj = op.join('cocoa', edition_folder, lang + '.lproj')
|
||||||
|
loc.po2allxibstrings(pofile, enlproj, dest_lproj)
|
||||||
|
if edition_folder == 'base':
|
||||||
|
loc.po2strings(pofile, op.join(enlproj, 'Localizable.strings'), op.join(dest_lproj, 'Localizable.strings'))
|
||||||
|
pofile = op.join('cocoalib', 'locale', lang, 'LC_MESSAGES', 'cocoalib.po')
|
||||||
|
loc.po2allxibstrings(pofile, op.join('cocoalib', 'en.lproj'), op.join('cocoalib', lang + '.lproj'))
|
||||||
|
build_all_cocoa_locs('cocoalib')
|
||||||
|
build_all_cocoa_locs(op.join('cocoa', 'base'))
|
||||||
|
build_all_cocoa_locs(op.join('cocoa', edition))
|
||||||
|
elif ui == 'qt':
|
||||||
|
loc.compile_all_po(op.join('qtlib', 'locale'))
|
||||||
|
loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale')
|
||||||
|
if op.exists(op.join('build', 'locale')):
|
||||||
|
shutil.rmtree(op.join('build', 'locale'))
|
||||||
|
shutil.copytree('locale', op.join('build', 'locale'), ignore=shutil.ignore_patterns('*.po', '*.pot'))
|
||||||
|
|
||||||
|
def build_updatepot():
|
||||||
|
print("Building .pot files from source files")
|
||||||
|
print("Building core.pot")
|
||||||
|
all_cores = ['core', 'core_se', 'core_me', 'core_pe']
|
||||||
|
loc.generate_pot(all_cores, op.join('locale', 'core.pot'), ['tr'])
|
||||||
|
print("Building columns.pot")
|
||||||
|
loc.generate_pot(all_cores, op.join('locale', 'columns.pot'), ['coltr'])
|
||||||
|
print("Building ui.pot")
|
||||||
|
ui_packages = ['qt', op.join('cocoa', 'inter')]
|
||||||
|
loc.generate_pot(ui_packages, op.join('locale', 'ui.pot'), ['tr'])
|
||||||
|
print("Building hscommon.pot")
|
||||||
|
loc.generate_pot(['hscommon'], op.join('hscommon', 'locale', 'hscommon.pot'), ['tr'])
|
||||||
|
print("Building qtlib.pot")
|
||||||
|
loc.generate_pot(['qtlib'], op.join('qtlib', 'locale', 'qtlib.pot'), ['tr'])
|
||||||
|
print("Building cocoalib.pot")
|
||||||
|
loc.allstrings2pot(op.join('cocoalib', 'en.lproj'), op.join('cocoalib', 'locale', 'cocoalib.pot'))
|
||||||
|
print("Enhancing ui.pot with Cocoa's strings files")
|
||||||
|
loc.allstrings2pot(op.join('cocoa', 'base', 'en.lproj'), op.join('locale', 'ui.pot'),
|
||||||
|
excludes={'core', 'message', 'columns'})
|
||||||
|
loc.allstrings2pot(op.join('cocoa', 'se', 'en.lproj'), op.join('locale', 'ui.pot'))
|
||||||
|
loc.allstrings2pot(op.join('cocoa', 'me', 'en.lproj'), op.join('locale', 'ui.pot'))
|
||||||
|
loc.allstrings2pot(op.join('cocoa', 'pe', 'en.lproj'), op.join('locale', 'ui.pot'))
|
||||||
|
|
||||||
|
def build_mergepot():
|
||||||
|
print("Updating .po files using .pot files")
|
||||||
|
loc.merge_pots_into_pos('locale')
|
||||||
|
loc.merge_pots_into_pos(op.join('hscommon', 'locale'))
|
||||||
|
loc.merge_pots_into_pos(op.join('qtlib', 'locale'))
|
||||||
|
loc.merge_pots_into_pos(op.join('cocoalib', 'locale'))
|
||||||
|
|
||||||
|
def build_cocoa_ext(extname, dest, source_files, extra_frameworks=(), extra_includes=()):
|
||||||
|
extra_link_args = ["-framework", "CoreFoundation", "-framework", "Foundation"]
|
||||||
|
for extra in extra_frameworks:
|
||||||
|
extra_link_args += ['-framework', extra]
|
||||||
|
ext = Extension(extname, source_files, extra_link_args=extra_link_args, include_dirs=extra_includes)
|
||||||
|
setup(script_args=['build_ext', '--inplace'], ext_modules=[ext])
|
||||||
|
fn = extname + '.so'
|
||||||
|
assert op.exists(fn)
|
||||||
|
move(fn, op.join(dest, fn))
|
||||||
|
|
||||||
|
def build_cocoa_proxy_module():
|
||||||
|
print("Building Cocoa Proxy")
|
||||||
|
import objp.p2o
|
||||||
|
objp.p2o.generate_python_proxy_code('cocoalib/cocoa/CocoaProxy.h', 'build/CocoaProxy.m')
|
||||||
|
build_cocoa_ext("CocoaProxy", 'cocoalib/cocoa',
|
||||||
|
['cocoalib/cocoa/CocoaProxy.m', 'build/CocoaProxy.m', 'build/ObjP.m', 'cocoalib/HSErrorReportWindow.m'],
|
||||||
|
['AppKit', 'CoreServices'],
|
||||||
|
['cocoalib'])
|
||||||
|
|
||||||
|
def build_cocoa_bridging_interfaces(edition):
|
||||||
|
print("Building Cocoa Bridging Interfaces")
|
||||||
|
import objp.o2p
|
||||||
|
import objp.p2o
|
||||||
|
add_to_pythonpath('cocoa')
|
||||||
|
add_to_pythonpath('cocoalib')
|
||||||
|
from cocoa.inter import (PyGUIObject, GUIObjectView, PyColumns, ColumnsView, PyOutline,
|
||||||
|
OutlineView, PySelectableList, SelectableListView, PyTable, TableView, PyFairware)
|
||||||
|
from inter.details_panel import PyDetailsPanel, DetailsPanelView
|
||||||
|
from inter.directory_outline import PyDirectoryOutline, DirectoryOutlineView
|
||||||
|
from inter.prioritize_dialog import PyPrioritizeDialog, PrioritizeDialogView
|
||||||
|
from inter.prioritize_list import PyPrioritizeList, PrioritizeListView
|
||||||
|
from inter.problem_dialog import PyProblemDialog
|
||||||
|
from inter.ignore_list_dialog import PyIgnoreListDialog, IgnoreListDialogView
|
||||||
|
from inter.result_table import PyResultTable, ResultTableView
|
||||||
|
from inter.stats_label import PyStatsLabel, StatsLabelView
|
||||||
|
from inter.app import PyDupeGuruBase, DupeGuruView
|
||||||
|
appmod = importlib.import_module('inter.app_{}'.format(edition))
|
||||||
|
allclasses = [PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyFairware,
|
||||||
|
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
|
||||||
|
PyIgnoreListDialog, PyResultTable, PyStatsLabel, PyDupeGuruBase, appmod.PyDupeGuru]
|
||||||
|
for class_ in allclasses:
|
||||||
|
objp.o2p.generate_objc_code(class_, 'cocoa/autogen', inherit=True)
|
||||||
|
allclasses = [GUIObjectView, ColumnsView, OutlineView, SelectableListView, TableView,
|
||||||
|
DetailsPanelView, DirectoryOutlineView, PrioritizeDialogView, PrioritizeListView,
|
||||||
|
IgnoreListDialogView, ResultTableView, StatsLabelView, DupeGuruView]
|
||||||
|
clsspecs = [objp.o2p.spec_from_python_class(class_) for class_ in allclasses]
|
||||||
|
objp.p2o.generate_python_proxy_code_from_clsspec(clsspecs, 'build/CocoaViews.m')
|
||||||
|
build_cocoa_ext('CocoaViews', 'cocoa/inter', ['build/CocoaViews.m', 'build/ObjP.m'])
|
||||||
|
|
||||||
|
def build_pe_modules(ui):
|
||||||
|
print("Building PE Modules")
|
||||||
|
exts = [
|
||||||
|
Extension("_block", [op.join('core_pe', 'modules', 'block.c'), op.join('core_pe', 'modules', 'common.c')]),
|
||||||
|
Extension("_cache", [op.join('core_pe', 'modules', 'cache.c'), op.join('core_pe', 'modules', 'common.c')]),
|
||||||
|
]
|
||||||
|
if ui == 'qt':
|
||||||
|
exts.append(Extension("_block_qt", [op.join('qt', 'pe', 'modules', 'block.c')]))
|
||||||
|
elif ui == 'cocoa':
|
||||||
|
exts.append(Extension(
|
||||||
|
"_block_osx", [op.join('core_pe', 'modules', 'block_osx.m'), op.join('core_pe', 'modules', 'common.c')],
|
||||||
|
extra_link_args=[
|
||||||
|
"-framework", "CoreFoundation",
|
||||||
|
"-framework", "Foundation",
|
||||||
|
"-framework", "ApplicationServices",]
|
||||||
|
))
|
||||||
|
setup(
|
||||||
|
script_args = ['build_ext', '--inplace'],
|
||||||
|
ext_modules = exts,
|
||||||
|
)
|
||||||
|
move_all('_block_qt*', op.join('qt', 'pe'))
|
||||||
|
move_all('_block*', 'core_pe')
|
||||||
|
move_all('_cache*', 'core_pe')
|
||||||
|
|
||||||
|
def build_normal(edition, ui, dev):
|
||||||
|
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
||||||
|
add_to_pythonpath('.')
|
||||||
|
build_help(edition)
|
||||||
|
build_localizations(ui, edition)
|
||||||
|
print("Building dupeGuru")
|
||||||
|
if edition == 'pe':
|
||||||
|
build_pe_modules(ui)
|
||||||
|
if ui == 'cocoa':
|
||||||
|
build_cocoa(edition, dev)
|
||||||
|
elif ui == 'qt':
|
||||||
|
build_qt(edition, dev)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
options = parse_args()
|
||||||
|
conf = json.load(open('conf.json'))
|
||||||
|
edition = conf['edition']
|
||||||
|
ui = conf['ui']
|
||||||
|
dev = conf['dev']
|
||||||
|
if dev:
|
||||||
|
print("Building in Dev mode")
|
||||||
|
if options.clean:
|
||||||
|
if op.exists('build'):
|
||||||
|
shutil.rmtree('build')
|
||||||
|
if not op.exists('build'):
|
||||||
|
os.mkdir('build')
|
||||||
|
if options.doc:
|
||||||
|
build_help(edition)
|
||||||
|
elif options.loc:
|
||||||
|
build_localizations(ui, edition)
|
||||||
|
elif options.updatepot:
|
||||||
|
build_updatepot()
|
||||||
|
elif options.mergepot:
|
||||||
|
build_mergepot()
|
||||||
|
elif options.cocoamod:
|
||||||
|
build_cocoa_proxy_module()
|
||||||
|
build_cocoa_bridging_interfaces(edition)
|
||||||
|
else:
|
||||||
|
build_normal(edition, ui, dev)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
71
cocoa/base/AppDelegate.h
Normal file
71
cocoa/base/AppDelegate.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "PyDupeGuru.h"
|
||||||
|
#import "ResultWindow.h"
|
||||||
|
#import "DetailsPanel.h"
|
||||||
|
#import "DirectoryPanel.h"
|
||||||
|
#import "IgnoreListDialog.h"
|
||||||
|
#import "HSAboutBox.h"
|
||||||
|
#import "HSRecentFiles.h"
|
||||||
|
|
||||||
|
@interface AppDelegateBase : NSObject
|
||||||
|
{
|
||||||
|
IBOutlet NSMenu *recentResultsMenu;
|
||||||
|
IBOutlet NSMenu *actionsMenu;
|
||||||
|
IBOutlet NSMenu *columnsMenu;
|
||||||
|
|
||||||
|
PyDupeGuru *model;
|
||||||
|
ResultWindowBase *_resultWindow;
|
||||||
|
DirectoryPanel *_directoryPanel;
|
||||||
|
DetailsPanel *_detailsPanel;
|
||||||
|
IgnoreListDialog *_ignoreListDialog;
|
||||||
|
NSWindowController *_preferencesPanel;
|
||||||
|
HSAboutBox *_aboutBox;
|
||||||
|
HSRecentFiles *_recentResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Virtual */
|
||||||
|
- (PyDupeGuru *)model;
|
||||||
|
- (ResultWindowBase *)createResultWindow;
|
||||||
|
- (DirectoryPanel *)createDirectoryPanel;
|
||||||
|
- (DetailsPanel *)createDetailsPanel;
|
||||||
|
- (NSString *)homepageURL;
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
- (ResultWindowBase *)resultWindow;
|
||||||
|
- (DirectoryPanel *)directoryPanel;
|
||||||
|
- (DetailsPanel *)detailsPanel;
|
||||||
|
- (HSRecentFiles *)recentResults;
|
||||||
|
- (NSMenu *)columnsMenu;
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
|
||||||
|
- (void)applicationWillBecomeActive:(NSNotification *)aNotification;
|
||||||
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
|
||||||
|
- (void)applicationWillTerminate:(NSNotification *)aNotification;
|
||||||
|
- (void)recentFileClicked:(NSString *)path;
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
- (IBAction)loadResults:(id)sender;
|
||||||
|
- (IBAction)openWebsite:(id)sender;
|
||||||
|
- (IBAction)openHelp:(id)sender;
|
||||||
|
- (IBAction)showAboutBox:(id)sender;
|
||||||
|
- (IBAction)showDirectoryWindow:(id)sender;
|
||||||
|
- (IBAction)showPreferencesPanel:(id)sender;
|
||||||
|
- (IBAction)showResultWindow:(id)sender;
|
||||||
|
- (IBAction)showIgnoreList:(id)sender;
|
||||||
|
- (IBAction)startScanning:(id)sender;
|
||||||
|
|
||||||
|
/* model --> view */
|
||||||
|
- (void)showMessage:(NSString *)msg;
|
||||||
|
- (void)setupAsRegistered;
|
||||||
|
- (void)showFairwareNagWithPrompt:(NSString *)prompt;
|
||||||
|
- (void)showDemoNagWithPrompt:(NSString *)prompt;
|
||||||
|
@end
|
||||||
261
cocoa/base/AppDelegate.m
Normal file
261
cocoa/base/AppDelegate.m
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "ProgressController.h"
|
||||||
|
#import "HSFairwareReminder.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "Consts.h"
|
||||||
|
#import "Dialogs.h"
|
||||||
|
#import "ValueTransformers.h"
|
||||||
|
#import <Sparkle/SUUpdater.h>
|
||||||
|
|
||||||
|
@implementation AppDelegateBase
|
||||||
|
+ (void)initialize
|
||||||
|
{
|
||||||
|
HSVTAdd *vt = [[[HSVTAdd alloc] initWithValue:4] autorelease];
|
||||||
|
[NSValueTransformer setValueTransformer:vt forName:@"vtRowHeightOffset"];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)awakeFromNib
|
||||||
|
{
|
||||||
|
model = [[PyDupeGuru alloc] init];
|
||||||
|
[model bindCallback:createCallback(@"DupeGuruView", self)];
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
/* Because the pref pane is lazily loaded, we have to manually do the update check if the
|
||||||
|
preference is set.
|
||||||
|
*/
|
||||||
|
if ([ud boolForKey:@"SUEnableAutomaticChecks"]) {
|
||||||
|
[[SUUpdater sharedUpdater] checkForUpdatesInBackground];
|
||||||
|
}
|
||||||
|
_recentResults = [[HSRecentFiles alloc] initWithName:@"recentResults" menu:recentResultsMenu];
|
||||||
|
[_recentResults setDelegate:self];
|
||||||
|
_resultWindow = [self createResultWindow];
|
||||||
|
_directoryPanel = [self createDirectoryPanel];
|
||||||
|
_detailsPanel = [self createDetailsPanel];
|
||||||
|
_ignoreListDialog = [[IgnoreListDialog alloc] initWithPyRef:[model ignoreListDialog]];
|
||||||
|
_aboutBox = nil; // Lazily loaded
|
||||||
|
_preferencesPanel = nil; // Lazily loaded
|
||||||
|
[[[self directoryPanel] window] makeKeyAndOrderFront:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Virtual */
|
||||||
|
|
||||||
|
- (PyDupeGuru *)model
|
||||||
|
{
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ResultWindowBase *)createResultWindow
|
||||||
|
{
|
||||||
|
return nil; // must be overriden by all editions
|
||||||
|
}
|
||||||
|
|
||||||
|
- (DirectoryPanel *)createDirectoryPanel
|
||||||
|
{
|
||||||
|
return [[DirectoryPanel alloc] initWithParentApp:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (DetailsPanel *)createDetailsPanel
|
||||||
|
{
|
||||||
|
return [[DetailsPanel alloc] initWithPyRef:[model detailsPanel]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)homepageURL
|
||||||
|
{
|
||||||
|
return @""; // must be overriden by all editions
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
- (ResultWindowBase *)resultWindow
|
||||||
|
{
|
||||||
|
return _resultWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (DirectoryPanel *)directoryPanel
|
||||||
|
{
|
||||||
|
return _directoryPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (DetailsPanel *)detailsPanel
|
||||||
|
{
|
||||||
|
return _detailsPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (HSRecentFiles *)recentResults
|
||||||
|
{
|
||||||
|
return _recentResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSMenu *)columnsMenu { return columnsMenu; }
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
- (IBAction)loadResults:(id)sender
|
||||||
|
{
|
||||||
|
NSOpenPanel *op = [NSOpenPanel openPanel];
|
||||||
|
[op setCanChooseFiles:YES];
|
||||||
|
[op setCanChooseDirectories:NO];
|
||||||
|
[op setCanCreateDirectories:NO];
|
||||||
|
[op setAllowsMultipleSelection:NO];
|
||||||
|
[op setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
|
||||||
|
[op setTitle:TR(@"Select a results file to load")];
|
||||||
|
if ([op runModal] == NSOKButton) {
|
||||||
|
NSString *filename = [[op filenames] objectAtIndex:0];
|
||||||
|
[model loadResultsFrom:filename];
|
||||||
|
[[self recentResults] addFile:filename];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)openWebsite:(id)sender
|
||||||
|
{
|
||||||
|
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[self homepageURL]]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)openHelp:(id)sender
|
||||||
|
{
|
||||||
|
NSBundle *b = [NSBundle mainBundle];
|
||||||
|
NSString *p = [b pathForResource:@"index" ofType:@"html" inDirectory:@"help"];
|
||||||
|
NSURL *u = [NSURL fileURLWithPath:p];
|
||||||
|
[[NSWorkspace sharedWorkspace] openURL:u];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)showAboutBox:(id)sender
|
||||||
|
{
|
||||||
|
if (_aboutBox == nil) {
|
||||||
|
_aboutBox = [[HSAboutBox alloc] initWithApp:model];
|
||||||
|
}
|
||||||
|
[[_aboutBox window] makeKeyAndOrderFront:sender];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)showDirectoryWindow:(id)sender
|
||||||
|
{
|
||||||
|
[[[self directoryPanel] window] makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)showPreferencesPanel:(id)sender
|
||||||
|
{
|
||||||
|
if (_preferencesPanel == nil) {
|
||||||
|
_preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
|
||||||
|
}
|
||||||
|
[_preferencesPanel showWindow:sender];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)showResultWindow:(id)sender
|
||||||
|
{
|
||||||
|
[[[self resultWindow] window] makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)showIgnoreList:(id)sender
|
||||||
|
{
|
||||||
|
[model showIgnoreList];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)startScanning:(id)sender
|
||||||
|
{
|
||||||
|
[[self resultWindow] startDuplicateScan:sender];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
[[ProgressController mainProgressController] setWorker:model];
|
||||||
|
[model initialRegistrationSetup];
|
||||||
|
[model loadSession];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
if (![[[self directoryPanel] window] isVisible]) {
|
||||||
|
[[self directoryPanel] showWindow:NSApp];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
|
||||||
|
{
|
||||||
|
if ([model resultsAreModified]) {
|
||||||
|
NSString *msg = TR(@"You have unsaved results, do you really want to quit?");
|
||||||
|
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) { // NO
|
||||||
|
return NSTerminateCancel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NSTerminateNow;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillTerminate:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
NSInteger sc = [ud integerForKey:@"sessionCountSinceLastIgnorePurge"];
|
||||||
|
if (sc >= 10) {
|
||||||
|
sc = -1;
|
||||||
|
[model purgeIgnoreList];
|
||||||
|
}
|
||||||
|
sc++;
|
||||||
|
[model saveSession];
|
||||||
|
[ud setInteger:sc forKey:@"sessionCountSinceLastIgnorePurge"];
|
||||||
|
// NSApplication does not release nib instances objects, we must do it manually
|
||||||
|
// Well, it isn't needed because the memory is freed anyway (we are quitting the application
|
||||||
|
// But I need to release HSRecentFiles so it saves the user defaults
|
||||||
|
[_directoryPanel release];
|
||||||
|
[_recentResults release];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)recentFileClicked:(NSString *)path
|
||||||
|
{
|
||||||
|
[model loadResultsFrom:path];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* model --> view */
|
||||||
|
- (void)showMessage:(NSString *)msg
|
||||||
|
{
|
||||||
|
[Dialogs showMessage:msg];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)askYesNoWithPrompt:(NSString *)prompt
|
||||||
|
{
|
||||||
|
return [Dialogs askYesNo:prompt] == NSAlertFirstButtonReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showProblemDialog
|
||||||
|
{
|
||||||
|
[[self resultWindow] showProblemDialog];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setupAsRegistered
|
||||||
|
{
|
||||||
|
// Nothing to do.
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showFairwareNagWithPrompt:(NSString *)prompt
|
||||||
|
{
|
||||||
|
[HSFairwareReminder showFairwareNagWithApp:[self model] prompt:prompt];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showDemoNagWithPrompt:(NSString *)prompt
|
||||||
|
{
|
||||||
|
[HSFairwareReminder showDemoNagWithApp:[self model] prompt:prompt];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)selectDestFolderWithPrompt:(NSString *)prompt
|
||||||
|
{
|
||||||
|
NSOpenPanel *op = [NSOpenPanel openPanel];
|
||||||
|
[op setCanChooseFiles:NO];
|
||||||
|
[op setCanChooseDirectories:YES];
|
||||||
|
[op setCanCreateDirectories:YES];
|
||||||
|
[op setAllowsMultipleSelection:NO];
|
||||||
|
[op setTitle:prompt];
|
||||||
|
if ([op runModal] == NSOKButton) {
|
||||||
|
return [[op filenames] objectAtIndex:0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
21
cocoa/base/Consts.h
Normal file
21
cocoa/base/Consts.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "HSConsts.h"
|
||||||
|
|
||||||
|
#define JobStarted @"JobStarted"
|
||||||
|
#define JobInProgress @"JobInProgress"
|
||||||
|
#define TableFontSize @"TableFontSize"
|
||||||
|
|
||||||
|
#define jobLoad @"job_load"
|
||||||
|
#define jobScan @"job_scan"
|
||||||
|
#define jobCopy @"job_copy"
|
||||||
|
#define jobMove @"job_move"
|
||||||
|
#define jobDelete @"job_delete"
|
||||||
|
|
||||||
|
#define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType"
|
||||||
27
cocoa/base/DetailsPanel.h
Normal file
27
cocoa/base/DetailsPanel.h
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <Python.h>
|
||||||
|
#import "PyDetailsPanel.h"
|
||||||
|
|
||||||
|
@interface DetailsPanel : NSWindowController
|
||||||
|
{
|
||||||
|
IBOutlet NSTableView *detailsTable;
|
||||||
|
|
||||||
|
PyDetailsPanel *model;
|
||||||
|
}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||||
|
- (PyDetailsPanel *)model;
|
||||||
|
|
||||||
|
- (BOOL)isVisible;
|
||||||
|
- (void)toggleVisibility;
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)refresh;
|
||||||
|
@end
|
||||||
72
cocoa/base/DetailsPanel.m
Normal file
72
cocoa/base/DetailsPanel.m
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "DetailsPanel.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
|
||||||
|
@implementation DetailsPanel
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||||
|
{
|
||||||
|
self = [super initWithWindowNibName:@"DetailsPanel"];
|
||||||
|
[self window]; //So the detailsTable is initialized.
|
||||||
|
model = [[PyDetailsPanel alloc] initWithModel:aPyRef];
|
||||||
|
[model bindCallback:createCallback(@"DetailsPanelView", self)];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[model release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyDetailsPanel *)model
|
||||||
|
{
|
||||||
|
return (PyDetailsPanel *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)refreshDetails
|
||||||
|
{
|
||||||
|
[detailsTable reloadData];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isVisible
|
||||||
|
{
|
||||||
|
return [[self window] isVisible];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)toggleVisibility
|
||||||
|
{
|
||||||
|
if ([self isVisible]) {
|
||||||
|
[[self window] close];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[self refreshDetails]; // selection might have changed since last time
|
||||||
|
[[self window] orderFront:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NSTableView Delegate */
|
||||||
|
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||||
|
{
|
||||||
|
return [[self model] numberOfRows];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
return [[self model] valueForColumn:[column identifier] row:row];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)refresh
|
||||||
|
{
|
||||||
|
if ([[self window] isVisible]) {
|
||||||
|
[self refreshDetails];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
19
cocoa/base/DirectoryOutline.h
Normal file
19
cocoa/base/DirectoryOutline.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <Python.h>
|
||||||
|
#import "HSOutline.h"
|
||||||
|
#import "PyDirectoryOutline.h"
|
||||||
|
|
||||||
|
#define DGAddedFoldersNotification @"DGAddedFoldersNotification"
|
||||||
|
|
||||||
|
@interface DirectoryOutline : HSOutline {}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView;
|
||||||
|
- (PyDirectoryOutline *)model;
|
||||||
|
@end;
|
||||||
81
cocoa/base/DirectoryOutline.m
Normal file
81
cocoa/base/DirectoryOutline.m
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "DirectoryOutline.h"
|
||||||
|
|
||||||
|
@implementation DirectoryOutline
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView
|
||||||
|
{
|
||||||
|
self = [super initWithPyRef:aPyRef wrapperClass:[PyDirectoryOutline class]
|
||||||
|
callbackClassName:@"DirectoryOutlineView" view:aOutlineView];
|
||||||
|
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyDirectoryOutline *)model
|
||||||
|
{
|
||||||
|
return (PyDirectoryOutline *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
|
||||||
|
{
|
||||||
|
NSPasteboard *pboard;
|
||||||
|
NSDragOperation sourceDragMask;
|
||||||
|
sourceDragMask = [info draggingSourceOperationMask];
|
||||||
|
pboard = [info draggingPasteboard];
|
||||||
|
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
|
||||||
|
if (sourceDragMask & NSDragOperationLink)
|
||||||
|
return NSDragOperationLink;
|
||||||
|
}
|
||||||
|
return NSDragOperationNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
|
||||||
|
{
|
||||||
|
NSPasteboard *pboard;
|
||||||
|
NSDragOperation sourceDragMask;
|
||||||
|
sourceDragMask = [info draggingSourceOperationMask];
|
||||||
|
pboard = [info draggingPasteboard];
|
||||||
|
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
|
||||||
|
NSArray *foldernames = [pboard propertyListForType:NSFilenamesPboardType];
|
||||||
|
if (!(sourceDragMask & NSDragOperationLink))
|
||||||
|
return NO;
|
||||||
|
for (NSString *foldername in foldernames) {
|
||||||
|
[[self model] addDirectory:foldername];
|
||||||
|
}
|
||||||
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:foldernames forKey:@"foldernames"];
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:DGAddedFoldersNotification
|
||||||
|
object:self userInfo:userInfo];
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)outlineView:(NSOutlineView *)aOutlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
||||||
|
{
|
||||||
|
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
||||||
|
NSTextFieldCell *textCell = cell;
|
||||||
|
NSIndexPath *path = item;
|
||||||
|
BOOL selected = [path isEqualTo:[[self view] selectedPath]];
|
||||||
|
if (selected) {
|
||||||
|
[textCell setTextColor:[NSColor blackColor]];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSInteger state = [self intProperty:@"state" valueAtPath:path];
|
||||||
|
if (state == 1) {
|
||||||
|
[textCell setTextColor:[NSColor blueColor]];
|
||||||
|
}
|
||||||
|
else if (state == 2) {
|
||||||
|
[textCell setTextColor:[NSColor redColor]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[textCell setTextColor:[NSColor blackColor]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
43
cocoa/base/DirectoryPanel.h
Normal file
43
cocoa/base/DirectoryPanel.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "HSOutlineView.h"
|
||||||
|
#import "HSRecentFiles.h"
|
||||||
|
#import "DirectoryOutline.h"
|
||||||
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
|
@class AppDelegateBase;
|
||||||
|
|
||||||
|
@interface DirectoryPanel : NSWindowController <NSOpenSavePanelDelegate>
|
||||||
|
{
|
||||||
|
IBOutlet NSPopUpButton *addButtonPopUp;
|
||||||
|
IBOutlet NSPopUpButton *loadRecentButtonPopUp;
|
||||||
|
IBOutlet HSOutlineView *outlineView;
|
||||||
|
IBOutlet NSButton *removeButton;
|
||||||
|
IBOutlet NSButton *loadResultsButton;
|
||||||
|
|
||||||
|
AppDelegateBase *_app;
|
||||||
|
PyDupeGuru *model;
|
||||||
|
HSRecentFiles *_recentDirectories;
|
||||||
|
DirectoryOutline *outline;
|
||||||
|
BOOL _alwaysShowPopUp;
|
||||||
|
}
|
||||||
|
- (id)initWithParentApp:(AppDelegateBase *)aParentApp;
|
||||||
|
|
||||||
|
- (void)fillPopUpMenu; // Virtual
|
||||||
|
- (void)adjustUIToLocalization;
|
||||||
|
|
||||||
|
- (IBAction)askForDirectory:(id)sender;
|
||||||
|
- (IBAction)popupAddDirectoryMenu:(id)sender;
|
||||||
|
- (IBAction)popupLoadRecentMenu:(id)sender;
|
||||||
|
- (IBAction)removeSelectedDirectory:(id)sender;
|
||||||
|
|
||||||
|
- (void)addDirectory:(NSString *)directory;
|
||||||
|
- (void)refreshRemoveButtonText;
|
||||||
|
@end
|
||||||
192
cocoa/base/DirectoryPanel.m
Normal file
192
cocoa/base/DirectoryPanel.m
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "DirectoryPanel.h"
|
||||||
|
#import "Dialogs.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "Consts.h"
|
||||||
|
|
||||||
|
@implementation DirectoryPanel
|
||||||
|
- (id)initWithParentApp:(AppDelegateBase *)aParentApp
|
||||||
|
{
|
||||||
|
self = [super initWithWindowNibName:@"DirectoryPanel"];
|
||||||
|
[self window];
|
||||||
|
_app = aParentApp;
|
||||||
|
model = [_app model];
|
||||||
|
[[self window] setTitle:[model appName]];
|
||||||
|
_alwaysShowPopUp = NO;
|
||||||
|
[self fillPopUpMenu];
|
||||||
|
_recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]];
|
||||||
|
[_recentDirectories setDelegate:self];
|
||||||
|
outline = [[DirectoryOutline alloc] initWithPyRef:[model directoryTree] outlineView:outlineView];
|
||||||
|
[self refreshRemoveButtonText];
|
||||||
|
[self adjustUIToLocalization];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:)
|
||||||
|
name:NSOutlineViewSelectionDidChangeNotification object:outlineView];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(outlineAddedFolders:)
|
||||||
|
name:DGAddedFoldersNotification object:outline];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[outline release];
|
||||||
|
[_recentDirectories release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
|
||||||
|
- (void)fillPopUpMenu
|
||||||
|
{
|
||||||
|
NSMenu *m = [addButtonPopUp menu];
|
||||||
|
NSMenuItem *mi = [m addItemWithTitle:TR(@"Add New Folder...") action:@selector(askForDirectory:) keyEquivalent:@""];
|
||||||
|
[mi setTarget:self];
|
||||||
|
[m addItem:[NSMenuItem separatorItem]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)adjustUIToLocalization
|
||||||
|
{
|
||||||
|
NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
|
||||||
|
NSInteger loadResultsWidthDelta = 0;
|
||||||
|
if ([lang isEqual:@"ru"]) {
|
||||||
|
loadResultsWidthDelta = 50;
|
||||||
|
}
|
||||||
|
else if ([lang isEqual:@"uk"]) {
|
||||||
|
loadResultsWidthDelta = 70;
|
||||||
|
}
|
||||||
|
else if ([lang isEqual:@"hy"]) {
|
||||||
|
loadResultsWidthDelta = 30;
|
||||||
|
}
|
||||||
|
if (loadResultsWidthDelta) {
|
||||||
|
NSRect r = [loadResultsButton frame];
|
||||||
|
r.size.width += loadResultsWidthDelta;
|
||||||
|
r.origin.x -= loadResultsWidthDelta;
|
||||||
|
[loadResultsButton setFrame:r];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
|
||||||
|
- (IBAction)askForDirectory:(id)sender
|
||||||
|
{
|
||||||
|
NSOpenPanel *op = [NSOpenPanel openPanel];
|
||||||
|
[op setCanChooseFiles:YES];
|
||||||
|
[op setCanChooseDirectories:YES];
|
||||||
|
[op setAllowsMultipleSelection:YES];
|
||||||
|
[op setTitle:TR(@"Select a folder to add to the scanning list")];
|
||||||
|
[op setDelegate:self];
|
||||||
|
if ([op runModal] == NSOKButton) {
|
||||||
|
for (NSString *directory in [op filenames]) {
|
||||||
|
[self addDirectory:directory];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)popupAddDirectoryMenu:(id)sender
|
||||||
|
{
|
||||||
|
if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) {
|
||||||
|
[self askForDirectory:sender];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[addButtonPopUp selectItem:nil];
|
||||||
|
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)popupLoadRecentMenu:(id)sender
|
||||||
|
{
|
||||||
|
if ([[[_app recentResults] filepaths] count] > 0) {
|
||||||
|
NSMenu *m = [loadRecentButtonPopUp menu];
|
||||||
|
while ([m numberOfItems] > 0) {
|
||||||
|
[m removeItemAtIndex:0];
|
||||||
|
}
|
||||||
|
NSMenuItem *mi = [m addItemWithTitle:TR(@"Load from file...") action:@selector(loadResults:) keyEquivalent:@""];
|
||||||
|
[mi setTarget:_app];
|
||||||
|
[m addItem:[NSMenuItem separatorItem]];
|
||||||
|
[[_app recentResults] fillMenu:m];
|
||||||
|
[loadRecentButtonPopUp selectItem:nil];
|
||||||
|
[[loadRecentButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[_app loadResults:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)removeSelectedDirectory:(id)sender
|
||||||
|
{
|
||||||
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
|
[[outline model] removeSelectedDirectory];
|
||||||
|
[self refreshRemoveButtonText];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
- (void)addDirectory:(NSString *)directory
|
||||||
|
{
|
||||||
|
NSInteger r = [model addDirectory:directory];
|
||||||
|
if (r) {
|
||||||
|
NSString *m = @"";
|
||||||
|
if (r == 1) {
|
||||||
|
m = TR(@"'%@' already is in the list.");
|
||||||
|
}
|
||||||
|
else if (r == 2) {
|
||||||
|
m = TR(@"'%@' does not exist.");
|
||||||
|
}
|
||||||
|
[Dialogs showMessage:[NSString stringWithFormat:m,directory]];
|
||||||
|
}
|
||||||
|
[_recentDirectories addFile:directory];
|
||||||
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)refreshRemoveButtonText
|
||||||
|
{
|
||||||
|
if ([outlineView selectedRow] < 0) {
|
||||||
|
[removeButton setEnabled:NO];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[removeButton setEnabled:YES];
|
||||||
|
NSIndexPath *path = [outline selectedIndexPath];
|
||||||
|
if (path != nil) {
|
||||||
|
NSInteger state = [outline intProperty:@"state" valueAtPath:path];
|
||||||
|
BOOL shouldDisplayArrow = ([path length] > 1) && (state == 2);
|
||||||
|
NSString *imgName = shouldDisplayArrow ? @"NSGoLeftTemplate" : @"NSRemoveTemplate";
|
||||||
|
[removeButton setImage:[NSImage imageNamed:imgName]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
|
||||||
|
{
|
||||||
|
BOOL isdir;
|
||||||
|
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isdir];
|
||||||
|
return isdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)recentFileClicked:(NSString *)path
|
||||||
|
{
|
||||||
|
[self addDirectory:path];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notifications */
|
||||||
|
|
||||||
|
- (void)directorySelectionChanged:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
[self refreshRemoveButtonText];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)outlineAddedFolders:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
NSArray *foldernames = [[aNotification userInfo] objectForKey:@"foldernames"];
|
||||||
|
for (NSString *foldername in foldernames) {
|
||||||
|
[_recentDirectories addFile:foldername];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
25
cocoa/base/IgnoreListDialog.h
Normal file
25
cocoa/base/IgnoreListDialog.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "PyIgnoreListDialog.h"
|
||||||
|
#import "HSTable.h"
|
||||||
|
|
||||||
|
@interface IgnoreListDialog : NSWindowController
|
||||||
|
{
|
||||||
|
IBOutlet NSTableView *ignoreListTableView;
|
||||||
|
|
||||||
|
PyIgnoreListDialog *model;
|
||||||
|
HSTable *ignoreListTable;
|
||||||
|
}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||||
|
|
||||||
|
- (void)initializeColumns;
|
||||||
|
- (IBAction)removeSelected:(id)sender;
|
||||||
|
- (IBAction)clear:(id)sender;
|
||||||
|
@end
|
||||||
57
cocoa/base/IgnoreListDialog.m
Normal file
57
cocoa/base/IgnoreListDialog.m
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "IgnoreListDialog.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
|
||||||
|
@implementation IgnoreListDialog
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||||
|
{
|
||||||
|
self = [super initWithWindowNibName:@"IgnoreListDialog"];
|
||||||
|
[self window]; //So the detailsTable is initialized.
|
||||||
|
model = [[PyIgnoreListDialog alloc] initWithModel:aPyRef];
|
||||||
|
[model bindCallback:createCallback(@"IgnoreListDialogView", self)];
|
||||||
|
ignoreListTable = [[HSTable alloc] initWithPyRef:[model ignoreListTable] tableView:ignoreListTableView];
|
||||||
|
[self initializeColumns];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[ignoreListTable release];
|
||||||
|
[model release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)initializeColumns
|
||||||
|
{
|
||||||
|
HSColumnDef defs[] = {
|
||||||
|
{@"path1", 240, 40, 0, NO, nil},
|
||||||
|
{@"path2", 240, 40, 0, NO, nil},
|
||||||
|
nil
|
||||||
|
};
|
||||||
|
[[ignoreListTable columns] initializeColumns:defs];
|
||||||
|
[[ignoreListTable columns] setColumnsAsReadOnly];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)removeSelected:(id)sender
|
||||||
|
{
|
||||||
|
[model removeSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)clear:(id)sender
|
||||||
|
{
|
||||||
|
[model clear];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* model --> view */
|
||||||
|
- (void)show
|
||||||
|
{
|
||||||
|
[self showWindow:self];
|
||||||
|
}
|
||||||
|
@end
|
||||||
34
cocoa/base/PrioritizeDialog.h
Normal file
34
cocoa/base/PrioritizeDialog.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "PyPrioritizeDialog.h"
|
||||||
|
#import "HSPopUpList.h"
|
||||||
|
#import "HSSelectableList.h"
|
||||||
|
#import "PrioritizeList.h"
|
||||||
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
|
@interface PrioritizeDialog : NSWindowController
|
||||||
|
{
|
||||||
|
IBOutlet NSPopUpButton *categoryPopUpView;
|
||||||
|
IBOutlet NSTableView *criteriaTableView;
|
||||||
|
IBOutlet NSTableView *prioritizationTableView;
|
||||||
|
|
||||||
|
PyPrioritizeDialog *model;
|
||||||
|
HSPopUpList *categoryPopUp;
|
||||||
|
HSSelectableList *criteriaList;
|
||||||
|
PrioritizeList *prioritizationList;
|
||||||
|
}
|
||||||
|
- (id)initWithApp:(PyDupeGuru *)aApp;
|
||||||
|
- (PyPrioritizeDialog *)model;
|
||||||
|
|
||||||
|
- (IBAction)addSelected:(id)sender;
|
||||||
|
- (IBAction)removeSelected:(id)sender;
|
||||||
|
- (IBAction)ok:(id)sender;
|
||||||
|
- (IBAction)cancel:(id)sender;
|
||||||
|
@end;
|
||||||
60
cocoa/base/PrioritizeDialog.m
Normal file
60
cocoa/base/PrioritizeDialog.m
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "PrioritizeDialog.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
|
||||||
|
@implementation PrioritizeDialog
|
||||||
|
- (id)initWithApp:(PyDupeGuru *)aApp
|
||||||
|
{
|
||||||
|
self = [super initWithWindowNibName:@"PrioritizeDialog"];
|
||||||
|
[self window];
|
||||||
|
model = [[PyPrioritizeDialog alloc] initWithApp:[aApp pyRef]];
|
||||||
|
categoryPopUp = [[HSPopUpList alloc] initWithPyRef:[[self model] categoryList] popupView:categoryPopUpView];
|
||||||
|
criteriaList = [[HSSelectableList alloc] initWithPyRef:[[self model] criteriaList] tableView:criteriaTableView];
|
||||||
|
prioritizationList = [[PrioritizeList alloc] initWithPyRef:[[self model] prioritizationList] tableView:prioritizationTableView];
|
||||||
|
[model bindCallback:createCallback(@"PrioritizeDialogView", self)];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[categoryPopUp release];
|
||||||
|
[criteriaList release];
|
||||||
|
[prioritizationList release];
|
||||||
|
[model release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyPrioritizeDialog *)model
|
||||||
|
{
|
||||||
|
return (PyPrioritizeDialog *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)addSelected:(id)sender
|
||||||
|
{
|
||||||
|
[[self model] addSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)removeSelected:(id)sender
|
||||||
|
{
|
||||||
|
[[self model] removeSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)ok:(id)sender
|
||||||
|
{
|
||||||
|
[NSApp stopModal];
|
||||||
|
[self close];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)cancel:(id)sender
|
||||||
|
{
|
||||||
|
[NSApp abortModal];
|
||||||
|
[self close];
|
||||||
|
}
|
||||||
|
@end
|
||||||
16
cocoa/base/PrioritizeList.h
Normal file
16
cocoa/base/PrioritizeList.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "HSSelectableList.h"
|
||||||
|
#import "PyPrioritizeList.h"
|
||||||
|
|
||||||
|
@interface PrioritizeList : HSSelectableList {}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView;
|
||||||
|
- (PyPrioritizeList *)model;
|
||||||
|
@end
|
||||||
58
cocoa/base/PrioritizeList.m
Normal file
58
cocoa/base/PrioritizeList.m
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "PrioritizeList.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "Consts.h"
|
||||||
|
|
||||||
|
@implementation PrioritizeList
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView
|
||||||
|
{
|
||||||
|
self = [super initWithPyRef:aPyRef wrapperClass:[PyPrioritizeList class]
|
||||||
|
callbackClassName:@"PrioritizeListView" view:aTableView];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyPrioritizeList *)model
|
||||||
|
{
|
||||||
|
return (PyPrioritizeList *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setView:(NSTableView *)aTableView
|
||||||
|
{
|
||||||
|
[super setView:aTableView];
|
||||||
|
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
|
||||||
|
{
|
||||||
|
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
|
||||||
|
[pboard declareTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType] owner:self];
|
||||||
|
[pboard setData:data forType:DGPrioritizeIndexPasteboardType];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row
|
||||||
|
proposedDropOperation:(NSTableViewDropOperation)op
|
||||||
|
{
|
||||||
|
if (op == NSTableViewDropAbove) {
|
||||||
|
return NSDragOperationMove;
|
||||||
|
}
|
||||||
|
return NSDragOperationNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info
|
||||||
|
row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
|
||||||
|
{
|
||||||
|
NSPasteboard* pboard = [info draggingPasteboard];
|
||||||
|
NSData* rowData = [pboard dataForType:DGPrioritizeIndexPasteboardType];
|
||||||
|
NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
|
||||||
|
[[self model] moveIndexes:[Utils indexSet2Array:rowIndexes] toIndex:row];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
@end
|
||||||
24
cocoa/base/ProblemDialog.h
Normal file
24
cocoa/base/ProblemDialog.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "PyProblemDialog.h"
|
||||||
|
#import "HSTable.h"
|
||||||
|
|
||||||
|
@interface ProblemDialog : NSWindowController
|
||||||
|
{
|
||||||
|
IBOutlet NSTableView *problemTableView;
|
||||||
|
|
||||||
|
PyProblemDialog *model;
|
||||||
|
HSTable *problemTable;
|
||||||
|
}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||||
|
|
||||||
|
- (void)initializeColumns;
|
||||||
|
- (IBAction)revealSelected:(id)sender;
|
||||||
|
@end
|
||||||
44
cocoa/base/ProblemDialog.m
Normal file
44
cocoa/base/ProblemDialog.m
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ProblemDialog.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
|
||||||
|
@implementation ProblemDialog
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||||
|
{
|
||||||
|
self = [super initWithWindowNibName:@"ProblemDialog"];
|
||||||
|
[self window]; //So the detailsTable is initialized.
|
||||||
|
model = [[PyProblemDialog alloc] initWithModel:aPyRef];
|
||||||
|
problemTable = [[HSTable alloc] initWithPyRef:[model problemTable] tableView:problemTableView];
|
||||||
|
[self initializeColumns];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[problemTable release];
|
||||||
|
[model release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)initializeColumns
|
||||||
|
{
|
||||||
|
HSColumnDef defs[] = {
|
||||||
|
{@"path", 202, 40, 0, NO, nil},
|
||||||
|
{@"msg", 228, 40, 0, NO, nil},
|
||||||
|
nil
|
||||||
|
};
|
||||||
|
[[problemTable columns] initializeColumns:defs];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)revealSelected:(id)sender
|
||||||
|
{
|
||||||
|
[model revealSelected];
|
||||||
|
}
|
||||||
|
@end
|
||||||
24
cocoa/base/ResultTable.h
Normal file
24
cocoa/base/ResultTable.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <Quartz/Quartz.h>
|
||||||
|
#import "HSTable.h"
|
||||||
|
#import "PyResultTable.h"
|
||||||
|
|
||||||
|
@interface ResultTable : HSTable <QLPreviewPanelDataSource, QLPreviewPanelDelegate>
|
||||||
|
{
|
||||||
|
NSSet *_deltaColumns;
|
||||||
|
}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView;
|
||||||
|
- (PyResultTable *)model;
|
||||||
|
- (BOOL)powerMarkerMode;
|
||||||
|
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
|
||||||
|
- (BOOL)deltaValuesMode;
|
||||||
|
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
|
||||||
|
@end;
|
||||||
190
cocoa/base/ResultTable.m
Normal file
190
cocoa/base/ResultTable.m
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ResultTable.h"
|
||||||
|
#import "Dialogs.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "Consts.h"
|
||||||
|
#import "HSQuicklook.h"
|
||||||
|
|
||||||
|
@interface HSTable (private)
|
||||||
|
- (void)setPySelection;
|
||||||
|
- (void)setViewSelection;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ResultTable
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView
|
||||||
|
{
|
||||||
|
self = [super initWithPyRef:aPyRef wrapperClass:[PyResultTable class] callbackClassName:@"ResultTableView" view:aTableView];
|
||||||
|
_deltaColumns = [[NSSet setWithArray:[[self model] deltaColumns]] retain];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[_deltaColumns release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyResultTable *)model
|
||||||
|
{
|
||||||
|
return (PyResultTable *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
- (void)updateQuicklookIfNeeded
|
||||||
|
{
|
||||||
|
if ([[QLPreviewPanel sharedPreviewPanel] dataSource] == self) {
|
||||||
|
[[QLPreviewPanel sharedPreviewPanel] reloadData];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPySelection
|
||||||
|
{
|
||||||
|
[super setPySelection];
|
||||||
|
[self updateQuicklookIfNeeded];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setViewSelection
|
||||||
|
{
|
||||||
|
[super setViewSelection];
|
||||||
|
[self updateQuicklookIfNeeded];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
- (BOOL)powerMarkerMode
|
||||||
|
{
|
||||||
|
return [[self model] powerMarkerMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode
|
||||||
|
{
|
||||||
|
[[self model] setPowerMarkerMode:aPowerMarkerMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)deltaValuesMode
|
||||||
|
{
|
||||||
|
return [[self model] deltaValuesMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode
|
||||||
|
{
|
||||||
|
[[self model] setDeltaValuesMode:aDeltaValuesMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Datasource */
|
||||||
|
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
NSString *identifier = [column identifier];
|
||||||
|
if ([identifier isEqual:@"marked"]) {
|
||||||
|
return [[self model] valueForColumn:@"marked" row:row];
|
||||||
|
}
|
||||||
|
return [[self model] valueForRow:row column:identifier];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
NSString *identifier = [column identifier];
|
||||||
|
if ([identifier isEqual:@"marked"]) {
|
||||||
|
[[self model] setValue:object forColumn:identifier row:row];
|
||||||
|
}
|
||||||
|
else if ([identifier isEqual:@"name"]) {
|
||||||
|
NSString *oldName = [[self model] valueForRow:row column:identifier];
|
||||||
|
NSString *newName = object;
|
||||||
|
if (![newName isEqual:oldName]) {
|
||||||
|
BOOL renamed = [[self model] renameSelected:newName];
|
||||||
|
if (!renamed) {
|
||||||
|
[Dialogs showMessage:[NSString stringWithFormat:TR(@"The name '%@' already exists."), newName]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[[self view] setNeedsDisplay:YES];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)tableColumn
|
||||||
|
{
|
||||||
|
if ([[[self view] sortDescriptors] count] < 1)
|
||||||
|
return;
|
||||||
|
NSSortDescriptor *sd = [[[self view] sortDescriptors] objectAtIndex:0];
|
||||||
|
[[self model] sortBy:[sd key] ascending:[sd ascending]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
BOOL isSelected = [[self view] isRowSelected:row];
|
||||||
|
BOOL isMarkable = n2b([[self model] valueForColumn:@"markable" row:row]);
|
||||||
|
if ([[column identifier] isEqual:@"marked"]) {
|
||||||
|
[cell setEnabled:isMarkable];
|
||||||
|
// Low-tech solution, for indentation, but it works...
|
||||||
|
NSCellImagePosition pos = isMarkable ? NSImageRight : NSImageLeft;
|
||||||
|
[cell setImagePosition:pos];
|
||||||
|
}
|
||||||
|
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
||||||
|
NSColor *color = [NSColor textColor];
|
||||||
|
if (isSelected) {
|
||||||
|
color = [NSColor selectedTextColor];
|
||||||
|
}
|
||||||
|
else if (isMarkable) {
|
||||||
|
if ([self deltaValuesMode]) {
|
||||||
|
if ([_deltaColumns containsObject:[column identifier]]) {
|
||||||
|
color = [NSColor orangeColor];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
color = [NSColor blueColor];
|
||||||
|
}
|
||||||
|
[(NSTextFieldCell *)cell setTextColor:color];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tableViewHadDeletePressed:(NSTableView *)tableView
|
||||||
|
{
|
||||||
|
[[self model] removeSelected];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tableViewHadSpacePressed:(NSTableView *)tableView
|
||||||
|
{
|
||||||
|
[[self model] markSelected];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quicklook */
|
||||||
|
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel
|
||||||
|
{
|
||||||
|
return [[[self model] selectedRows] count];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index
|
||||||
|
{
|
||||||
|
NSArray *selectedRows = [[self model] selectedRows];
|
||||||
|
NSInteger absIndex = n2i([selectedRows objectAtIndex:index]);
|
||||||
|
NSString *path = [[self model] pathAtIndex:absIndex];
|
||||||
|
return [[HSQLPreviewItem alloc] initWithUrl:[NSURL fileURLWithPath:path] title:path];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event
|
||||||
|
{
|
||||||
|
// redirect all key down events to the table view
|
||||||
|
if ([event type] == NSKeyDown) {
|
||||||
|
[[self view] keyDown:event];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)invalidateMarkings
|
||||||
|
{
|
||||||
|
[[self view] setNeedsDisplay:YES];
|
||||||
|
}
|
||||||
|
@end
|
||||||
79
cocoa/base/ResultWindow.h
Normal file
79
cocoa/base/ResultWindow.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <Quartz/Quartz.h>
|
||||||
|
#import "StatsLabel.h"
|
||||||
|
#import "ResultTable.h"
|
||||||
|
#import "ProblemDialog.h"
|
||||||
|
#import "HSTableView.h"
|
||||||
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
|
@class AppDelegateBase;
|
||||||
|
|
||||||
|
@interface ResultWindowBase : NSWindowController
|
||||||
|
{
|
||||||
|
@protected
|
||||||
|
IBOutlet NSSegmentedControl *optionsSwitch;
|
||||||
|
IBOutlet NSToolbarItem *optionsToolbarItem;
|
||||||
|
IBOutlet HSTableView *matches;
|
||||||
|
IBOutlet NSTextField *stats;
|
||||||
|
IBOutlet NSSearchField *filterField;
|
||||||
|
|
||||||
|
AppDelegateBase *app;
|
||||||
|
PyDupeGuru *model;
|
||||||
|
NSMenu *columnsMenu;
|
||||||
|
ResultTable *table;
|
||||||
|
StatsLabel *statsLabel;
|
||||||
|
ProblemDialog *problemDialog;
|
||||||
|
QLPreviewPanel* previewPanel;
|
||||||
|
}
|
||||||
|
- (id)initWithParentApp:(AppDelegateBase *)app;
|
||||||
|
|
||||||
|
/* Virtual */
|
||||||
|
- (void)initResultColumns;
|
||||||
|
- (void)setScanOptions;
|
||||||
|
|
||||||
|
/* Helpers */
|
||||||
|
- (void)fillColumnsMenu;
|
||||||
|
- (void)sendMarkedToTrash:(BOOL)hardlinkDeleted;
|
||||||
|
- (void)updateOptionSegments;
|
||||||
|
- (void)showProblemDialog;
|
||||||
|
- (void)adjustUIToLocalization;
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
- (IBAction)changeOptions:(id)sender;
|
||||||
|
- (IBAction)copyMarked:(id)sender;
|
||||||
|
- (IBAction)deleteMarked:(id)sender;
|
||||||
|
- (IBAction)hardlinkMarked:(id)sender;
|
||||||
|
- (IBAction)exportToXHTML:(id)sender;
|
||||||
|
- (IBAction)filter:(id)sender;
|
||||||
|
- (IBAction)ignoreSelected:(id)sender;
|
||||||
|
- (IBAction)invokeCustomCommand:(id)sender;
|
||||||
|
- (IBAction)markAll:(id)sender;
|
||||||
|
- (IBAction)markInvert:(id)sender;
|
||||||
|
- (IBAction)markNone:(id)sender;
|
||||||
|
- (IBAction)markSelected:(id)sender;
|
||||||
|
- (IBAction)moveMarked:(id)sender;
|
||||||
|
- (IBAction)openClicked:(id)sender;
|
||||||
|
- (IBAction)openSelected:(id)sender;
|
||||||
|
- (IBAction)removeMarked:(id)sender;
|
||||||
|
- (IBAction)removeSelected:(id)sender;
|
||||||
|
- (IBAction)renameSelected:(id)sender;
|
||||||
|
- (IBAction)reprioritizeResults:(id)sender;
|
||||||
|
- (IBAction)resetColumnsToDefault:(id)sender;
|
||||||
|
- (IBAction)revealSelected:(id)sender;
|
||||||
|
- (IBAction)saveResults:(id)sender;
|
||||||
|
- (IBAction)startDuplicateScan:(id)sender;
|
||||||
|
- (IBAction)switchSelected:(id)sender;
|
||||||
|
- (IBAction)toggleColumn:(id)sender;
|
||||||
|
- (IBAction)toggleDelta:(id)sender;
|
||||||
|
- (IBAction)toggleDetailsPanel:(id)sender;
|
||||||
|
- (IBAction)togglePowerMarker:(id)sender;
|
||||||
|
- (IBAction)toggleQuicklookPanel:(id)sender;
|
||||||
|
@end
|
||||||
372
cocoa/base/ResultWindow.m
Normal file
372
cocoa/base/ResultWindow.m
Normal file
@@ -0,0 +1,372 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ResultWindow.h"
|
||||||
|
#import "Dialogs.h"
|
||||||
|
#import "ProgressController.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "Consts.h"
|
||||||
|
#import "PrioritizeDialog.h"
|
||||||
|
|
||||||
|
@implementation ResultWindowBase
|
||||||
|
- (id)initWithParentApp:(AppDelegateBase *)aApp;
|
||||||
|
{
|
||||||
|
self = [super initWithWindowNibName:@"ResultWindow"];
|
||||||
|
app = aApp;
|
||||||
|
model = [app model];
|
||||||
|
[[self window] setTitle:fmt(@"%@ Results", [model appName])];
|
||||||
|
columnsMenu = [app columnsMenu];
|
||||||
|
/* Put a cute iTunes-like bottom bar */
|
||||||
|
[[self window] setContentBorderThickness:28 forEdge:NSMinYEdge];
|
||||||
|
table = [[ResultTable alloc] initWithPyRef:[model resultTable] view:matches];
|
||||||
|
statsLabel = [[StatsLabel alloc] initWithPyRef:[model statsLabel] view:stats];
|
||||||
|
problemDialog = [[ProblemDialog alloc] initWithPyRef:[model problemDialog]];
|
||||||
|
[self initResultColumns];
|
||||||
|
[self fillColumnsMenu];
|
||||||
|
[matches setTarget:self];
|
||||||
|
[matches setDoubleAction:@selector(openClicked:)];
|
||||||
|
[self adjustUIToLocalization];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobInProgress:) name:JobInProgress object:nil];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[table release];
|
||||||
|
[statsLabel release];
|
||||||
|
[problemDialog release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Virtual */
|
||||||
|
- (void)initResultColumns
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setScanOptions
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helpers */
|
||||||
|
- (void)fillColumnsMenu
|
||||||
|
{
|
||||||
|
NSArray *menuItems = [[[table columns] model] menuItems];
|
||||||
|
for (NSInteger i=0; i < [menuItems count]; i++) {
|
||||||
|
NSArray *pair = [menuItems objectAtIndex:i];
|
||||||
|
NSString *display = [pair objectAtIndex:0];
|
||||||
|
BOOL marked = n2b([pair objectAtIndex:1]);
|
||||||
|
NSMenuItem *mi = [columnsMenu addItemWithTitle:display action:@selector(toggleColumn:) keyEquivalent:@""];
|
||||||
|
[mi setTarget:self];
|
||||||
|
[mi setState:marked ? NSOnState : NSOffState];
|
||||||
|
[mi setTag:i];
|
||||||
|
}
|
||||||
|
[columnsMenu addItem:[NSMenuItem separatorItem]];
|
||||||
|
NSMenuItem *mi = [columnsMenu addItemWithTitle:TR(@"Reset to Default")
|
||||||
|
action:@selector(resetColumnsToDefault:) keyEquivalent:@""];
|
||||||
|
[mi setTarget:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)sendMarkedToTrash:(BOOL)hardlinkDeleted
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
||||||
|
if (hardlinkDeleted) {
|
||||||
|
[model hardlinkMarked];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[model deleteMarked];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateOptionSegments
|
||||||
|
{
|
||||||
|
[optionsSwitch setSelected:[[app detailsPanel] isVisible] forSegment:0];
|
||||||
|
[optionsSwitch setSelected:[table powerMarkerMode] forSegment:1];
|
||||||
|
[optionsSwitch setSelected:[table deltaValuesMode] forSegment:2];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showProblemDialog
|
||||||
|
{
|
||||||
|
[problemDialog showWindow:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)adjustUIToLocalization
|
||||||
|
{
|
||||||
|
NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
|
||||||
|
NSInteger seg1delta = 0;
|
||||||
|
NSInteger seg2delta = 0;
|
||||||
|
if ([lang isEqual:@"ru"]) {
|
||||||
|
seg2delta = 20;
|
||||||
|
}
|
||||||
|
else if ([lang isEqual:@"uk"]) {
|
||||||
|
seg2delta = 20;
|
||||||
|
}
|
||||||
|
else if ([lang isEqual:@"hy"]) {
|
||||||
|
seg1delta = 20;
|
||||||
|
}
|
||||||
|
if (seg1delta || seg2delta) {
|
||||||
|
[optionsSwitch setWidth:[optionsSwitch widthForSegment:0]+seg1delta forSegment:0];
|
||||||
|
[optionsSwitch setWidth:[optionsSwitch widthForSegment:1]+seg2delta forSegment:1];
|
||||||
|
NSSize s = [optionsToolbarItem maxSize];
|
||||||
|
s.width += seg1delta + seg2delta;
|
||||||
|
[optionsToolbarItem setMaxSize:s];
|
||||||
|
[optionsToolbarItem setMinSize:s];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
- (IBAction)changeOptions:(id)sender
|
||||||
|
{
|
||||||
|
NSInteger seg = [optionsSwitch selectedSegment];
|
||||||
|
if (seg == 0) {
|
||||||
|
[self toggleDetailsPanel:sender];
|
||||||
|
}
|
||||||
|
else if (seg == 1) {
|
||||||
|
[self togglePowerMarker:sender];
|
||||||
|
}
|
||||||
|
else if (seg == 2) {
|
||||||
|
[self toggleDelta:sender];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)copyMarked:(id)sender
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
||||||
|
[model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])];
|
||||||
|
[model copyMarked];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)deleteMarked:(id)sender
|
||||||
|
{
|
||||||
|
[self sendMarkedToTrash:NO];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)hardlinkMarked:(id)sender
|
||||||
|
{
|
||||||
|
[self sendMarkedToTrash:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)exportToXHTML:(id)sender
|
||||||
|
{
|
||||||
|
NSString *exported = [model exportToXHTML];
|
||||||
|
[[NSWorkspace sharedWorkspace] openFile:exported];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)filter:(id)sender
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[model setEscapeFilterRegexp:!n2b([ud objectForKey:@"useRegexpFilter"])];
|
||||||
|
[model applyFilter:[filterField stringValue]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)ignoreSelected:(id)sender
|
||||||
|
{
|
||||||
|
[model addSelectedToIgnoreList];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)invokeCustomCommand:(id)sender
|
||||||
|
{
|
||||||
|
[model invokeCustomCommand];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)markAll:(id)sender
|
||||||
|
{
|
||||||
|
[model markAll];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)markInvert:(id)sender
|
||||||
|
{
|
||||||
|
[model markInvert];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)markNone:(id)sender
|
||||||
|
{
|
||||||
|
[model markNone];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)markSelected:(id)sender
|
||||||
|
{
|
||||||
|
[model toggleSelectedMark];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)moveMarked:(id)sender
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
||||||
|
[model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])];
|
||||||
|
[model moveMarked];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)openClicked:(id)sender
|
||||||
|
{
|
||||||
|
if ([matches clickedRow] < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[matches selectRowIndexes:[NSIndexSet indexSetWithIndex:[matches clickedRow]] byExtendingSelection:NO];
|
||||||
|
[model openSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)openSelected:(id)sender
|
||||||
|
{
|
||||||
|
[model openSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)removeMarked:(id)sender
|
||||||
|
{
|
||||||
|
[model removeMarked];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)removeSelected:(id)sender
|
||||||
|
{
|
||||||
|
[model removeSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)renameSelected:(id)sender
|
||||||
|
{
|
||||||
|
NSInteger col = [matches columnWithIdentifier:@"0"];
|
||||||
|
NSInteger row = [matches selectedRow];
|
||||||
|
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)reprioritizeResults:(id)sender
|
||||||
|
{
|
||||||
|
PrioritizeDialog *dlg = [[PrioritizeDialog alloc] initWithApp:model];
|
||||||
|
NSInteger result = [NSApp runModalForWindow:[dlg window]];
|
||||||
|
if (result == NSRunStoppedResponse) {
|
||||||
|
[[dlg model] performReprioritization];
|
||||||
|
}
|
||||||
|
[dlg release];
|
||||||
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)resetColumnsToDefault:(id)sender
|
||||||
|
{
|
||||||
|
[[[table columns] model] resetToDefaults];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)revealSelected:(id)sender
|
||||||
|
{
|
||||||
|
[model revealSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)saveResults:(id)sender
|
||||||
|
{
|
||||||
|
NSSavePanel *sp = [NSSavePanel savePanel];
|
||||||
|
[sp setCanCreateDirectories:YES];
|
||||||
|
[sp setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
|
||||||
|
[sp setTitle:TR(@"Select a file to save your results to")];
|
||||||
|
if ([sp runModal] == NSOKButton) {
|
||||||
|
[model saveResultsAs:[sp filename]];
|
||||||
|
[[app recentResults] addFile:[sp filename]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)startDuplicateScan:(id)sender
|
||||||
|
{
|
||||||
|
if ([model resultsAreModified]) {
|
||||||
|
if ([Dialogs askYesNo:TR(@"You have unsaved results, do you really want to continue?")] == NSAlertSecondButtonReturn) // NO
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[self setScanOptions];
|
||||||
|
[model doScan];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)switchSelected:(id)sender
|
||||||
|
{
|
||||||
|
[model makeSelectedReference];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)toggleColumn:(id)sender
|
||||||
|
{
|
||||||
|
NSMenuItem *mi = sender;
|
||||||
|
BOOL checked = [[[table columns] model] toggleMenuItem:[mi tag]];
|
||||||
|
[mi setState:checked ? NSOnState : NSOffState];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)toggleDetailsPanel:(id)sender
|
||||||
|
{
|
||||||
|
[[app detailsPanel] toggleVisibility];
|
||||||
|
[self updateOptionSegments];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)toggleDelta:(id)sender
|
||||||
|
{
|
||||||
|
[table setDeltaValuesMode:![table deltaValuesMode]];
|
||||||
|
[self updateOptionSegments];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)togglePowerMarker:(id)sender
|
||||||
|
{
|
||||||
|
[table setPowerMarkerMode:![table powerMarkerMode]];
|
||||||
|
[self updateOptionSegments];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (IBAction)toggleQuicklookPanel:(id)sender
|
||||||
|
{
|
||||||
|
if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
|
||||||
|
[[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quicklook */
|
||||||
|
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel;
|
||||||
|
{
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
|
||||||
|
{
|
||||||
|
// This document is now responsible of the preview panel
|
||||||
|
// It is allowed to set the delegate, data source and refresh panel.
|
||||||
|
previewPanel = [panel retain];
|
||||||
|
panel.delegate = table;
|
||||||
|
panel.dataSource = table;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel
|
||||||
|
{
|
||||||
|
// This document loses its responsisibility on the preview panel
|
||||||
|
// Until the next call to -beginPreviewPanelControl: it must not
|
||||||
|
// change the panel's delegate, data source or refresh it.
|
||||||
|
[previewPanel release];
|
||||||
|
previewPanel = nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)jobInProgress:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
[Dialogs showMessage:TR(@"A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again.")];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)jobStarted:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
|
NSDictionary *ui = [aNotification userInfo];
|
||||||
|
NSString *desc = [ui valueForKey:@"desc"];
|
||||||
|
[[ProgressController mainProgressController] setJobDesc:desc];
|
||||||
|
NSString *jobid = [ui valueForKey:@"jobid"];
|
||||||
|
[[ProgressController mainProgressController] setJobId:jobid];
|
||||||
|
[[ProgressController mainProgressController] showSheetForParent:[self window]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
|
||||||
|
{
|
||||||
|
return ![[ProgressController mainProgressController] isShown];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)validateMenuItem:(NSMenuItem *)item
|
||||||
|
{
|
||||||
|
return ![[ProgressController mainProgressController] isShown];
|
||||||
|
}
|
||||||
|
@end
|
||||||
17
cocoa/base/StatsLabel.h
Normal file
17
cocoa/base/StatsLabel.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "HSGUIController.h"
|
||||||
|
#import "PyStatsLabel.h"
|
||||||
|
|
||||||
|
@interface StatsLabel : HSGUIController {}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aLabelView;
|
||||||
|
- (PyStatsLabel *)model;
|
||||||
|
- (NSTextField *)labelView;
|
||||||
|
@end
|
||||||
34
cocoa/base/StatsLabel.m
Normal file
34
cocoa/base/StatsLabel.m
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "StatsLabel.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
|
||||||
|
@implementation StatsLabel
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aLabelView
|
||||||
|
{
|
||||||
|
return [super initWithPyRef:aPyRef wrapperClass:[PyStatsLabel class]
|
||||||
|
callbackClassName:@"StatsLabelView" view:aLabelView];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyStatsLabel *)model
|
||||||
|
{
|
||||||
|
return (PyStatsLabel *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSTextField *)labelView
|
||||||
|
{
|
||||||
|
return (NSTextField *)view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)refresh
|
||||||
|
{
|
||||||
|
[[self labelView] setStringValue:[[self model] display]];
|
||||||
|
}
|
||||||
|
@end
|
||||||
12
cocoa/base/en.lproj/DetailsPanel.strings
Normal file
12
cocoa/base/en.lproj/DetailsPanel.strings
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
/* Class = "NSPanel"; title = "Details of Selected File"; ObjectID = "5"; */
|
||||||
|
"5.title" = "Details of Selected File";
|
||||||
|
|
||||||
|
/* Class = "NSTableColumn"; headerCell.title = "Selected"; ObjectID = "9"; */
|
||||||
|
"9.headerCell.title" = "Selected";
|
||||||
|
|
||||||
|
/* Class = "NSTableColumn"; headerCell.title = "Reference"; ObjectID = "10"; */
|
||||||
|
"10.headerCell.title" = "Reference";
|
||||||
|
|
||||||
|
/* Class = "NSTableColumn"; headerCell.title = "Attribute"; ObjectID = "11"; */
|
||||||
|
"11.headerCell.title" = "Attribute";
|
||||||
519
cocoa/base/en.lproj/DetailsPanel.xib
Normal file
519
cocoa/base/en.lproj/DetailsPanel.xib
Normal file
@@ -0,0 +1,519 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
|
||||||
|
<data>
|
||||||
|
<int key="IBDocument.SystemTarget">1060</int>
|
||||||
|
<string key="IBDocument.SystemVersion">11B26</string>
|
||||||
|
<string key="IBDocument.InterfaceBuilderVersion">1617</string>
|
||||||
|
<string key="IBDocument.AppKitVersion">1138</string>
|
||||||
|
<string key="IBDocument.HIToolboxVersion">566.00</string>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="NS.object.0">1617</string>
|
||||||
|
</object>
|
||||||
|
<array key="IBDocument.IntegratedClassDependencies">
|
||||||
|
<string>NSView</string>
|
||||||
|
<string>NSCustomObject</string>
|
||||||
|
<string>NSScrollView</string>
|
||||||
|
<string>NSWindowTemplate</string>
|
||||||
|
<string>NSTextFieldCell</string>
|
||||||
|
<string>NSTableHeaderView</string>
|
||||||
|
<string>NSTableColumn</string>
|
||||||
|
<string>NSScroller</string>
|
||||||
|
<string>NSTableView</string>
|
||||||
|
</array>
|
||||||
|
<array key="IBDocument.PluginDependencies">
|
||||||
|
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
</array>
|
||||||
|
<dictionary class="NSMutableDictionary" key="IBDocument.Metadata"/>
|
||||||
|
<array class="NSMutableArray" key="IBDocument.RootObjects" id="247693826">
|
||||||
|
<object class="NSCustomObject" id="449947658">
|
||||||
|
<string key="NSClassName">DetailsPanel</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSCustomObject" id="176092317">
|
||||||
|
<string key="NSClassName">FirstResponder</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSCustomObject" id="492866996">
|
||||||
|
<string key="NSClassName">NSApplication</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSWindowTemplate" id="476890502">
|
||||||
|
<int key="NSWindowStyleMask">155</int>
|
||||||
|
<int key="NSWindowBacking">2</int>
|
||||||
|
<string key="NSWindowRect">{{33, 261}, {451, 146}}</string>
|
||||||
|
<int key="NSWTFlags">-260571136</int>
|
||||||
|
<string key="NSWindowTitle">Details of Selected File</string>
|
||||||
|
<object class="NSMutableString" key="NSWindowClass">
|
||||||
|
<characters key="NS.bytes">NSPanel</characters>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableString" key="NSViewClass">
|
||||||
|
<characters key="NS.bytes">View</characters>
|
||||||
|
</object>
|
||||||
|
<nil key="NSUserInterfaceItemIdentifier"/>
|
||||||
|
<string key="NSWindowContentMinSize">{451, 146}</string>
|
||||||
|
<object class="NSView" key="NSWindowView" id="1027711962">
|
||||||
|
<reference key="NSNextResponder"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSScrollView" id="362108788">
|
||||||
|
<reference key="NSNextResponder" ref="1027711962"/>
|
||||||
|
<int key="NSvFlags">274</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSClipView" id="488480549">
|
||||||
|
<reference key="NSNextResponder" ref="362108788"/>
|
||||||
|
<int key="NSvFlags">2304</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSTableView" id="251969872">
|
||||||
|
<reference key="NSNextResponder" ref="488480549"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<string key="NSFrameSize">{449, 128}</string>
|
||||||
|
<reference key="NSSuperview" ref="488480549"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<int key="NSTag">2</int>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSTableHeaderView" key="NSHeaderView" id="374654484">
|
||||||
|
<reference key="NSNextResponder" ref="110085834"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<string key="NSFrameSize">{449, 17}</string>
|
||||||
|
<reference key="NSSuperview" ref="110085834"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSTableView" ref="251969872"/>
|
||||||
|
</object>
|
||||||
|
<object class="_NSCornerView" key="NSCornerView" id="717380566">
|
||||||
|
<reference key="NSNextResponder" ref="362108788"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{-26, 0}, {16, 17}}</string>
|
||||||
|
<reference key="NSSuperview" ref="362108788"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
</object>
|
||||||
|
<array class="NSMutableArray" key="NSTableColumns">
|
||||||
|
<object class="NSTableColumn" id="920315484">
|
||||||
|
<string key="NSIdentifier">0</string>
|
||||||
|
<double key="NSWidth">70</double>
|
||||||
|
<double key="NSMinWidth">40</double>
|
||||||
|
<double key="NSMaxWidth">1000</double>
|
||||||
|
<object class="NSTableHeaderCell" key="NSHeaderCell">
|
||||||
|
<int key="NSCellFlags">75628096</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<string key="NSContents">Attribute</string>
|
||||||
|
<object class="NSFont" key="NSSupport" id="26">
|
||||||
|
<string key="NSName">LucidaGrande</string>
|
||||||
|
<double key="NSSize">11</double>
|
||||||
|
<int key="NSfFlags">3100</int>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor" id="690482064">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC4zMzMzMzI5OQA</bytes>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSTextColor" id="427935076">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">headerTextColor</string>
|
||||||
|
<object class="NSColor" key="NSColor" id="45430568">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MAA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSTextFieldCell" key="NSDataCell" id="402714943">
|
||||||
|
<int key="NSCellFlags">337772096</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<reference key="NSSupport" ref="26"/>
|
||||||
|
<reference key="NSControlView" ref="251969872"/>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor" id="1015576963">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">controlBackgroundColor</string>
|
||||||
|
<object class="NSColor" key="NSColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSTextColor" id="183338835">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">controlTextColor</string>
|
||||||
|
<reference key="NSColor" ref="45430568"/>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<int key="NSResizingMask">2</int>
|
||||||
|
<bool key="NSIsResizeable">YES</bool>
|
||||||
|
<reference key="NSTableView" ref="251969872"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSTableColumn" id="683288430">
|
||||||
|
<string key="NSIdentifier">1</string>
|
||||||
|
<double key="NSWidth">198</double>
|
||||||
|
<double key="NSMinWidth">40</double>
|
||||||
|
<double key="NSMaxWidth">1000</double>
|
||||||
|
<object class="NSTableHeaderCell" key="NSHeaderCell">
|
||||||
|
<int key="NSCellFlags">75628096</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<string key="NSContents">Selected</string>
|
||||||
|
<reference key="NSSupport" ref="26"/>
|
||||||
|
<reference key="NSBackgroundColor" ref="690482064"/>
|
||||||
|
<reference key="NSTextColor" ref="427935076"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSTextFieldCell" key="NSDataCell" id="649788063">
|
||||||
|
<int key="NSCellFlags">337772096</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<reference key="NSSupport" ref="26"/>
|
||||||
|
<reference key="NSControlView" ref="251969872"/>
|
||||||
|
<reference key="NSBackgroundColor" ref="1015576963"/>
|
||||||
|
<reference key="NSTextColor" ref="183338835"/>
|
||||||
|
</object>
|
||||||
|
<int key="NSResizingMask">3</int>
|
||||||
|
<bool key="NSIsResizeable">YES</bool>
|
||||||
|
<reference key="NSTableView" ref="251969872"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSTableColumn" id="694550305">
|
||||||
|
<string key="NSIdentifier">2</string>
|
||||||
|
<double key="NSWidth">172</double>
|
||||||
|
<double key="NSMinWidth">56.4755859375</double>
|
||||||
|
<double key="NSMaxWidth">1000</double>
|
||||||
|
<object class="NSTableHeaderCell" key="NSHeaderCell">
|
||||||
|
<int key="NSCellFlags">75628096</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<string key="NSContents">Reference</string>
|
||||||
|
<reference key="NSSupport" ref="26"/>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">headerColor</string>
|
||||||
|
<object class="NSColor" key="NSColor" id="666582600">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MQA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<reference key="NSTextColor" ref="427935076"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSTextFieldCell" key="NSDataCell" id="915876533">
|
||||||
|
<int key="NSCellFlags">337772096</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<reference key="NSSupport" ref="26"/>
|
||||||
|
<reference key="NSControlView" ref="251969872"/>
|
||||||
|
<reference key="NSBackgroundColor" ref="1015576963"/>
|
||||||
|
<reference key="NSTextColor" ref="183338835"/>
|
||||||
|
</object>
|
||||||
|
<int key="NSResizingMask">3</int>
|
||||||
|
<bool key="NSIsResizeable">YES</bool>
|
||||||
|
<reference key="NSTableView" ref="251969872"/>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<double key="NSIntercellSpacingWidth">3</double>
|
||||||
|
<double key="NSIntercellSpacingHeight">2</double>
|
||||||
|
<reference key="NSBackgroundColor" ref="666582600"/>
|
||||||
|
<object class="NSColor" key="NSGridColor">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">gridColor</string>
|
||||||
|
<object class="NSColor" key="NSColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC41AA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<double key="NSRowHeight">14</double>
|
||||||
|
<int key="NSTvFlags">1111523328</int>
|
||||||
|
<reference key="NSDelegate"/>
|
||||||
|
<reference key="NSDataSource"/>
|
||||||
|
<int key="NSColumnAutoresizingStyle">1</int>
|
||||||
|
<int key="NSDraggingSourceMaskForLocal">15</int>
|
||||||
|
<int key="NSDraggingSourceMaskForNonLocal">0</int>
|
||||||
|
<bool key="NSAllowsTypeSelect">YES</bool>
|
||||||
|
<int key="NSTableViewDraggingDestinationStyle">0</int>
|
||||||
|
<int key="NSTableViewGroupRowStyle">1</int>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{1, 17}, {449, 128}}</string>
|
||||||
|
<reference key="NSSuperview" ref="362108788"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="251969872"/>
|
||||||
|
<reference key="NSDocView" ref="251969872"/>
|
||||||
|
<reference key="NSBGColor" ref="1015576963"/>
|
||||||
|
<int key="NScvFlags">4</int>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="733942317">
|
||||||
|
<reference key="NSNextResponder" ref="362108788"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{-30, 17}, {15, 129}}</string>
|
||||||
|
<reference key="NSSuperview" ref="362108788"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSTarget" ref="362108788"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSPercent">0.89375001192092896</double>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="168632382">
|
||||||
|
<reference key="NSNextResponder" ref="362108788"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{-100, -100}, {394, 15}}</string>
|
||||||
|
<reference key="NSSuperview" ref="362108788"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<int key="NSsFlags">1</int>
|
||||||
|
<reference key="NSTarget" ref="362108788"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSPercent">0.96332520246505737</double>
|
||||||
|
</object>
|
||||||
|
<object class="NSClipView" id="110085834">
|
||||||
|
<reference key="NSNextResponder" ref="362108788"/>
|
||||||
|
<int key="NSvFlags">2304</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<reference ref="374654484"/>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{1, 0}, {449, 17}}</string>
|
||||||
|
<reference key="NSSuperview" ref="362108788"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="374654484"/>
|
||||||
|
<reference key="NSDocView" ref="374654484"/>
|
||||||
|
<reference key="NSBGColor" ref="1015576963"/>
|
||||||
|
<int key="NScvFlags">4</int>
|
||||||
|
</object>
|
||||||
|
<reference ref="717380566"/>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrameSize">{451, 146}</string>
|
||||||
|
<reference key="NSSuperview" ref="1027711962"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="488480549"/>
|
||||||
|
<int key="NSsFlags">133650</int>
|
||||||
|
<reference key="NSVScroller" ref="733942317"/>
|
||||||
|
<reference key="NSHScroller" ref="168632382"/>
|
||||||
|
<reference key="NSContentView" ref="488480549"/>
|
||||||
|
<reference key="NSHeaderClipView" ref="110085834"/>
|
||||||
|
<reference key="NSCornerView" ref="717380566"/>
|
||||||
|
<bytes key="NSScrollAmts">QSAAAEEgAABBgAAAQYAAAA</bytes>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrameSize">{451, 146}</string>
|
||||||
|
<reference key="NSSuperview"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
</object>
|
||||||
|
<string key="NSScreenRect">{{0, 0}, {1920, 1058}}</string>
|
||||||
|
<string key="NSMinSize">{451, 162}</string>
|
||||||
|
<string key="NSMaxSize">{10000000000000, 10000000000000}</string>
|
||||||
|
<string key="NSFrameAutosaveName">DetailsPanel</string>
|
||||||
|
<bool key="NSWindowIsRestorable">YES</bool>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||||
|
<array class="NSMutableArray" key="connectionRecords">
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">window</string>
|
||||||
|
<reference key="source" ref="449947658"/>
|
||||||
|
<reference key="destination" ref="476890502"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">12</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">dataSource</string>
|
||||||
|
<reference key="source" ref="251969872"/>
|
||||||
|
<reference key="destination" ref="449947658"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">21</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">detailsTable</string>
|
||||||
|
<reference key="source" ref="449947658"/>
|
||||||
|
<reference key="destination" ref="251969872"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">22</int>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||||
|
<array key="orderedObjects">
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">0</int>
|
||||||
|
<array key="object" id="0"/>
|
||||||
|
<reference key="children" ref="247693826"/>
|
||||||
|
<nil key="parent"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-2</int>
|
||||||
|
<reference key="object" ref="449947658"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">File's Owner</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-1</int>
|
||||||
|
<reference key="object" ref="176092317"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">First Responder</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">5</int>
|
||||||
|
<reference key="object" ref="476890502"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="1027711962"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">details</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">6</int>
|
||||||
|
<reference key="object" ref="1027711962"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="362108788"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="476890502"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">7</int>
|
||||||
|
<reference key="object" ref="362108788"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="251969872"/>
|
||||||
|
<reference ref="733942317"/>
|
||||||
|
<reference ref="168632382"/>
|
||||||
|
<reference ref="374654484"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1027711962"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">8</int>
|
||||||
|
<reference key="object" ref="251969872"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="683288430"/>
|
||||||
|
<reference ref="694550305"/>
|
||||||
|
<reference ref="920315484"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="362108788"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">9</int>
|
||||||
|
<reference key="object" ref="683288430"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="649788063"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="251969872"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">10</int>
|
||||||
|
<reference key="object" ref="694550305"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="915876533"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="251969872"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">11</int>
|
||||||
|
<reference key="object" ref="920315484"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="402714943"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="251969872"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">15</int>
|
||||||
|
<reference key="object" ref="649788063"/>
|
||||||
|
<reference key="parent" ref="683288430"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">16</int>
|
||||||
|
<reference key="object" ref="915876533"/>
|
||||||
|
<reference key="parent" ref="694550305"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">17</int>
|
||||||
|
<reference key="object" ref="402714943"/>
|
||||||
|
<reference key="parent" ref="920315484"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">18</int>
|
||||||
|
<reference key="object" ref="733942317"/>
|
||||||
|
<reference key="parent" ref="362108788"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">19</int>
|
||||||
|
<reference key="object" ref="168632382"/>
|
||||||
|
<reference key="parent" ref="362108788"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">20</int>
|
||||||
|
<reference key="object" ref="374654484"/>
|
||||||
|
<reference key="parent" ref="362108788"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-3</int>
|
||||||
|
<reference key="object" ref="492866996"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">Application</string>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
</object>
|
||||||
|
<dictionary class="NSMutableDictionary" key="flattenedProperties">
|
||||||
|
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="10.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="11.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="15.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<boolean value="YES" key="15.IBShouldRemoveOnLegacySave"/>
|
||||||
|
<string key="16.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<boolean value="YES" key="16.IBShouldRemoveOnLegacySave"/>
|
||||||
|
<string key="17.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<boolean value="YES" key="17.IBShouldRemoveOnLegacySave"/>
|
||||||
|
<string key="18.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<boolean value="YES" key="18.IBShouldRemoveOnLegacySave"/>
|
||||||
|
<string key="19.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<boolean value="YES" key="19.IBShouldRemoveOnLegacySave"/>
|
||||||
|
<string key="20.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<boolean value="YES" key="20.IBShouldRemoveOnLegacySave"/>
|
||||||
|
<string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="5.IBWindowTemplateEditedContentRect">{{109, 671}, {451, 146}}</string>
|
||||||
|
<string key="6.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="7.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="8.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="9.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
</dictionary>
|
||||||
|
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
|
||||||
|
<nil key="activeLocalization"/>
|
||||||
|
<dictionary class="NSMutableDictionary" key="localizations"/>
|
||||||
|
<nil key="sourceID"/>
|
||||||
|
<int key="maxID">22</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||||
|
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||||
|
<object class="IBPartialClassDescription">
|
||||||
|
<string key="className">DetailsPanel</string>
|
||||||
|
<string key="superclassName">HSWindowController</string>
|
||||||
|
<object class="NSMutableDictionary" key="outlets">
|
||||||
|
<string key="NS.key.0">detailsTable</string>
|
||||||
|
<string key="NS.object.0">NSTableView</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||||
|
<string key="NS.key.0">detailsTable</string>
|
||||||
|
<object class="IBToOneOutletInfo" key="NS.object.0">
|
||||||
|
<string key="name">detailsTable</string>
|
||||||
|
<string key="candidateClassName">NSTableView</string>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
|
<string key="majorKey">IBProjectSource</string>
|
||||||
|
<string key="minorKey">./Classes/DetailsPanel.h</string>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="IBPartialClassDescription">
|
||||||
|
<string key="className">HSWindowController</string>
|
||||||
|
<string key="superclassName">NSWindowController</string>
|
||||||
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
|
<string key="majorKey">IBProjectSource</string>
|
||||||
|
<string key="minorKey">./Classes/HSWindowController.h</string>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
</object>
|
||||||
|
<int key="IBDocument.localizationMode">0</int>
|
||||||
|
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
|
<real value="1060" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
|
<integer value="1050" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
|
||||||
|
<real value="4100" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||||
|
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||||
|
</data>
|
||||||
|
</archive>
|
||||||
23
cocoa/base/en.lproj/DirectoryPanel.strings
Normal file
23
cocoa/base/en.lproj/DirectoryPanel.strings
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/* Class = "NSTableColumn"; headerCell.title = "State"; ObjectID = "13"; */
|
||||||
|
"13.headerCell.title" = "State";
|
||||||
|
|
||||||
|
/* Class = "NSTableColumn"; headerCell.title = "Name"; ObjectID = "15"; */
|
||||||
|
"15.headerCell.title" = "Name";
|
||||||
|
|
||||||
|
/* Class = "NSButtonCell"; title = "Scan"; ObjectID = "48"; */
|
||||||
|
"48.title" = "Scan";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Normal"; ObjectID = "55"; */
|
||||||
|
"55.title" = "Normal";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Reference"; ObjectID = "56"; */
|
||||||
|
"56.title" = "Reference";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Excluded"; ObjectID = "57"; */
|
||||||
|
"57.title" = "Excluded";
|
||||||
|
|
||||||
|
/* Class = "NSTextFieldCell"; title = "Select folders to scan and press \"Scan\"."; ObjectID = "71"; */
|
||||||
|
"71.title" = "Select folders to scan and press \"Scan\".";
|
||||||
|
|
||||||
|
/* Class = "NSButtonCell"; title = "Load Results"; ObjectID = "73"; */
|
||||||
|
"73.title" = "Load Results";
|
||||||
1083
cocoa/base/en.lproj/DirectoryPanel.xib
Normal file
1083
cocoa/base/en.lproj/DirectoryPanel.xib
Normal file
File diff suppressed because it is too large
Load Diff
12
cocoa/base/en.lproj/IgnoreListDialog.strings
Normal file
12
cocoa/base/en.lproj/IgnoreListDialog.strings
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
/* Class = "NSWindow"; title = "Ignore List"; ObjectID = "1"; */
|
||||||
|
"1.title" = "Ignore List";
|
||||||
|
|
||||||
|
/* Class = "NSButtonCell"; title = "Close"; ObjectID = "19"; */
|
||||||
|
"19.title" = "Close";
|
||||||
|
|
||||||
|
/* Class = "NSButtonCell"; title = "Remove Selected"; ObjectID = "21"; */
|
||||||
|
"21.title" = "Remove Selected";
|
||||||
|
|
||||||
|
/* Class = "NSButtonCell"; title = "Clear"; ObjectID = "28"; */
|
||||||
|
"28.title" = "Clear";
|
||||||
504
cocoa/base/en.lproj/IgnoreListDialog.xib
Normal file
504
cocoa/base/en.lproj/IgnoreListDialog.xib
Normal file
@@ -0,0 +1,504 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
|
||||||
|
<data>
|
||||||
|
<int key="IBDocument.SystemTarget">1060</int>
|
||||||
|
<string key="IBDocument.SystemVersion">11D50</string>
|
||||||
|
<string key="IBDocument.InterfaceBuilderVersion">2177</string>
|
||||||
|
<string key="IBDocument.AppKitVersion">1138.32</string>
|
||||||
|
<string key="IBDocument.HIToolboxVersion">568.00</string>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="NS.object.0">2177</string>
|
||||||
|
</object>
|
||||||
|
<array key="IBDocument.IntegratedClassDependencies">
|
||||||
|
<string>NSView</string>
|
||||||
|
<string>NSTableView</string>
|
||||||
|
<string>NSScrollView</string>
|
||||||
|
<string>NSWindowTemplate</string>
|
||||||
|
<string>NSTableHeaderView</string>
|
||||||
|
<string>NSButtonCell</string>
|
||||||
|
<string>NSButton</string>
|
||||||
|
<string>NSScroller</string>
|
||||||
|
<string>NSCustomObject</string>
|
||||||
|
</array>
|
||||||
|
<array key="IBDocument.PluginDependencies">
|
||||||
|
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
</array>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.Metadata">
|
||||||
|
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
|
||||||
|
<integer value="1" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
|
||||||
|
<object class="NSCustomObject" id="1001">
|
||||||
|
<string key="NSClassName">IgnoreListDialog</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSCustomObject" id="1003">
|
||||||
|
<string key="NSClassName">FirstResponder</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSCustomObject" id="1004">
|
||||||
|
<string key="NSClassName">NSApplication</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSWindowTemplate" id="359561441">
|
||||||
|
<int key="NSWindowStyleMask">11</int>
|
||||||
|
<int key="NSWindowBacking">2</int>
|
||||||
|
<string key="NSWindowRect">{{477, 306}, {574, 347}}</string>
|
||||||
|
<int key="NSWTFlags">1685585920</int>
|
||||||
|
<string key="NSWindowTitle">Ignore List</string>
|
||||||
|
<string key="NSWindowClass">NSWindow</string>
|
||||||
|
<nil key="NSViewClass"/>
|
||||||
|
<nil key="NSUserInterfaceItemIdentifier"/>
|
||||||
|
<object class="NSView" key="NSWindowView" id="976198330">
|
||||||
|
<reference key="NSNextResponder"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSScrollView" id="458371270">
|
||||||
|
<reference key="NSNextResponder" ref="976198330"/>
|
||||||
|
<int key="NSvFlags">274</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSClipView" id="831830981">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">2304</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSTableView" id="252791348">
|
||||||
|
<reference key="NSNextResponder" ref="831830981"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<string key="NSFrameSize">{532, 211}</string>
|
||||||
|
<reference key="NSSuperview" ref="831830981"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="777677330"/>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSTableHeaderView" key="NSHeaderView" id="903452051">
|
||||||
|
<reference key="NSNextResponder" ref="777677330"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<string key="NSFrameSize">{532, 17}</string>
|
||||||
|
<reference key="NSSuperview" ref="777677330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="564034022"/>
|
||||||
|
<reference key="NSTableView" ref="252791348"/>
|
||||||
|
</object>
|
||||||
|
<object class="_NSCornerView" key="NSCornerView" id="564034022">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{224, 0}, {16, 17}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="831830981"/>
|
||||||
|
</object>
|
||||||
|
<array class="NSMutableArray" key="NSTableColumns"/>
|
||||||
|
<double key="NSIntercellSpacingWidth">3</double>
|
||||||
|
<double key="NSIntercellSpacingHeight">2</double>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MQA</bytes>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSGridColor">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">gridColor</string>
|
||||||
|
<object class="NSColor" key="NSColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC41AA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<double key="NSRowHeight">17</double>
|
||||||
|
<int key="NSTvFlags">1512046592</int>
|
||||||
|
<reference key="NSDelegate"/>
|
||||||
|
<reference key="NSDataSource"/>
|
||||||
|
<int key="NSColumnAutoresizingStyle">4</int>
|
||||||
|
<int key="NSDraggingSourceMaskForLocal">15</int>
|
||||||
|
<int key="NSDraggingSourceMaskForNonLocal">0</int>
|
||||||
|
<bool key="NSAllowsTypeSelect">NO</bool>
|
||||||
|
<int key="NSTableViewDraggingDestinationStyle">0</int>
|
||||||
|
<int key="NSTableViewGroupRowStyle">1</int>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{1, 17}, {532, 249}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="252791348"/>
|
||||||
|
<reference key="NSDocView" ref="252791348"/>
|
||||||
|
<object class="NSColor" key="NSBGColor" id="765209443">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">controlBackgroundColor</string>
|
||||||
|
<object class="NSColor" key="NSColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<int key="NScvFlags">4</int>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="99096694">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{224, 17}, {15, 102}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="47224920"/>
|
||||||
|
<reference key="NSTarget" ref="458371270"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSPercent">0.99052132701421802</double>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="47224920">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{1, 154}, {438, 15}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="253286088"/>
|
||||||
|
<int key="NSsFlags">1</int>
|
||||||
|
<reference key="NSTarget" ref="458371270"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSCurValue">1</double>
|
||||||
|
<double key="NSPercent">0.98871331828442433</double>
|
||||||
|
</object>
|
||||||
|
<object class="NSClipView" id="777677330">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">2304</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<reference ref="903452051"/>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{1, 0}, {532, 17}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="903452051"/>
|
||||||
|
<reference key="NSDocView" ref="903452051"/>
|
||||||
|
<reference key="NSBGColor" ref="765209443"/>
|
||||||
|
<int key="NScvFlags">4</int>
|
||||||
|
</object>
|
||||||
|
<reference ref="564034022"/>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{20, 60}, {534, 267}}</string>
|
||||||
|
<reference key="NSSuperview" ref="976198330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="831830981"/>
|
||||||
|
<int key="NSsFlags">133682</int>
|
||||||
|
<reference key="NSVScroller" ref="99096694"/>
|
||||||
|
<reference key="NSHScroller" ref="47224920"/>
|
||||||
|
<reference key="NSContentView" ref="831830981"/>
|
||||||
|
<reference key="NSHeaderClipView" ref="777677330"/>
|
||||||
|
<reference key="NSCornerView" ref="564034022"/>
|
||||||
|
<bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
|
||||||
|
</object>
|
||||||
|
<object class="NSButton" id="4380169">
|
||||||
|
<reference key="NSNextResponder" ref="976198330"/>
|
||||||
|
<int key="NSvFlags">289</int>
|
||||||
|
<string key="NSFrame">{{464, 12}, {96, 32}}</string>
|
||||||
|
<reference key="NSSuperview" ref="976198330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSButtonCell" key="NSCell" id="373771329">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">134217728</int>
|
||||||
|
<string key="NSContents">Close</string>
|
||||||
|
<object class="NSFont" key="NSSupport" id="680801460">
|
||||||
|
<string key="NSName">LucidaGrande</string>
|
||||||
|
<double key="NSSize">13</double>
|
||||||
|
<int key="NSfFlags">1044</int>
|
||||||
|
</object>
|
||||||
|
<reference key="NSControlView" ref="4380169"/>
|
||||||
|
<int key="NSButtonFlags">-2038284033</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string type="base64-UTF8" key="NSKeyEquivalent">DQ</string>
|
||||||
|
<int key="NSPeriodicDelay">200</int>
|
||||||
|
<int key="NSPeriodicInterval">25</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSButton" id="253286088">
|
||||||
|
<reference key="NSNextResponder" ref="976198330"/>
|
||||||
|
<int key="NSvFlags">292</int>
|
||||||
|
<string key="NSFrame">{{14, 12}, {154, 32}}</string>
|
||||||
|
<reference key="NSSuperview" ref="976198330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="983148229"/>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSButtonCell" key="NSCell" id="671547957">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">134217728</int>
|
||||||
|
<string key="NSContents">Remove Selected</string>
|
||||||
|
<reference key="NSSupport" ref="680801460"/>
|
||||||
|
<reference key="NSControlView" ref="253286088"/>
|
||||||
|
<int key="NSButtonFlags">-2038284033</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string key="NSKeyEquivalent"/>
|
||||||
|
<int key="NSPeriodicDelay">200</int>
|
||||||
|
<int key="NSPeriodicInterval">25</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSButton" id="983148229">
|
||||||
|
<reference key="NSNextResponder" ref="976198330"/>
|
||||||
|
<int key="NSvFlags">292</int>
|
||||||
|
<string key="NSFrame">{{162, 12}, {154, 32}}</string>
|
||||||
|
<reference key="NSSuperview" ref="976198330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="4380169"/>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSButtonCell" key="NSCell" id="409951495">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">134217728</int>
|
||||||
|
<string key="NSContents">Clear</string>
|
||||||
|
<reference key="NSSupport" ref="680801460"/>
|
||||||
|
<reference key="NSControlView" ref="983148229"/>
|
||||||
|
<int key="NSButtonFlags">-2038284033</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string key="NSKeyEquivalent"/>
|
||||||
|
<int key="NSPeriodicDelay">200</int>
|
||||||
|
<int key="NSPeriodicInterval">25</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrameSize">{574, 347}</string>
|
||||||
|
<reference key="NSSuperview"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="458371270"/>
|
||||||
|
</object>
|
||||||
|
<string key="NSScreenRect">{{0, 0}, {1920, 1058}}</string>
|
||||||
|
<string key="NSMaxSize">{10000000000000, 10000000000000}</string>
|
||||||
|
<bool key="NSWindowIsRestorable">YES</bool>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||||
|
<array class="NSMutableArray" key="connectionRecords">
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">window</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="359561441"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">22</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">ignoreListTableView</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="252791348"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">30</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBActionConnection" key="connection">
|
||||||
|
<string key="label">removeSelected:</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="253286088"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">31</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBActionConnection" key="connection">
|
||||||
|
<string key="label">clear:</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="983148229"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">32</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBActionConnection" key="connection">
|
||||||
|
<string key="label">performClose:</string>
|
||||||
|
<reference key="source" ref="359561441"/>
|
||||||
|
<reference key="destination" ref="4380169"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">25</int>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||||
|
<array key="orderedObjects">
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">0</int>
|
||||||
|
<array key="object" id="0"/>
|
||||||
|
<reference key="children" ref="1000"/>
|
||||||
|
<nil key="parent"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-2</int>
|
||||||
|
<reference key="object" ref="1001"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">File's Owner</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-1</int>
|
||||||
|
<reference key="object" ref="1003"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">First Responder</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-3</int>
|
||||||
|
<reference key="object" ref="1004"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">Application</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">1</int>
|
||||||
|
<reference key="object" ref="359561441"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="976198330"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">2</int>
|
||||||
|
<reference key="object" ref="976198330"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="458371270"/>
|
||||||
|
<reference ref="4380169"/>
|
||||||
|
<reference ref="253286088"/>
|
||||||
|
<reference ref="983148229"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="359561441"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">5</int>
|
||||||
|
<reference key="object" ref="458371270"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="99096694"/>
|
||||||
|
<reference ref="47224920"/>
|
||||||
|
<reference ref="252791348"/>
|
||||||
|
<reference ref="903452051"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="976198330"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">6</int>
|
||||||
|
<reference key="object" ref="99096694"/>
|
||||||
|
<reference key="parent" ref="458371270"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">7</int>
|
||||||
|
<reference key="object" ref="47224920"/>
|
||||||
|
<reference key="parent" ref="458371270"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">8</int>
|
||||||
|
<reference key="object" ref="252791348"/>
|
||||||
|
<array class="NSMutableArray" key="children"/>
|
||||||
|
<reference key="parent" ref="458371270"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">9</int>
|
||||||
|
<reference key="object" ref="903452051"/>
|
||||||
|
<reference key="parent" ref="458371270"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">18</int>
|
||||||
|
<reference key="object" ref="4380169"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="373771329"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="976198330"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">19</int>
|
||||||
|
<reference key="object" ref="373771329"/>
|
||||||
|
<reference key="parent" ref="4380169"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">20</int>
|
||||||
|
<reference key="object" ref="253286088"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="671547957"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="976198330"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">21</int>
|
||||||
|
<reference key="object" ref="671547957"/>
|
||||||
|
<reference key="parent" ref="253286088"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">27</int>
|
||||||
|
<reference key="object" ref="983148229"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="409951495"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="976198330"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">28</int>
|
||||||
|
<reference key="object" ref="409951495"/>
|
||||||
|
<reference key="parent" ref="983148229"/>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
</object>
|
||||||
|
<dictionary class="NSMutableDictionary" key="flattenedProperties">
|
||||||
|
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="1.IBWindowTemplateEditedContentRect">{{477, 306}, {480, 309}}</string>
|
||||||
|
<boolean value="NO" key="1.NSWindowTemplate.visibleAtLaunch"/>
|
||||||
|
<string key="18.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="19.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="20.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="21.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="27.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="28.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="6.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="7.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="8.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="9.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
</dictionary>
|
||||||
|
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
|
||||||
|
<nil key="activeLocalization"/>
|
||||||
|
<dictionary class="NSMutableDictionary" key="localizations"/>
|
||||||
|
<nil key="sourceID"/>
|
||||||
|
<int key="maxID">32</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||||
|
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||||
|
<object class="IBPartialClassDescription">
|
||||||
|
<string key="className">IgnoreListDialog</string>
|
||||||
|
<string key="superclassName">NSWindowController</string>
|
||||||
|
<dictionary class="NSMutableDictionary" key="actions">
|
||||||
|
<string key="clear:">id</string>
|
||||||
|
<string key="removeSelected:">id</string>
|
||||||
|
</dictionary>
|
||||||
|
<dictionary class="NSMutableDictionary" key="actionInfosByName">
|
||||||
|
<object class="IBActionInfo" key="clear:">
|
||||||
|
<string key="name">clear:</string>
|
||||||
|
<string key="candidateClassName">id</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBActionInfo" key="removeSelected:">
|
||||||
|
<string key="name">removeSelected:</string>
|
||||||
|
<string key="candidateClassName">id</string>
|
||||||
|
</object>
|
||||||
|
</dictionary>
|
||||||
|
<object class="NSMutableDictionary" key="outlets">
|
||||||
|
<string key="NS.key.0">ignoreListTableView</string>
|
||||||
|
<string key="NS.object.0">NSTableView</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||||
|
<string key="NS.key.0">ignoreListTableView</string>
|
||||||
|
<object class="IBToOneOutletInfo" key="NS.object.0">
|
||||||
|
<string key="name">ignoreListTableView</string>
|
||||||
|
<string key="candidateClassName">NSTableView</string>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
|
<string key="majorKey">IBProjectSource</string>
|
||||||
|
<string key="minorKey">./Classes/IgnoreListDialog.h</string>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
</object>
|
||||||
|
<int key="IBDocument.localizationMode">0</int>
|
||||||
|
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
|
<real value="1060" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
|
<real value="1060" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
|
||||||
|
<real value="4100" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||||
|
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||||
|
</data>
|
||||||
|
</archive>
|
||||||
27
cocoa/base/en.lproj/Localizable.strings
Normal file
27
cocoa/base/en.lproj/Localizable.strings
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
"Add New Folder..." = "Add New Folder...";
|
||||||
|
"Load from file..." = "Load from file...";
|
||||||
|
"Reset to Default" = "Reset to Default";
|
||||||
|
|
||||||
|
"Select a results file to load" = "Select a results file to load";
|
||||||
|
"You have unsaved results, do you really want to quit?" = "You have unsaved results, do you really want to quit?";
|
||||||
|
"Select a file to save your results to" = "Select a file to save your results to";
|
||||||
|
"Select a folder to add to the scanning list" = "Select a folder to add to the scanning list";
|
||||||
|
"You have unsaved results, do you really want to continue?" = "You have unsaved results, do you really want to continue?";
|
||||||
|
"'%@' already is in the list." = "'%@' already is in the list.";
|
||||||
|
"'%@' does not exist." = "'%@' does not exist.";
|
||||||
|
"The name '%@' already exists." = "The name '%@' already exists.";
|
||||||
|
"A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again." = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again.";
|
||||||
|
"Your iTunes Library contains %d dead tracks ready to be removed. Continue?" = "Your iTunes Library contains %d dead tracks ready to be removed. Continue?";
|
||||||
|
"You have no dead tracks in your iTunes Library" = "You have no dead tracks in your iTunes Library";
|
||||||
|
"Do you really want to remove all your cached picture analysis?" = "Do you really want to remove all your cached picture analysis?";
|
||||||
|
|
||||||
|
|
||||||
|
"Add iTunes Directory" = "Add iTunes Directory";
|
||||||
|
"Remove Dead Tracks in iTunes" = "Remove Dead Tracks in iTunes";
|
||||||
|
|
||||||
|
"Add iPhoto Library" = "Add iPhoto Library";
|
||||||
|
"Clear Picture Cache" = "Clear Picture Cache";
|
||||||
|
|
||||||
|
"Yes" = "Yes";
|
||||||
|
"No" = "No";
|
||||||
|
"OK" = "OK";
|
||||||
177
cocoa/base/en.lproj/MainMenu.strings
Normal file
177
cocoa/base/en.lproj/MainMenu.strings
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "5"; */
|
||||||
|
"5.title" = "Bring All to Front";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Window"; ObjectID = "19"; */
|
||||||
|
"19.title" = "Window";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "23"; */
|
||||||
|
"23.title" = "Minimize";
|
||||||
|
|
||||||
|
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
|
||||||
|
"24.title" = "Window";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "About dupeGuru"; ObjectID = "58"; */
|
||||||
|
"58.title" = "About dupeGuru";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Help"; ObjectID = "103"; */
|
||||||
|
"103.title" = "Help";
|
||||||
|
|
||||||
|
/* Class = "NSMenu"; title = "Help"; ObjectID = "106"; */
|
||||||
|
"106.title" = "Help";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "dupeGuru Help"; ObjectID = "111"; */
|
||||||
|
"111.title" = "dupeGuru Help";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Hide dupeGuru"; ObjectID = "134"; */
|
||||||
|
"134.title" = "Hide dupeGuru";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Quit dupeGuru"; ObjectID = "136"; */
|
||||||
|
"136.title" = "Quit dupeGuru";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */
|
||||||
|
"145.title" = "Hide Others";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */
|
||||||
|
"150.title" = "Show All";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "197"; */
|
||||||
|
"197.title" = "Zoom";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Details Panel"; ObjectID = "398"; */
|
||||||
|
"398.title" = "Details Panel";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Preferences..."; ObjectID = "541"; */
|
||||||
|
"541.title" = "Preferences...";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Folder Selection Window"; ObjectID = "579"; */
|
||||||
|
"579.title" = "Folder Selection Window";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Actions"; ObjectID = "597"; */
|
||||||
|
"597.title" = "Actions";
|
||||||
|
|
||||||
|
/* Class = "NSMenu"; title = "Actions"; ObjectID = "598"; */
|
||||||
|
"598.title" = "Actions";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Send Marked to Trash"; ObjectID = "599"; */
|
||||||
|
"599.title" = "Send Marked to Trash";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Move Marked to..."; ObjectID = "600"; */
|
||||||
|
"600.title" = "Move Marked to...";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Copy Marked to..."; ObjectID = "601"; */
|
||||||
|
"601.title" = "Copy Marked to...";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Make Selected Reference"; ObjectID = "602"; */
|
||||||
|
"602.title" = "Make Selected Reference";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Remove Marked from Results"; ObjectID = "603"; */
|
||||||
|
"603.title" = "Remove Marked from Results";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Remove Selected from Results"; ObjectID = "605"; */
|
||||||
|
"605.title" = "Remove Selected from Results";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Columns"; ObjectID = "618"; */
|
||||||
|
"618.title" = "Columns";
|
||||||
|
|
||||||
|
/* Class = "NSMenu"; title = "Columns"; ObjectID = "619"; */
|
||||||
|
"619.title" = "Columns";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Open Selected with Default Application"; ObjectID = "708"; */
|
||||||
|
"708.title" = "Open Selected with Default Application";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Reveal Selected in Finder"; ObjectID = "710"; */
|
||||||
|
"710.title" = "Reveal Selected in Finder";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Add Selected to Ignore List"; ObjectID = "922"; */
|
||||||
|
"922.title" = "Add Selected to Ignore List";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Close Window"; ObjectID = "924"; */
|
||||||
|
"924.title" = "Close Window";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Start Duplicate Scan"; ObjectID = "926"; */
|
||||||
|
"926.title" = "Start Duplicate Scan";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Rename Selected"; ObjectID = "933"; */
|
||||||
|
"933.title" = "Rename Selected";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Export Results to XHTML"; ObjectID = "947"; */
|
||||||
|
"947.title" = "Export Results to XHTML";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Check for update..."; ObjectID = "950"; */
|
||||||
|
"950.title" = "Check for update...";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Mode"; ObjectID = "959"; */
|
||||||
|
"959.title" = "Mode";
|
||||||
|
|
||||||
|
/* Class = "NSMenu"; title = "Mode"; ObjectID = "960"; */
|
||||||
|
"960.title" = "Mode";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Show Dupes Only"; ObjectID = "961"; */
|
||||||
|
"961.title" = "Show Dupes Only";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Show Delta Values"; ObjectID = "962"; */
|
||||||
|
"962.title" = "Show Delta Values";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Edit"; ObjectID = "965"; */
|
||||||
|
"965.title" = "Edit";
|
||||||
|
|
||||||
|
/* Class = "NSMenu"; title = "Edit"; ObjectID = "966"; */
|
||||||
|
"966.title" = "Edit";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Cut"; ObjectID = "985"; */
|
||||||
|
"985.title" = "Cut";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Copy"; ObjectID = "986"; */
|
||||||
|
"986.title" = "Copy";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Paste"; ObjectID = "991"; */
|
||||||
|
"991.title" = "Paste";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Mark All"; ObjectID = "1011"; */
|
||||||
|
"1011.title" = "Mark All";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Mark None"; ObjectID = "1012"; */
|
||||||
|
"1012.title" = "Mark None";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Invert Marking"; ObjectID = "1013"; */
|
||||||
|
"1013.title" = "Invert Marking";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Mark Selected"; ObjectID = "1014"; */
|
||||||
|
"1014.title" = "Mark Selected";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "dupeGuru Website"; ObjectID = "1023"; */
|
||||||
|
"1023.title" = "dupeGuru Website";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Invoke Custom Command"; ObjectID = "1177"; */
|
||||||
|
"1177.title" = "Invoke Custom Command";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "File"; ObjectID = "1203"; */
|
||||||
|
"1203.title" = "File";
|
||||||
|
|
||||||
|
/* Class = "NSMenu"; title = "File"; ObjectID = "1204"; */
|
||||||
|
"1204.title" = "File";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Load Results..."; ObjectID = "1205"; */
|
||||||
|
"1205.title" = "Load Results...";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Save Results..."; ObjectID = "1206"; */
|
||||||
|
"1206.title" = "Save Results...";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Delete Marked and Replace with Hardlinks"; ObjectID = "1227"; */
|
||||||
|
"1227.title" = "Delete Marked and Replace with Hardlinks";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Load Recent Results"; ObjectID = "1239"; */
|
||||||
|
"1239.title" = "Load Recent Results";
|
||||||
|
|
||||||
|
/* Class = "NSMenu"; title = "Load Recent Results"; ObjectID = "1240"; */
|
||||||
|
"1240.title" = "Load Recent Results";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Results Window"; ObjectID = "1272"; */
|
||||||
|
"1272.title" = "Results Window";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Re-Prioritize Results"; ObjectID = "1276"; */
|
||||||
|
"1276.title" = "Re-Prioritize Results";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Ignore List"; ObjectID = "1283"; */
|
||||||
|
"1283.title" = "Ignore List";
|
||||||
2083
cocoa/base/en.lproj/MainMenu.xib
Normal file
2083
cocoa/base/en.lproj/MainMenu.xib
Normal file
File diff suppressed because it is too large
Load Diff
12
cocoa/base/en.lproj/PrioritizeDialog.strings
Normal file
12
cocoa/base/en.lproj/PrioritizeDialog.strings
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
/* Class = "NSWindow"; title = "Re-Prioritize duplicates"; ObjectID = "1"; */
|
||||||
|
"1.title" = "Re-Prioritize duplicates";
|
||||||
|
|
||||||
|
/* Class = "NSButtonCell"; title = "Ok"; ObjectID = "37"; */
|
||||||
|
"37.title" = "Ok";
|
||||||
|
|
||||||
|
/* Class = "NSButtonCell"; title = "Cancel"; ObjectID = "39"; */
|
||||||
|
"39.title" = "Cancel";
|
||||||
|
|
||||||
|
/* Class = "NSTextFieldCell"; title = "Add criteria to the right box and click OK to send the dupes that correspond the best to these criteria to their respective group's reference position. Read the help file for more information."; ObjectID = "41"; */
|
||||||
|
"41.title" = "Add criteria to the right box and click OK to send the dupes that correspond the best to these criteria to their respective group's reference position. Read the help file for more information.";
|
||||||
897
cocoa/base/en.lproj/PrioritizeDialog.xib
Normal file
897
cocoa/base/en.lproj/PrioritizeDialog.xib
Normal file
@@ -0,0 +1,897 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
|
||||||
|
<data>
|
||||||
|
<int key="IBDocument.SystemTarget">1060</int>
|
||||||
|
<string key="IBDocument.SystemVersion">11D50</string>
|
||||||
|
<string key="IBDocument.InterfaceBuilderVersion">2182</string>
|
||||||
|
<string key="IBDocument.AppKitVersion">1138.32</string>
|
||||||
|
<string key="IBDocument.HIToolboxVersion">568.00</string>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="NS.object.0">2182</string>
|
||||||
|
</object>
|
||||||
|
<array key="IBDocument.IntegratedClassDependencies">
|
||||||
|
<string>NSTextField</string>
|
||||||
|
<string>NSPopUpButton</string>
|
||||||
|
<string>NSScroller</string>
|
||||||
|
<string>NSButton</string>
|
||||||
|
<string>NSMenu</string>
|
||||||
|
<string>NSScrollView</string>
|
||||||
|
<string>NSTextFieldCell</string>
|
||||||
|
<string>NSButtonCell</string>
|
||||||
|
<string>NSTableView</string>
|
||||||
|
<string>NSCustomObject</string>
|
||||||
|
<string>NSView</string>
|
||||||
|
<string>NSWindowTemplate</string>
|
||||||
|
<string>NSPopUpButtonCell</string>
|
||||||
|
<string>NSTableColumn</string>
|
||||||
|
</array>
|
||||||
|
<array key="IBDocument.PluginDependencies">
|
||||||
|
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
</array>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.Metadata">
|
||||||
|
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
|
||||||
|
<integer value="1" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
|
||||||
|
<object class="NSCustomObject" id="1001">
|
||||||
|
<string key="NSClassName">PrioritizeDialog</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSCustomObject" id="1003">
|
||||||
|
<string key="NSClassName">FirstResponder</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSCustomObject" id="1004">
|
||||||
|
<string key="NSClassName">NSApplication</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSWindowTemplate" id="1005">
|
||||||
|
<int key="NSWindowStyleMask">9</int>
|
||||||
|
<int key="NSWindowBacking">2</int>
|
||||||
|
<string key="NSWindowRect">{{196, 240}, {609, 401}}</string>
|
||||||
|
<int key="NSWTFlags">1618477056</int>
|
||||||
|
<string key="NSWindowTitle">Re-Prioritize duplicates</string>
|
||||||
|
<string key="NSWindowClass">NSWindow</string>
|
||||||
|
<nil key="NSViewClass"/>
|
||||||
|
<nil key="NSUserInterfaceItemIdentifier"/>
|
||||||
|
<object class="NSView" key="NSWindowView" id="1006">
|
||||||
|
<reference key="NSNextResponder"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSPopUpButton" id="357095790">
|
||||||
|
<reference key="NSNextResponder" ref="1006"/>
|
||||||
|
<int key="NSvFlags">268</int>
|
||||||
|
<string key="NSFrame">{{17, 298}, {268, 26}}</string>
|
||||||
|
<reference key="NSSuperview" ref="1006"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="386461059"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:179</string>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSPopUpButtonCell" key="NSCell" id="892922362">
|
||||||
|
<int key="NSCellFlags">-2076049856</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<object class="NSFont" key="NSSupport" id="591283433">
|
||||||
|
<string key="NSName">LucidaGrande</string>
|
||||||
|
<double key="NSSize">13</double>
|
||||||
|
<int key="NSfFlags">1044</int>
|
||||||
|
</object>
|
||||||
|
<string key="NSCellIdentifier">_NS:179</string>
|
||||||
|
<reference key="NSControlView" ref="357095790"/>
|
||||||
|
<int key="NSButtonFlags">109199615</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string key="NSKeyEquivalent"/>
|
||||||
|
<int key="NSPeriodicDelay">400</int>
|
||||||
|
<int key="NSPeriodicInterval">75</int>
|
||||||
|
<nil key="NSMenuItem"/>
|
||||||
|
<bool key="NSMenuItemRespectAlignment">YES</bool>
|
||||||
|
<object class="NSMenu" key="NSMenu" id="309249465">
|
||||||
|
<string key="NSTitle">OtherViews</string>
|
||||||
|
<array class="NSMutableArray" key="NSMenuItems"/>
|
||||||
|
<reference key="NSMenuFont" ref="591283433"/>
|
||||||
|
</object>
|
||||||
|
<int key="NSSelectedIndex">-1</int>
|
||||||
|
<int key="NSPreferredEdge">1</int>
|
||||||
|
<bool key="NSUsesItemFromMenu">YES</bool>
|
||||||
|
<bool key="NSAltersState">YES</bool>
|
||||||
|
<int key="NSArrowPosition">2</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSScrollView" id="386461059">
|
||||||
|
<reference key="NSNextResponder" ref="1006"/>
|
||||||
|
<int key="NSvFlags">276</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSClipView" id="991694656">
|
||||||
|
<reference key="NSNextResponder" ref="386461059"/>
|
||||||
|
<int key="NSvFlags">2304</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSTableView" id="675311684">
|
||||||
|
<reference key="NSNextResponder" ref="991694656"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<string key="NSFrameSize">{260, 233}</string>
|
||||||
|
<reference key="NSSuperview" ref="991694656"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="978953877"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1197</string>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="_NSCornerView" key="NSCornerView">
|
||||||
|
<nil key="NSNextResponder"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{224, 0}, {16, 17}}</string>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1202</string>
|
||||||
|
</object>
|
||||||
|
<array class="NSMutableArray" key="NSTableColumns">
|
||||||
|
<object class="NSTableColumn" id="331887935">
|
||||||
|
<double key="NSWidth">257</double>
|
||||||
|
<double key="NSMinWidth">40</double>
|
||||||
|
<double key="NSMaxWidth">1000</double>
|
||||||
|
<object class="NSTableHeaderCell" key="NSHeaderCell">
|
||||||
|
<int key="NSCellFlags">75628096</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<string key="NSContents"/>
|
||||||
|
<object class="NSFont" key="NSSupport" id="26">
|
||||||
|
<string key="NSName">LucidaGrande</string>
|
||||||
|
<double key="NSSize">11</double>
|
||||||
|
<int key="NSfFlags">3100</int>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC4zMzMzMzI5ODU2AA</bytes>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSTextColor" id="165509352">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">headerTextColor</string>
|
||||||
|
<object class="NSColor" key="NSColor" id="95789318">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MAA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSTextFieldCell" key="NSDataCell" id="252516817">
|
||||||
|
<int key="NSCellFlags">337772096</int>
|
||||||
|
<int key="NSCellFlags2">2560</int>
|
||||||
|
<string key="NSContents"/>
|
||||||
|
<reference key="NSSupport" ref="591283433"/>
|
||||||
|
<reference key="NSControlView" ref="675311684"/>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor" id="768431893">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">controlBackgroundColor</string>
|
||||||
|
<object class="NSColor" key="NSColor" id="857981892">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSTextColor" id="983887457">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">controlTextColor</string>
|
||||||
|
<reference key="NSColor" ref="95789318"/>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<int key="NSResizingMask">3</int>
|
||||||
|
<bool key="NSIsResizeable">YES</bool>
|
||||||
|
<bool key="NSIsEditable">YES</bool>
|
||||||
|
<reference key="NSTableView" ref="675311684"/>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<double key="NSIntercellSpacingWidth">3</double>
|
||||||
|
<double key="NSIntercellSpacingHeight">2</double>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor" id="949356842">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MQA</bytes>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSGridColor" id="353256269">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">gridColor</string>
|
||||||
|
<object class="NSColor" key="NSColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC41AA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<double key="NSRowHeight">17</double>
|
||||||
|
<int key="NSTvFlags">306184192</int>
|
||||||
|
<reference key="NSDelegate"/>
|
||||||
|
<reference key="NSDataSource"/>
|
||||||
|
<int key="NSColumnAutoresizingStyle">4</int>
|
||||||
|
<int key="NSDraggingSourceMaskForLocal">15</int>
|
||||||
|
<int key="NSDraggingSourceMaskForNonLocal">0</int>
|
||||||
|
<bool key="NSAllowsTypeSelect">NO</bool>
|
||||||
|
<int key="NSTableViewDraggingDestinationStyle">0</int>
|
||||||
|
<int key="NSTableViewGroupRowStyle">1</int>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{1, 1}, {260, 233}}</string>
|
||||||
|
<reference key="NSSuperview" ref="386461059"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="675311684"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1195</string>
|
||||||
|
<reference key="NSDocView" ref="675311684"/>
|
||||||
|
<reference key="NSBGColor" ref="768431893"/>
|
||||||
|
<int key="NScvFlags">4</int>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="978953877">
|
||||||
|
<reference key="NSNextResponder" ref="386461059"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{224, 17}, {15, 102}}</string>
|
||||||
|
<reference key="NSSuperview" ref="386461059"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="1055237002"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1214</string>
|
||||||
|
<reference key="NSTarget" ref="386461059"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSPercent">0.9925373134328358</double>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="1055237002">
|
||||||
|
<reference key="NSNextResponder" ref="386461059"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{1, 119}, {223, 15}}</string>
|
||||||
|
<reference key="NSSuperview" ref="386461059"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="878293971"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1216</string>
|
||||||
|
<int key="NSsFlags">1</int>
|
||||||
|
<reference key="NSTarget" ref="386461059"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSPercent">0.99581589958159</double>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{20, 60}, {262, 235}}</string>
|
||||||
|
<reference key="NSSuperview" ref="1006"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="991694656"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1193</string>
|
||||||
|
<int key="NSsFlags">133682</int>
|
||||||
|
<reference key="NSVScroller" ref="978953877"/>
|
||||||
|
<reference key="NSHScroller" ref="1055237002"/>
|
||||||
|
<reference key="NSContentView" ref="991694656"/>
|
||||||
|
<bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
|
||||||
|
</object>
|
||||||
|
<object class="NSScrollView" id="687805562">
|
||||||
|
<reference key="NSNextResponder" ref="1006"/>
|
||||||
|
<int key="NSvFlags">274</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSClipView" id="256485327">
|
||||||
|
<reference key="NSNextResponder" ref="687805562"/>
|
||||||
|
<int key="NSvFlags">2304</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSTableView" id="69698213">
|
||||||
|
<reference key="NSNextResponder" ref="256485327"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<string key="NSFrameSize">{248, 260}</string>
|
||||||
|
<reference key="NSSuperview" ref="256485327"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="258173410"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1197</string>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="_NSCornerView" key="NSCornerView">
|
||||||
|
<nil key="NSNextResponder"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{224, 0}, {16, 17}}</string>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1202</string>
|
||||||
|
</object>
|
||||||
|
<array class="NSMutableArray" key="NSTableColumns">
|
||||||
|
<object class="NSTableColumn" id="604129553">
|
||||||
|
<double key="NSWidth">245</double>
|
||||||
|
<double key="NSMinWidth">40</double>
|
||||||
|
<double key="NSMaxWidth">1000</double>
|
||||||
|
<object class="NSTableHeaderCell" key="NSHeaderCell">
|
||||||
|
<int key="NSCellFlags">75628096</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<string key="NSContents"/>
|
||||||
|
<reference key="NSSupport" ref="26"/>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC4zMzMzMzI5ODU2AA</bytes>
|
||||||
|
</object>
|
||||||
|
<reference key="NSTextColor" ref="165509352"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSTextFieldCell" key="NSDataCell" id="998326916">
|
||||||
|
<int key="NSCellFlags">337772096</int>
|
||||||
|
<int key="NSCellFlags2">2048</int>
|
||||||
|
<string key="NSContents"/>
|
||||||
|
<reference key="NSSupport" ref="591283433"/>
|
||||||
|
<reference key="NSControlView" ref="69698213"/>
|
||||||
|
<reference key="NSBackgroundColor" ref="768431893"/>
|
||||||
|
<reference key="NSTextColor" ref="983887457"/>
|
||||||
|
</object>
|
||||||
|
<int key="NSResizingMask">3</int>
|
||||||
|
<bool key="NSIsResizeable">YES</bool>
|
||||||
|
<bool key="NSIsEditable">YES</bool>
|
||||||
|
<reference key="NSTableView" ref="69698213"/>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<double key="NSIntercellSpacingWidth">3</double>
|
||||||
|
<double key="NSIntercellSpacingHeight">2</double>
|
||||||
|
<reference key="NSBackgroundColor" ref="949356842"/>
|
||||||
|
<reference key="NSGridColor" ref="353256269"/>
|
||||||
|
<double key="NSRowHeight">17</double>
|
||||||
|
<int key="NSTvFlags">306184192</int>
|
||||||
|
<reference key="NSDelegate"/>
|
||||||
|
<reference key="NSDataSource"/>
|
||||||
|
<int key="NSColumnAutoresizingStyle">4</int>
|
||||||
|
<int key="NSDraggingSourceMaskForLocal">15</int>
|
||||||
|
<int key="NSDraggingSourceMaskForNonLocal">0</int>
|
||||||
|
<bool key="NSAllowsTypeSelect">NO</bool>
|
||||||
|
<int key="NSTableViewDraggingDestinationStyle">0</int>
|
||||||
|
<int key="NSTableViewGroupRowStyle">1</int>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{1, 1}, {248, 260}}</string>
|
||||||
|
<reference key="NSSuperview" ref="687805562"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="69698213"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1195</string>
|
||||||
|
<reference key="NSDocView" ref="69698213"/>
|
||||||
|
<reference key="NSBGColor" ref="768431893"/>
|
||||||
|
<int key="NScvFlags">4</int>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="258173410">
|
||||||
|
<reference key="NSNextResponder" ref="687805562"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{224, 17}, {15, 102}}</string>
|
||||||
|
<reference key="NSSuperview" ref="687805562"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="218354894"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1214</string>
|
||||||
|
<reference key="NSTarget" ref="687805562"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSPercent">0.9925373134328358</double>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="218354894">
|
||||||
|
<reference key="NSNextResponder" ref="687805562"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{1, 119}, {223, 15}}</string>
|
||||||
|
<reference key="NSSuperview" ref="687805562"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="955319632"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1216</string>
|
||||||
|
<int key="NSsFlags">1</int>
|
||||||
|
<reference key="NSTarget" ref="687805562"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSPercent">0.99581589958159</double>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{339, 60}, {250, 262}}</string>
|
||||||
|
<reference key="NSSuperview" ref="1006"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="256485327"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:1193</string>
|
||||||
|
<int key="NSsFlags">133682</int>
|
||||||
|
<reference key="NSVScroller" ref="258173410"/>
|
||||||
|
<reference key="NSHScroller" ref="218354894"/>
|
||||||
|
<reference key="NSContentView" ref="256485327"/>
|
||||||
|
<bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
|
||||||
|
</object>
|
||||||
|
<object class="NSButton" id="878293971">
|
||||||
|
<reference key="NSNextResponder" ref="1006"/>
|
||||||
|
<int key="NSvFlags">268</int>
|
||||||
|
<string key="NSFrame">{{284, 186}, {53, 32}}</string>
|
||||||
|
<reference key="NSSuperview" ref="1006"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="596965746"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:161</string>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSButtonCell" key="NSCell" id="208161580">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">134217728</int>
|
||||||
|
<string key="NSContents">--></string>
|
||||||
|
<reference key="NSSupport" ref="591283433"/>
|
||||||
|
<string key="NSCellIdentifier">_NS:161</string>
|
||||||
|
<reference key="NSControlView" ref="878293971"/>
|
||||||
|
<int key="NSButtonFlags">-2038284033</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string key="NSKeyEquivalent"/>
|
||||||
|
<int key="NSPeriodicDelay">200</int>
|
||||||
|
<int key="NSPeriodicInterval">25</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSButton" id="596965746">
|
||||||
|
<reference key="NSNextResponder" ref="1006"/>
|
||||||
|
<int key="NSvFlags">268</int>
|
||||||
|
<string key="NSFrame">{{284, 160}, {53, 32}}</string>
|
||||||
|
<reference key="NSSuperview" ref="1006"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="687805562"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:161</string>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSButtonCell" key="NSCell" id="410417059">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">134217728</int>
|
||||||
|
<string key="NSContents"><--</string>
|
||||||
|
<reference key="NSSupport" ref="591283433"/>
|
||||||
|
<string key="NSCellIdentifier">_NS:161</string>
|
||||||
|
<reference key="NSControlView" ref="596965746"/>
|
||||||
|
<int key="NSButtonFlags">-2038284033</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string key="NSKeyEquivalent"/>
|
||||||
|
<int key="NSPeriodicDelay">200</int>
|
||||||
|
<int key="NSPeriodicInterval">25</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSButton" id="620738811">
|
||||||
|
<reference key="NSNextResponder" ref="1006"/>
|
||||||
|
<int key="NSvFlags">289</int>
|
||||||
|
<string key="NSFrame">{{495, 12}, {100, 32}}</string>
|
||||||
|
<reference key="NSSuperview" ref="1006"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:161</string>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSButtonCell" key="NSCell" id="468133349">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">134217728</int>
|
||||||
|
<string key="NSContents">Ok</string>
|
||||||
|
<reference key="NSSupport" ref="591283433"/>
|
||||||
|
<string key="NSCellIdentifier">_NS:161</string>
|
||||||
|
<reference key="NSControlView" ref="620738811"/>
|
||||||
|
<int key="NSButtonFlags">-2038284033</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string type="base64-UTF8" key="NSKeyEquivalent">DQ</string>
|
||||||
|
<int key="NSPeriodicDelay">200</int>
|
||||||
|
<int key="NSPeriodicInterval">25</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSButton" id="955319632">
|
||||||
|
<reference key="NSNextResponder" ref="1006"/>
|
||||||
|
<int key="NSvFlags">289</int>
|
||||||
|
<string key="NSFrame">{{395, 12}, {100, 32}}</string>
|
||||||
|
<reference key="NSSuperview" ref="1006"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="620738811"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:161</string>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSButtonCell" key="NSCell" id="323479517">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">134217728</int>
|
||||||
|
<string key="NSContents">Cancel</string>
|
||||||
|
<reference key="NSSupport" ref="591283433"/>
|
||||||
|
<string key="NSCellIdentifier">_NS:161</string>
|
||||||
|
<reference key="NSControlView" ref="955319632"/>
|
||||||
|
<int key="NSButtonFlags">-2038284033</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string type="base64-UTF8" key="NSKeyEquivalent">Gw</string>
|
||||||
|
<int key="NSPeriodicDelay">200</int>
|
||||||
|
<int key="NSPeriodicInterval">25</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSTextField" id="970734229">
|
||||||
|
<reference key="NSNextResponder" ref="1006"/>
|
||||||
|
<int key="NSvFlags">268</int>
|
||||||
|
<string key="NSFrame">{{17, 330}, {575, 51}}</string>
|
||||||
|
<reference key="NSSuperview" ref="1006"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="357095790"/>
|
||||||
|
<string key="NSReuseIdentifierKey">_NS:3936</string>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSTextFieldCell" key="NSCell" id="424127272">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">4194304</int>
|
||||||
|
<string key="NSContents">Add criteria to the right box and click OK to send the dupes that correspond the best to these criteria to their respective group's reference position. Read the help file for more information.</string>
|
||||||
|
<reference key="NSSupport" ref="591283433"/>
|
||||||
|
<string key="NSCellIdentifier">_NS:3936</string>
|
||||||
|
<reference key="NSControlView" ref="970734229"/>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">controlColor</string>
|
||||||
|
<reference key="NSColor" ref="857981892"/>
|
||||||
|
</object>
|
||||||
|
<reference key="NSTextColor" ref="983887457"/>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrameSize">{609, 401}</string>
|
||||||
|
<reference key="NSSuperview"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="970734229"/>
|
||||||
|
</object>
|
||||||
|
<string key="NSScreenRect">{{0, 0}, {1920, 1058}}</string>
|
||||||
|
<string key="NSMaxSize">{10000000000000, 10000000000000}</string>
|
||||||
|
<bool key="NSWindowIsRestorable">YES</bool>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||||
|
<array class="NSMutableArray" key="connectionRecords">
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">categoryPopUpView</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="357095790"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">10</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">criteriaTableView</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="675311684"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">20</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">prioritizationTableView</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="69698213"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">33</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBActionConnection" key="connection">
|
||||||
|
<string key="label">addSelected:</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="878293971"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">34</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBActionConnection" key="connection">
|
||||||
|
<string key="label">removeSelected:</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="596965746"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">35</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBActionConnection" key="connection">
|
||||||
|
<string key="label">cancel:</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="955319632"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">42</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBActionConnection" key="connection">
|
||||||
|
<string key="label">ok:</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="620738811"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">43</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">window</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="1005"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">44</int>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||||
|
<array key="orderedObjects">
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">0</int>
|
||||||
|
<array key="object" id="0"/>
|
||||||
|
<reference key="children" ref="1000"/>
|
||||||
|
<nil key="parent"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-2</int>
|
||||||
|
<reference key="object" ref="1001"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">File's Owner</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-1</int>
|
||||||
|
<reference key="object" ref="1003"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">First Responder</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-3</int>
|
||||||
|
<reference key="object" ref="1004"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">Application</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">1</int>
|
||||||
|
<reference key="object" ref="1005"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="1006"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">2</int>
|
||||||
|
<reference key="object" ref="1006"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="970734229"/>
|
||||||
|
<reference ref="357095790"/>
|
||||||
|
<reference ref="386461059"/>
|
||||||
|
<reference ref="878293971"/>
|
||||||
|
<reference ref="596965746"/>
|
||||||
|
<reference ref="687805562"/>
|
||||||
|
<reference ref="620738811"/>
|
||||||
|
<reference ref="955319632"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1005"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">3</int>
|
||||||
|
<reference key="object" ref="357095790"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="892922362"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1006"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">4</int>
|
||||||
|
<reference key="object" ref="892922362"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="309249465"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="357095790"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">5</int>
|
||||||
|
<reference key="object" ref="309249465"/>
|
||||||
|
<array class="NSMutableArray" key="children"/>
|
||||||
|
<reference key="parent" ref="892922362"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">11</int>
|
||||||
|
<reference key="object" ref="386461059"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="978953877"/>
|
||||||
|
<reference ref="1055237002"/>
|
||||||
|
<reference ref="675311684"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1006"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">12</int>
|
||||||
|
<reference key="object" ref="978953877"/>
|
||||||
|
<reference key="parent" ref="386461059"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">14</int>
|
||||||
|
<reference key="object" ref="1055237002"/>
|
||||||
|
<reference key="parent" ref="386461059"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">15</int>
|
||||||
|
<reference key="object" ref="675311684"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="331887935"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="386461059"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">16</int>
|
||||||
|
<reference key="object" ref="331887935"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="252516817"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="675311684"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">19</int>
|
||||||
|
<reference key="object" ref="252516817"/>
|
||||||
|
<reference key="parent" ref="331887935"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">21</int>
|
||||||
|
<reference key="object" ref="878293971"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="208161580"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1006"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">22</int>
|
||||||
|
<reference key="object" ref="208161580"/>
|
||||||
|
<reference key="parent" ref="878293971"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">23</int>
|
||||||
|
<reference key="object" ref="596965746"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="410417059"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1006"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">24</int>
|
||||||
|
<reference key="object" ref="410417059"/>
|
||||||
|
<reference key="parent" ref="596965746"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">25</int>
|
||||||
|
<reference key="object" ref="687805562"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="69698213"/>
|
||||||
|
<reference ref="218354894"/>
|
||||||
|
<reference ref="258173410"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1006"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">26</int>
|
||||||
|
<reference key="object" ref="69698213"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="604129553"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="687805562"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">27</int>
|
||||||
|
<reference key="object" ref="218354894"/>
|
||||||
|
<reference key="parent" ref="687805562"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">28</int>
|
||||||
|
<reference key="object" ref="258173410"/>
|
||||||
|
<reference key="parent" ref="687805562"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">29</int>
|
||||||
|
<reference key="object" ref="604129553"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="998326916"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="69698213"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">30</int>
|
||||||
|
<reference key="object" ref="998326916"/>
|
||||||
|
<reference key="parent" ref="604129553"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">36</int>
|
||||||
|
<reference key="object" ref="620738811"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="468133349"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1006"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">37</int>
|
||||||
|
<reference key="object" ref="468133349"/>
|
||||||
|
<reference key="parent" ref="620738811"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">38</int>
|
||||||
|
<reference key="object" ref="955319632"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="323479517"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1006"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">39</int>
|
||||||
|
<reference key="object" ref="323479517"/>
|
||||||
|
<reference key="parent" ref="955319632"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">40</int>
|
||||||
|
<reference key="object" ref="970734229"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="424127272"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="1006"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">41</int>
|
||||||
|
<reference key="object" ref="424127272"/>
|
||||||
|
<reference key="parent" ref="970734229"/>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
</object>
|
||||||
|
<dictionary class="NSMutableDictionary" key="flattenedProperties">
|
||||||
|
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="1.IBWindowTemplateEditedContentRect">{{357, 418}, {480, 270}}</string>
|
||||||
|
<boolean value="NO" key="1.NSWindowTemplate.visibleAtLaunch"/>
|
||||||
|
<string key="11.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="12.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="14.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="15.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="16.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="19.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="21.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="22.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="23.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="24.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="25.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="26.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="27.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="28.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="29.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="30.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="36.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="37.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="38.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="39.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="4.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="40.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="41.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
</dictionary>
|
||||||
|
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
|
||||||
|
<nil key="activeLocalization"/>
|
||||||
|
<dictionary class="NSMutableDictionary" key="localizations"/>
|
||||||
|
<nil key="sourceID"/>
|
||||||
|
<int key="maxID">44</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||||
|
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||||
|
<object class="IBPartialClassDescription">
|
||||||
|
<string key="className">PrioritizeDialog</string>
|
||||||
|
<string key="superclassName">NSWindowController</string>
|
||||||
|
<dictionary class="NSMutableDictionary" key="actions">
|
||||||
|
<string key="addSelected:">id</string>
|
||||||
|
<string key="cancel:">id</string>
|
||||||
|
<string key="ok:">id</string>
|
||||||
|
<string key="removeSelected:">id</string>
|
||||||
|
</dictionary>
|
||||||
|
<dictionary class="NSMutableDictionary" key="actionInfosByName">
|
||||||
|
<object class="IBActionInfo" key="addSelected:">
|
||||||
|
<string key="name">addSelected:</string>
|
||||||
|
<string key="candidateClassName">id</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBActionInfo" key="cancel:">
|
||||||
|
<string key="name">cancel:</string>
|
||||||
|
<string key="candidateClassName">id</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBActionInfo" key="ok:">
|
||||||
|
<string key="name">ok:</string>
|
||||||
|
<string key="candidateClassName">id</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBActionInfo" key="removeSelected:">
|
||||||
|
<string key="name">removeSelected:</string>
|
||||||
|
<string key="candidateClassName">id</string>
|
||||||
|
</object>
|
||||||
|
</dictionary>
|
||||||
|
<dictionary class="NSMutableDictionary" key="outlets">
|
||||||
|
<string key="categoryPopUpView">NSPopUpButton</string>
|
||||||
|
<string key="criteriaTableView">NSTableView</string>
|
||||||
|
<string key="prioritizationTableView">NSTableView</string>
|
||||||
|
</dictionary>
|
||||||
|
<dictionary class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||||
|
<object class="IBToOneOutletInfo" key="categoryPopUpView">
|
||||||
|
<string key="name">categoryPopUpView</string>
|
||||||
|
<string key="candidateClassName">NSPopUpButton</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBToOneOutletInfo" key="criteriaTableView">
|
||||||
|
<string key="name">criteriaTableView</string>
|
||||||
|
<string key="candidateClassName">NSTableView</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBToOneOutletInfo" key="prioritizationTableView">
|
||||||
|
<string key="name">prioritizationTableView</string>
|
||||||
|
<string key="candidateClassName">NSTableView</string>
|
||||||
|
</object>
|
||||||
|
</dictionary>
|
||||||
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
|
<string key="majorKey">IBProjectSource</string>
|
||||||
|
<string key="minorKey">./Classes/PrioritizeDialog.h</string>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
</object>
|
||||||
|
<int key="IBDocument.localizationMode">0</int>
|
||||||
|
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
|
<real value="1060" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
|
<real value="1060" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
|
||||||
|
<real value="4100" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||||
|
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||||
|
</data>
|
||||||
|
</archive>
|
||||||
12
cocoa/base/en.lproj/ProblemDialog.strings
Normal file
12
cocoa/base/en.lproj/ProblemDialog.strings
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
/* Class = "NSWindow"; title = "Problems!"; ObjectID = "1"; */
|
||||||
|
"1.title" = "Problems!";
|
||||||
|
|
||||||
|
/* Class = "NSTextFieldCell"; title = "There were problems processing some (or all) of the files. The cause of these problems are described in the table below. Those files were not removed from your results."; ObjectID = "4"; */
|
||||||
|
"4.title" = "There were problems processing some (or all) of the files. The cause of these problems are described in the table below. Those files were not removed from your results.";
|
||||||
|
|
||||||
|
/* Class = "NSButtonCell"; title = "Close"; ObjectID = "19"; */
|
||||||
|
"19.title" = "Close";
|
||||||
|
|
||||||
|
/* Class = "NSButtonCell"; title = "Reveal Selected"; ObjectID = "21"; */
|
||||||
|
"21.title" = "Reveal Selected";
|
||||||
500
cocoa/base/en.lproj/ProblemDialog.xib
Normal file
500
cocoa/base/en.lproj/ProblemDialog.xib
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00">
|
||||||
|
<data>
|
||||||
|
<int key="IBDocument.SystemTarget">1060</int>
|
||||||
|
<string key="IBDocument.SystemVersion">11D50</string>
|
||||||
|
<string key="IBDocument.InterfaceBuilderVersion">2182</string>
|
||||||
|
<string key="IBDocument.AppKitVersion">1138.32</string>
|
||||||
|
<string key="IBDocument.HIToolboxVersion">568.00</string>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="NS.object.0">2182</string>
|
||||||
|
</object>
|
||||||
|
<array key="IBDocument.IntegratedClassDependencies">
|
||||||
|
<string>NSTextField</string>
|
||||||
|
<string>NSView</string>
|
||||||
|
<string>NSWindowTemplate</string>
|
||||||
|
<string>NSScrollView</string>
|
||||||
|
<string>NSCustomObject</string>
|
||||||
|
<string>NSTableView</string>
|
||||||
|
<string>NSTableHeaderView</string>
|
||||||
|
<string>NSButtonCell</string>
|
||||||
|
<string>NSButton</string>
|
||||||
|
<string>NSScroller</string>
|
||||||
|
<string>NSTextFieldCell</string>
|
||||||
|
</array>
|
||||||
|
<array key="IBDocument.PluginDependencies">
|
||||||
|
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
</array>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.Metadata">
|
||||||
|
<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
|
||||||
|
<integer value="1" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
|
||||||
|
<object class="NSCustomObject" id="1001">
|
||||||
|
<string key="NSClassName">ProblemDialog</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSCustomObject" id="1003">
|
||||||
|
<string key="NSClassName">FirstResponder</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSCustomObject" id="1004">
|
||||||
|
<string key="NSClassName">NSApplication</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSWindowTemplate" id="359561441">
|
||||||
|
<int key="NSWindowStyleMask">11</int>
|
||||||
|
<int key="NSWindowBacking">2</int>
|
||||||
|
<string key="NSWindowRect">{{477, 306}, {480, 309}}</string>
|
||||||
|
<int key="NSWTFlags">1685585920</int>
|
||||||
|
<string key="NSWindowTitle">Problems!</string>
|
||||||
|
<string key="NSWindowClass">NSWindow</string>
|
||||||
|
<nil key="NSViewClass"/>
|
||||||
|
<nil key="NSUserInterfaceItemIdentifier"/>
|
||||||
|
<object class="NSView" key="NSWindowView" id="976198330">
|
||||||
|
<reference key="NSNextResponder"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSTextField" id="573725554">
|
||||||
|
<reference key="NSNextResponder" ref="976198330"/>
|
||||||
|
<int key="NSvFlags">266</int>
|
||||||
|
<string key="NSFrame">{{17, 238}, {446, 51}}</string>
|
||||||
|
<reference key="NSSuperview" ref="976198330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSTextFieldCell" key="NSCell" id="1063844428">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">272891904</int>
|
||||||
|
<string key="NSContents">There were problems processing some (or all) of the files. The cause of these problems are described in the table below. Those files were not removed from your results.</string>
|
||||||
|
<object class="NSFont" key="NSSupport">
|
||||||
|
<string key="NSName">LucidaGrande</string>
|
||||||
|
<double key="NSSize">13</double>
|
||||||
|
<int key="NSfFlags">16</int>
|
||||||
|
</object>
|
||||||
|
<reference key="NSControlView" ref="573725554"/>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">controlColor</string>
|
||||||
|
<object class="NSColor" key="NSColor" id="869923403">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC42NjY2NjY2NjY3AA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSTextColor">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">controlTextColor</string>
|
||||||
|
<object class="NSColor" key="NSColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MAA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSScrollView" id="458371270">
|
||||||
|
<reference key="NSNextResponder" ref="976198330"/>
|
||||||
|
<int key="NSvFlags">274</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSClipView" id="831830981">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">2304</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<object class="NSTableView" id="252791348">
|
||||||
|
<reference key="NSNextResponder" ref="831830981"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<string key="NSFrameSize">{438, 152}</string>
|
||||||
|
<reference key="NSSuperview" ref="831830981"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSTableHeaderView" key="NSHeaderView" id="903452051">
|
||||||
|
<reference key="NSNextResponder" ref="777677330"/>
|
||||||
|
<int key="NSvFlags">256</int>
|
||||||
|
<string key="NSFrameSize">{438, 17}</string>
|
||||||
|
<reference key="NSSuperview" ref="777677330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSTableView" ref="252791348"/>
|
||||||
|
</object>
|
||||||
|
<object class="_NSCornerView" key="NSCornerView" id="564034022">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{224, 0}, {16, 17}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
</object>
|
||||||
|
<array class="NSMutableArray" key="NSTableColumns"/>
|
||||||
|
<double key="NSIntercellSpacingWidth">3</double>
|
||||||
|
<double key="NSIntercellSpacingHeight">2</double>
|
||||||
|
<object class="NSColor" key="NSBackgroundColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MQA</bytes>
|
||||||
|
</object>
|
||||||
|
<object class="NSColor" key="NSGridColor">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">gridColor</string>
|
||||||
|
<object class="NSColor" key="NSColor">
|
||||||
|
<int key="NSColorSpace">3</int>
|
||||||
|
<bytes key="NSWhite">MC41AA</bytes>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<double key="NSRowHeight">17</double>
|
||||||
|
<int key="NSTvFlags">-702545920</int>
|
||||||
|
<reference key="NSDelegate"/>
|
||||||
|
<reference key="NSDataSource"/>
|
||||||
|
<int key="NSColumnAutoresizingStyle">4</int>
|
||||||
|
<int key="NSDraggingSourceMaskForLocal">15</int>
|
||||||
|
<int key="NSDraggingSourceMaskForNonLocal">0</int>
|
||||||
|
<bool key="NSAllowsTypeSelect">YES</bool>
|
||||||
|
<int key="NSTableViewDraggingDestinationStyle">0</int>
|
||||||
|
<int key="NSTableViewGroupRowStyle">1</int>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{1, 17}, {438, 152}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="252791348"/>
|
||||||
|
<reference key="NSDocView" ref="252791348"/>
|
||||||
|
<object class="NSColor" key="NSBGColor" id="765209443">
|
||||||
|
<int key="NSColorSpace">6</int>
|
||||||
|
<string key="NSCatalogName">System</string>
|
||||||
|
<string key="NSColorName">controlBackgroundColor</string>
|
||||||
|
<reference key="NSColor" ref="869923403"/>
|
||||||
|
</object>
|
||||||
|
<int key="NScvFlags">4</int>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="99096694">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{224, 17}, {15, 102}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSTarget" ref="458371270"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSPercent">0.90131578947368418</double>
|
||||||
|
</object>
|
||||||
|
<object class="NSScroller" id="47224920">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">-2147483392</int>
|
||||||
|
<string key="NSFrame">{{1, 154}, {438, 15}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<int key="NSsFlags">1</int>
|
||||||
|
<reference key="NSTarget" ref="458371270"/>
|
||||||
|
<string key="NSAction">_doScroller:</string>
|
||||||
|
<double key="NSCurValue">1</double>
|
||||||
|
<double key="NSPercent">0.98871331828442433</double>
|
||||||
|
</object>
|
||||||
|
<object class="NSClipView" id="777677330">
|
||||||
|
<reference key="NSNextResponder" ref="458371270"/>
|
||||||
|
<int key="NSvFlags">2304</int>
|
||||||
|
<array class="NSMutableArray" key="NSSubviews">
|
||||||
|
<reference ref="903452051"/>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{1, 0}, {438, 17}}</string>
|
||||||
|
<reference key="NSSuperview" ref="458371270"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="903452051"/>
|
||||||
|
<reference key="NSDocView" ref="903452051"/>
|
||||||
|
<reference key="NSBGColor" ref="765209443"/>
|
||||||
|
<int key="NScvFlags">4</int>
|
||||||
|
</object>
|
||||||
|
<reference ref="564034022"/>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrame">{{20, 60}, {440, 170}}</string>
|
||||||
|
<reference key="NSSuperview" ref="976198330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<reference key="NSNextKeyView" ref="831830981"/>
|
||||||
|
<int key="NSsFlags">133682</int>
|
||||||
|
<reference key="NSVScroller" ref="99096694"/>
|
||||||
|
<reference key="NSHScroller" ref="47224920"/>
|
||||||
|
<reference key="NSContentView" ref="831830981"/>
|
||||||
|
<reference key="NSHeaderClipView" ref="777677330"/>
|
||||||
|
<reference key="NSCornerView" ref="564034022"/>
|
||||||
|
<bytes key="NSScrollAmts">QSAAAEEgAABBmAAAQZgAAA</bytes>
|
||||||
|
</object>
|
||||||
|
<object class="NSButton" id="4380169">
|
||||||
|
<reference key="NSNextResponder" ref="976198330"/>
|
||||||
|
<int key="NSvFlags">289</int>
|
||||||
|
<string key="NSFrame">{{356, 12}, {110, 32}}</string>
|
||||||
|
<reference key="NSSuperview" ref="976198330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSButtonCell" key="NSCell" id="373771329">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">134217728</int>
|
||||||
|
<string key="NSContents">Close</string>
|
||||||
|
<object class="NSFont" key="NSSupport" id="680801460">
|
||||||
|
<string key="NSName">LucidaGrande</string>
|
||||||
|
<double key="NSSize">13</double>
|
||||||
|
<int key="NSfFlags">1044</int>
|
||||||
|
</object>
|
||||||
|
<reference key="NSControlView" ref="4380169"/>
|
||||||
|
<int key="NSButtonFlags">-2038284033</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string type="base64-UTF8" key="NSKeyEquivalent">DQ</string>
|
||||||
|
<int key="NSPeriodicDelay">200</int>
|
||||||
|
<int key="NSPeriodicInterval">25</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSButton" id="253286088">
|
||||||
|
<reference key="NSNextResponder" ref="976198330"/>
|
||||||
|
<int key="NSvFlags">292</int>
|
||||||
|
<string key="NSFrame">{{14, 12}, {162, 32}}</string>
|
||||||
|
<reference key="NSSuperview" ref="976198330"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
<bool key="NSEnabled">YES</bool>
|
||||||
|
<object class="NSButtonCell" key="NSCell" id="671547957">
|
||||||
|
<int key="NSCellFlags">67239424</int>
|
||||||
|
<int key="NSCellFlags2">134217728</int>
|
||||||
|
<string key="NSContents">Reveal Selected</string>
|
||||||
|
<reference key="NSSupport" ref="680801460"/>
|
||||||
|
<reference key="NSControlView" ref="253286088"/>
|
||||||
|
<int key="NSButtonFlags">-2038284033</int>
|
||||||
|
<int key="NSButtonFlags2">129</int>
|
||||||
|
<string key="NSAlternateContents"/>
|
||||||
|
<string key="NSKeyEquivalent"/>
|
||||||
|
<int key="NSPeriodicDelay">200</int>
|
||||||
|
<int key="NSPeriodicInterval">25</int>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<string key="NSFrameSize">{480, 309}</string>
|
||||||
|
<reference key="NSSuperview"/>
|
||||||
|
<reference key="NSWindow"/>
|
||||||
|
</object>
|
||||||
|
<string key="NSScreenRect">{{0, 0}, {1920, 1058}}</string>
|
||||||
|
<string key="NSMaxSize">{10000000000000, 10000000000000}</string>
|
||||||
|
<bool key="NSWindowIsRestorable">YES</bool>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<object class="IBObjectContainer" key="IBDocument.Objects">
|
||||||
|
<array class="NSMutableArray" key="connectionRecords">
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">window</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="359561441"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">22</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBActionConnection" key="connection">
|
||||||
|
<string key="label">revealSelected:</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="253286088"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">24</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBOutletConnection" key="connection">
|
||||||
|
<string key="label">problemTableView</string>
|
||||||
|
<reference key="source" ref="1001"/>
|
||||||
|
<reference key="destination" ref="252791348"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">26</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBConnectionRecord">
|
||||||
|
<object class="IBActionConnection" key="connection">
|
||||||
|
<string key="label">performClose:</string>
|
||||||
|
<reference key="source" ref="359561441"/>
|
||||||
|
<reference key="destination" ref="4380169"/>
|
||||||
|
</object>
|
||||||
|
<int key="connectionID">25</int>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||||
|
<array key="orderedObjects">
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">0</int>
|
||||||
|
<array key="object" id="0"/>
|
||||||
|
<reference key="children" ref="1000"/>
|
||||||
|
<nil key="parent"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-2</int>
|
||||||
|
<reference key="object" ref="1001"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">File's Owner</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-1</int>
|
||||||
|
<reference key="object" ref="1003"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">First Responder</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">-3</int>
|
||||||
|
<reference key="object" ref="1004"/>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
<string key="objectName">Application</string>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">1</int>
|
||||||
|
<reference key="object" ref="359561441"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="976198330"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="0"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">2</int>
|
||||||
|
<reference key="object" ref="976198330"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="573725554"/>
|
||||||
|
<reference ref="458371270"/>
|
||||||
|
<reference ref="4380169"/>
|
||||||
|
<reference ref="253286088"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="359561441"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">3</int>
|
||||||
|
<reference key="object" ref="573725554"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="1063844428"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="976198330"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">4</int>
|
||||||
|
<reference key="object" ref="1063844428"/>
|
||||||
|
<reference key="parent" ref="573725554"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">5</int>
|
||||||
|
<reference key="object" ref="458371270"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="99096694"/>
|
||||||
|
<reference ref="47224920"/>
|
||||||
|
<reference ref="252791348"/>
|
||||||
|
<reference ref="903452051"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="976198330"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">6</int>
|
||||||
|
<reference key="object" ref="99096694"/>
|
||||||
|
<reference key="parent" ref="458371270"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">7</int>
|
||||||
|
<reference key="object" ref="47224920"/>
|
||||||
|
<reference key="parent" ref="458371270"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">8</int>
|
||||||
|
<reference key="object" ref="252791348"/>
|
||||||
|
<array class="NSMutableArray" key="children"/>
|
||||||
|
<reference key="parent" ref="458371270"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">9</int>
|
||||||
|
<reference key="object" ref="903452051"/>
|
||||||
|
<reference key="parent" ref="458371270"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">18</int>
|
||||||
|
<reference key="object" ref="4380169"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="373771329"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="976198330"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">19</int>
|
||||||
|
<reference key="object" ref="373771329"/>
|
||||||
|
<reference key="parent" ref="4380169"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">20</int>
|
||||||
|
<reference key="object" ref="253286088"/>
|
||||||
|
<array class="NSMutableArray" key="children">
|
||||||
|
<reference ref="671547957"/>
|
||||||
|
</array>
|
||||||
|
<reference key="parent" ref="976198330"/>
|
||||||
|
</object>
|
||||||
|
<object class="IBObjectRecord">
|
||||||
|
<int key="objectID">21</int>
|
||||||
|
<reference key="object" ref="671547957"/>
|
||||||
|
<reference key="parent" ref="253286088"/>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
</object>
|
||||||
|
<dictionary class="NSMutableDictionary" key="flattenedProperties">
|
||||||
|
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="1.IBWindowTemplateEditedContentRect">{{477, 306}, {480, 309}}</string>
|
||||||
|
<boolean value="NO" key="1.NSWindowTemplate.visibleAtLaunch"/>
|
||||||
|
<string key="18.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="19.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="20.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="21.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="4.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="5.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="6.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="7.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="8.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
<string key="9.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
|
||||||
|
</dictionary>
|
||||||
|
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
|
||||||
|
<nil key="activeLocalization"/>
|
||||||
|
<dictionary class="NSMutableDictionary" key="localizations"/>
|
||||||
|
<nil key="sourceID"/>
|
||||||
|
<int key="maxID">26</int>
|
||||||
|
</object>
|
||||||
|
<object class="IBClassDescriber" key="IBDocument.Classes">
|
||||||
|
<array class="NSMutableArray" key="referencedPartialClassDescriptions">
|
||||||
|
<object class="IBPartialClassDescription">
|
||||||
|
<string key="className">ProblemDialog</string>
|
||||||
|
<string key="superclassName">NSWindowController</string>
|
||||||
|
<object class="NSMutableDictionary" key="actions">
|
||||||
|
<string key="NS.key.0">revealSelected:</string>
|
||||||
|
<string key="NS.object.0">id</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="actionInfosByName">
|
||||||
|
<string key="NS.key.0">revealSelected:</string>
|
||||||
|
<object class="IBActionInfo" key="NS.object.0">
|
||||||
|
<string key="name">revealSelected:</string>
|
||||||
|
<string key="candidateClassName">id</string>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="outlets">
|
||||||
|
<string key="NS.key.0">problemTableView</string>
|
||||||
|
<string key="NS.object.0">NSTableView</string>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="toOneOutletInfosByName">
|
||||||
|
<string key="NS.key.0">problemTableView</string>
|
||||||
|
<object class="IBToOneOutletInfo" key="NS.object.0">
|
||||||
|
<string key="name">problemTableView</string>
|
||||||
|
<string key="candidateClassName">NSTableView</string>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
<object class="IBClassDescriptionSource" key="sourceIdentifier">
|
||||||
|
<string key="majorKey">IBProjectSource</string>
|
||||||
|
<string key="minorKey">./Classes/ProblemDialog.h</string>
|
||||||
|
</object>
|
||||||
|
</object>
|
||||||
|
</array>
|
||||||
|
</object>
|
||||||
|
<int key="IBDocument.localizationMode">0</int>
|
||||||
|
<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
|
<real value="1060" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencyDefaults">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string>
|
||||||
|
<real value="1060" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
|
||||||
|
<string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3</string>
|
||||||
|
<real value="4100" key="NS.object.0"/>
|
||||||
|
</object>
|
||||||
|
<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
|
||||||
|
<int key="IBDocument.defaultPropertyAccessControl">3</int>
|
||||||
|
</data>
|
||||||
|
</archive>
|
||||||
96
cocoa/base/en.lproj/ResultWindow.strings
Normal file
96
cocoa/base/en.lproj/ResultWindow.strings
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
|
||||||
|
/* Class = "NSWindow"; title = "dupeGuru Results"; ObjectID = "1"; */
|
||||||
|
"1.title" = "dupeGuru Results";
|
||||||
|
|
||||||
|
/* Class = "NSTextFieldCell"; title = "Marked: 0 files, 0 B. Total: 0 files, 0 B."; ObjectID = "6"; */
|
||||||
|
"6.title" = "Marked: 0 files, 0 B. Total: 0 files, 0 B.";
|
||||||
|
|
||||||
|
/* Class = "NSToolbarItem"; label = "Options"; ObjectID = "15"; */
|
||||||
|
"15.label" = "Options";
|
||||||
|
|
||||||
|
/* Class = "NSToolbarItem"; paletteLabel = "Options"; ObjectID = "15"; */
|
||||||
|
"15.paletteLabel" = "Options";
|
||||||
|
|
||||||
|
/* Class = "NSToolbarItem"; label = "Filter"; ObjectID = "16"; */
|
||||||
|
"16.label" = "Filter";
|
||||||
|
|
||||||
|
/* Class = "NSToolbarItem"; paletteLabel = "Filter"; ObjectID = "16"; */
|
||||||
|
"16.paletteLabel" = "Filter";
|
||||||
|
|
||||||
|
/* Class = "NSToolbarItem"; label = "Action"; ObjectID = "17"; */
|
||||||
|
"17.label" = "Action";
|
||||||
|
|
||||||
|
/* Class = "NSToolbarItem"; paletteLabel = "Action"; ObjectID = "17"; */
|
||||||
|
"17.paletteLabel" = "Action";
|
||||||
|
|
||||||
|
/* Class = "NSToolbarItem"; label = "Directories"; ObjectID = "19"; */
|
||||||
|
"19.label" = "Directories";
|
||||||
|
|
||||||
|
/* Class = "NSToolbarItem"; paletteLabel = "Directories"; ObjectID = "19"; */
|
||||||
|
"19.paletteLabel" = "Directories";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Delete Marked and Replace with Hardlinks"; ObjectID = "27"; */
|
||||||
|
"27.title" = "Delete Marked and Replace with Hardlinks";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Send Marked to Trash"; ObjectID = "29"; */
|
||||||
|
"29.title" = "Send Marked to Trash";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Move Marked to..."; ObjectID = "30"; */
|
||||||
|
"30.title" = "Move Marked to...";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Copy Marked to..."; ObjectID = "31"; */
|
||||||
|
"31.title" = "Copy Marked to...";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Remove Marked from Results"; ObjectID = "32"; */
|
||||||
|
"32.title" = "Remove Marked from Results";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Remove Selected from Results"; ObjectID = "34"; */
|
||||||
|
"34.title" = "Remove Selected from Results";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Add Selected to Ignore List"; ObjectID = "35"; */
|
||||||
|
"35.title" = "Add Selected to Ignore List";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Make Selected Reference"; ObjectID = "36"; */
|
||||||
|
"36.title" = "Make Selected Reference";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Open Selected with Default Application"; ObjectID = "38"; */
|
||||||
|
"38.title" = "Open Selected with Default Application";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Reveal Selected in Finder"; ObjectID = "39"; */
|
||||||
|
"39.title" = "Reveal Selected in Finder";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Rename Selected"; ObjectID = "40"; */
|
||||||
|
"40.title" = "Rename Selected";
|
||||||
|
|
||||||
|
/* Class = "NSSearchFieldCell"; placeholderString = "Filter"; ObjectID = "42"; */
|
||||||
|
"42.placeholderString" = "Filter";
|
||||||
|
|
||||||
|
/* Class = "NSSegmentedCell"; 44.ibShadowedLabels[0] = "Details"; ObjectID = "44"; */
|
||||||
|
"44.ibShadowedLabels[0]" = "Details";
|
||||||
|
|
||||||
|
/* Class = "NSSegmentedCell"; 44.ibShadowedLabels[1] = "Dupes Only"; ObjectID = "44"; */
|
||||||
|
"44.ibShadowedLabels[1]" = "Dupes Only";
|
||||||
|
|
||||||
|
/* Class = "NSSegmentedCell"; 44.ibShadowedLabels[2] = "Delta"; ObjectID = "44"; */
|
||||||
|
"44.ibShadowedLabels[2]" = "Delta";
|
||||||
|
|
||||||
|
/* Class = "NSMenu"; title = "Menu"; ObjectID = "67"; */
|
||||||
|
"67.title" = "Menu";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Add Selected to Ignore List"; ObjectID = "68"; */
|
||||||
|
"68.title" = "Add Selected to Ignore List";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Rename Selected"; ObjectID = "70"; */
|
||||||
|
"70.title" = "Rename Selected";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Remove Selected from Results"; ObjectID = "71"; */
|
||||||
|
"71.title" = "Remove Selected from Results";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Make Selected Reference"; ObjectID = "72"; */
|
||||||
|
"72.title" = "Make Selected Reference";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Reveal Selected in Finder"; ObjectID = "73"; */
|
||||||
|
"73.title" = "Reveal Selected in Finder";
|
||||||
|
|
||||||
|
/* Class = "NSMenuItem"; title = "Open Selected with Default Application"; ObjectID = "74"; */
|
||||||
|
"74.title" = "Open Selected with Default Application";
|
||||||
1818
cocoa/base/en.lproj/ResultWindow.xib
Normal file
1818
cocoa/base/en.lproj/ResultWindow.xib
Normal file
File diff suppressed because it is too large
Load Diff
41
cocoa/base/main.m
Normal file
41
cocoa/base/main.m
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <Python.h>
|
||||||
|
#import <wchar.h>
|
||||||
|
#import <locale.h>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||||
|
/* We have to set the locate to UTF8 for mbstowcs() to correctly convert non-ascii chars in paths */
|
||||||
|
setlocale(LC_ALL, "en_US.UTF-8");
|
||||||
|
NSString *respath = [[NSBundle mainBundle] resourcePath];
|
||||||
|
NSString *mainpy = [respath stringByAppendingPathComponent:@"dg_cocoa.py"];
|
||||||
|
wchar_t wPythonPath[PATH_MAX+1];
|
||||||
|
NSString *pypath = [respath stringByAppendingPathComponent:@"py"];
|
||||||
|
mbstowcs(wPythonPath, [pypath fileSystemRepresentation], PATH_MAX+1);
|
||||||
|
Py_SetPath(wPythonPath);
|
||||||
|
Py_SetPythonHome(wPythonPath);
|
||||||
|
Py_Initialize();
|
||||||
|
PyEval_InitThreads();
|
||||||
|
PyGILState_STATE gilState = PyGILState_Ensure();
|
||||||
|
FILE* fp = fopen([mainpy UTF8String], "r");
|
||||||
|
PyRun_SimpleFile(fp, [mainpy UTF8String]);
|
||||||
|
fclose(fp);
|
||||||
|
PyGILState_Release(gilState);
|
||||||
|
if (gilState == PyGILState_LOCKED) {
|
||||||
|
PyThreadState_Swap(NULL);
|
||||||
|
PyEval_ReleaseLock();
|
||||||
|
}
|
||||||
|
int result = NSApplicationMain(argc, (const char **) argv);
|
||||||
|
Py_Finalize();
|
||||||
|
[pool release];
|
||||||
|
return result;
|
||||||
|
}
|
||||||
219
cocoa/inter/app.py
Normal file
219
cocoa/inter/app.py
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from objp.util import pyref, dontwrap
|
||||||
|
from jobprogress import job
|
||||||
|
import cocoa
|
||||||
|
from cocoa import install_exception_hook, proxy
|
||||||
|
from cocoa.inter import PyFairware, FairwareView
|
||||||
|
from hscommon.trans import trget
|
||||||
|
|
||||||
|
from core.app import JobType
|
||||||
|
|
||||||
|
tr = trget('ui')
|
||||||
|
|
||||||
|
JOBID2TITLE = {
|
||||||
|
JobType.Scan: tr("Scanning for duplicates"),
|
||||||
|
JobType.Load: tr("Loading"),
|
||||||
|
JobType.Move: tr("Moving"),
|
||||||
|
JobType.Copy: tr("Copying"),
|
||||||
|
JobType.Delete: tr("Sending to Trash"),
|
||||||
|
}
|
||||||
|
|
||||||
|
class DupeGuruView(FairwareView):
|
||||||
|
def askYesNoWithPrompt_(self, prompt: str) -> bool: pass
|
||||||
|
def showProblemDialog(self): pass
|
||||||
|
def selectDestFolderWithPrompt_(self, prompt: str) -> str: pass
|
||||||
|
|
||||||
|
class PyDupeGuruBase(PyFairware):
|
||||||
|
FOLLOW_PROTOCOLS = ['Worker']
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def _init(self, modelclass):
|
||||||
|
logging.basicConfig(level=logging.WARNING, format='%(levelname)s %(message)s')
|
||||||
|
install_exception_hook()
|
||||||
|
appdata = proxy.getAppdataPath()
|
||||||
|
self.model = modelclass(self, appdata)
|
||||||
|
self.progress = cocoa.ThreadedJobPerformer()
|
||||||
|
|
||||||
|
#---Sub-proxies
|
||||||
|
def detailsPanel(self) -> pyref:
|
||||||
|
return self.model.details_panel
|
||||||
|
|
||||||
|
def directoryTree(self) -> pyref:
|
||||||
|
return self.model.directory_tree
|
||||||
|
|
||||||
|
def problemDialog(self) -> pyref:
|
||||||
|
return self.model.problem_dialog
|
||||||
|
|
||||||
|
def statsLabel(self) -> pyref:
|
||||||
|
return self.model.stats_label
|
||||||
|
|
||||||
|
def resultTable(self) -> pyref:
|
||||||
|
return self.model.result_table
|
||||||
|
|
||||||
|
def ignoreListDialog(self) -> pyref:
|
||||||
|
return self.model.ignore_list_dialog
|
||||||
|
|
||||||
|
#---Directories
|
||||||
|
def addDirectory_(self, directory: str) -> int:
|
||||||
|
return self.model.add_directory(directory)
|
||||||
|
|
||||||
|
#---Results
|
||||||
|
def doScan(self):
|
||||||
|
self.model.start_scanning()
|
||||||
|
|
||||||
|
def exportToXHTML(self) -> str:
|
||||||
|
return self.model.export_to_xhtml()
|
||||||
|
|
||||||
|
def loadSession(self):
|
||||||
|
self.model.load()
|
||||||
|
|
||||||
|
def loadResultsFrom_(self, filename: str):
|
||||||
|
self.model.load_from(filename)
|
||||||
|
|
||||||
|
def markAll(self):
|
||||||
|
self.model.mark_all()
|
||||||
|
|
||||||
|
def markNone(self):
|
||||||
|
self.model.mark_none()
|
||||||
|
|
||||||
|
def markInvert(self):
|
||||||
|
self.model.mark_invert()
|
||||||
|
|
||||||
|
def purgeIgnoreList(self):
|
||||||
|
self.model.purge_ignore_list()
|
||||||
|
|
||||||
|
def toggleSelectedMark(self):
|
||||||
|
self.model.toggle_selected_mark_state()
|
||||||
|
|
||||||
|
def saveSession(self):
|
||||||
|
self.model.save()
|
||||||
|
|
||||||
|
def saveResultsAs_(self, filename: str):
|
||||||
|
self.model.save_as(filename)
|
||||||
|
|
||||||
|
#---Actions
|
||||||
|
def addSelectedToIgnoreList(self):
|
||||||
|
self.model.add_selected_to_ignore_list()
|
||||||
|
|
||||||
|
def deleteMarked(self):
|
||||||
|
self.model.delete_marked()
|
||||||
|
|
||||||
|
def hardlinkMarked(self):
|
||||||
|
self.model.delete_marked(replace_with_hardlinks=True)
|
||||||
|
|
||||||
|
def applyFilter_(self, filter: str):
|
||||||
|
self.model.apply_filter(filter)
|
||||||
|
|
||||||
|
def makeSelectedReference(self):
|
||||||
|
self.model.make_selected_reference()
|
||||||
|
|
||||||
|
def copyMarked(self):
|
||||||
|
self.model.copy_or_move_marked(copy=True)
|
||||||
|
|
||||||
|
def moveMarked(self):
|
||||||
|
self.model.copy_or_move_marked(copy=False)
|
||||||
|
|
||||||
|
def openSelected(self):
|
||||||
|
self.model.open_selected()
|
||||||
|
|
||||||
|
def removeMarked(self):
|
||||||
|
self.model.remove_marked()
|
||||||
|
|
||||||
|
def removeSelected(self):
|
||||||
|
self.model.remove_selected()
|
||||||
|
|
||||||
|
def revealSelected(self):
|
||||||
|
self.model.reveal_selected()
|
||||||
|
|
||||||
|
def invokeCustomCommand(self):
|
||||||
|
self.model.invoke_custom_command()
|
||||||
|
|
||||||
|
def showIgnoreList(self):
|
||||||
|
self.model.ignore_list_dialog.show()
|
||||||
|
|
||||||
|
#---Information
|
||||||
|
def resultsAreModified(self) -> bool:
|
||||||
|
return self.model.results.is_modified
|
||||||
|
|
||||||
|
#---Properties
|
||||||
|
def setMixFileKind_(self, mix_file_kind: bool):
|
||||||
|
self.model.scanner.mix_file_kind = mix_file_kind
|
||||||
|
|
||||||
|
def setEscapeFilterRegexp_(self, escape_filter_regexp: bool):
|
||||||
|
self.model.options['escape_filter_regexp'] = escape_filter_regexp
|
||||||
|
|
||||||
|
def setRemoveEmptyFolders_(self, remove_empty_folders: bool):
|
||||||
|
self.model.options['clean_empty_dirs'] = remove_empty_folders
|
||||||
|
|
||||||
|
def setIgnoreHardlinkMatches_(self, ignore_hardlink_matches: bool):
|
||||||
|
self.model.options['ignore_hardlink_matches'] = ignore_hardlink_matches
|
||||||
|
|
||||||
|
def setCopyMoveDestType_(self, copymove_dest_type: int):
|
||||||
|
self.model.options['copymove_dest_type'] = copymove_dest_type
|
||||||
|
|
||||||
|
#---Worker
|
||||||
|
def getJobProgress(self) -> object: # NSNumber
|
||||||
|
try:
|
||||||
|
return self.progress.last_progress
|
||||||
|
except AttributeError:
|
||||||
|
# I have *no idea* why this can possible happen (last_progress is always set by
|
||||||
|
# create_job() *before* any threaded job notification, which shows the progress panel,
|
||||||
|
# is sent), but it happens anyway, so there we go. ref: #106
|
||||||
|
return -1
|
||||||
|
|
||||||
|
def getJobDesc(self) -> str:
|
||||||
|
try:
|
||||||
|
return self.progress.last_desc
|
||||||
|
except AttributeError:
|
||||||
|
# see getJobProgress
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def cancelJob(self):
|
||||||
|
self.progress.job_cancelled = True
|
||||||
|
|
||||||
|
def jobCompleted_(self, jobid: str):
|
||||||
|
result = self.model._job_completed(jobid, self.progress.last_error)
|
||||||
|
if not result:
|
||||||
|
self.progress.reraise_if_error()
|
||||||
|
|
||||||
|
#--- model --> view
|
||||||
|
@dontwrap
|
||||||
|
def open_path(self, path):
|
||||||
|
proxy.openPath_(str(path))
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def reveal_path(self, path):
|
||||||
|
proxy.revealPath_(str(path))
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def start_job(self, jobid, func, args=()):
|
||||||
|
try:
|
||||||
|
j = self.progress.create_job()
|
||||||
|
args = tuple([j] + list(args))
|
||||||
|
self.progress.run_threaded(func, args=args)
|
||||||
|
except job.JobInProgressError:
|
||||||
|
proxy.postNotification_userInfo_('JobInProgress', None)
|
||||||
|
else:
|
||||||
|
ud = {'desc': JOBID2TITLE[jobid], 'jobid':jobid}
|
||||||
|
proxy.postNotification_userInfo_('JobStarted', ud)
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def ask_yes_no(self, prompt):
|
||||||
|
return self.callback.askYesNoWithPrompt_(prompt)
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def show_results_window(self):
|
||||||
|
# Not needed yet because our progress dialog is shown as a sheet of the results window,
|
||||||
|
# which causes it to be already visible when the scan/load ends.
|
||||||
|
# XXX Make progress sheet be a child of the folder selection window.
|
||||||
|
pass
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def show_problem_dialog(self):
|
||||||
|
self.callback.showProblemDialog()
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def select_dest_folder(self, prompt):
|
||||||
|
return self.callback.selectDestFolderWithPrompt_(prompt)
|
||||||
|
|
||||||
283
cocoa/inter/app_me.py
Normal file
283
cocoa/inter/app_me.py
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2006/11/16
|
||||||
|
# Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
# which should be included with this package. The terms are also available at
|
||||||
|
# http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from appscript import app, its, k, CommandError, ApplicationNotFoundError
|
||||||
|
import plistlib
|
||||||
|
import time
|
||||||
|
import os.path as op
|
||||||
|
|
||||||
|
from cocoa import as_fetch, proxy
|
||||||
|
from hscommon import io
|
||||||
|
from hscommon.trans import trget
|
||||||
|
from hscommon.path import Path
|
||||||
|
|
||||||
|
from core import directories
|
||||||
|
from core.app import JobType
|
||||||
|
from core.scanner import ScanType
|
||||||
|
from core_me.app import DupeGuru as DupeGuruBase
|
||||||
|
from core_me import fs
|
||||||
|
from .app import JOBID2TITLE, PyDupeGuruBase
|
||||||
|
|
||||||
|
tr = trget('ui')
|
||||||
|
|
||||||
|
JobType.RemoveDeadTracks = 'jobRemoveDeadTracks'
|
||||||
|
JobType.ScanDeadTracks = 'jobScanDeadTracks'
|
||||||
|
|
||||||
|
JOBID2TITLE.update({
|
||||||
|
JobType.RemoveDeadTracks: tr("Removing dead tracks from your iTunes Library"),
|
||||||
|
JobType.ScanDeadTracks: tr("Scanning the iTunes Library"),
|
||||||
|
})
|
||||||
|
|
||||||
|
ITUNES = 'iTunes'
|
||||||
|
ITUNES_PATH = Path('iTunes Library')
|
||||||
|
|
||||||
|
def get_itunes_library(a):
|
||||||
|
try:
|
||||||
|
[source] = [s for s in a.sources(timeout=0) if s.kind(timeout=0) == k.library]
|
||||||
|
[library] = source.library_playlists(timeout=0)
|
||||||
|
return library
|
||||||
|
except ValueError:
|
||||||
|
logging.warning('Some unexpected iTunes configuration encountered')
|
||||||
|
return None
|
||||||
|
|
||||||
|
class ITunesSong(fs.MusicFile):
|
||||||
|
def __init__(self, song_data):
|
||||||
|
path = Path(proxy.url2path_(song_data['Location']))
|
||||||
|
fs.MusicFile.__init__(self, path)
|
||||||
|
self.id = song_data['Track ID']
|
||||||
|
|
||||||
|
def remove_from_library(self):
|
||||||
|
try:
|
||||||
|
a = app(ITUNES)
|
||||||
|
library = get_itunes_library(a)
|
||||||
|
if library is None:
|
||||||
|
return
|
||||||
|
[song] = library.file_tracks[its.database_ID == self.id]()
|
||||||
|
a.delete(song, timeout=0)
|
||||||
|
except ValueError:
|
||||||
|
msg = "Could not find song '{}' (trackid: {}) in iTunes Library".format(str(self.path), self.id)
|
||||||
|
raise EnvironmentError(msg)
|
||||||
|
except (CommandError, RuntimeError) as e:
|
||||||
|
raise EnvironmentError(str(e))
|
||||||
|
|
||||||
|
display_folder_path = ITUNES_PATH
|
||||||
|
|
||||||
|
def get_itunes_database_path():
|
||||||
|
plisturls = proxy.prefValue_inDomain_('iTunesRecentDatabases', 'com.apple.iApps')
|
||||||
|
if not plisturls:
|
||||||
|
raise directories.InvalidPathError()
|
||||||
|
plistpath = proxy.url2path_(plisturls[0])
|
||||||
|
return Path(plistpath)
|
||||||
|
|
||||||
|
def get_itunes_songs(plistpath):
|
||||||
|
if not io.exists(plistpath):
|
||||||
|
return []
|
||||||
|
plist = plistlib.readPlist(str(plistpath))
|
||||||
|
result = []
|
||||||
|
for song_data in plist['Tracks'].values():
|
||||||
|
if song_data['Track Type'] != 'File':
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
song = ITunesSong(song_data)
|
||||||
|
except KeyError: # No "Location" or "Track ID" key in track
|
||||||
|
continue
|
||||||
|
if io.exists(song.path):
|
||||||
|
result.append(song)
|
||||||
|
return result
|
||||||
|
|
||||||
|
class Directories(directories.Directories):
|
||||||
|
def __init__(self, fileclasses):
|
||||||
|
directories.Directories.__init__(self, fileclasses)
|
||||||
|
try:
|
||||||
|
self.itunes_libpath = get_itunes_database_path()
|
||||||
|
except directories.InvalidPathError:
|
||||||
|
self.itunes_libpath = None
|
||||||
|
|
||||||
|
def _get_files(self, from_path, j):
|
||||||
|
if from_path == ITUNES_PATH:
|
||||||
|
if self.itunes_libpath is None:
|
||||||
|
return []
|
||||||
|
is_ref = self.get_state(from_path) == directories.DirectoryState.Reference
|
||||||
|
songs = get_itunes_songs(self.itunes_libpath)
|
||||||
|
for song in songs:
|
||||||
|
song.is_ref = is_ref
|
||||||
|
return songs
|
||||||
|
else:
|
||||||
|
return directories.Directories._get_files(self, from_path, j)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_subfolders(path):
|
||||||
|
if path == ITUNES_PATH:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return directories.Directories.get_subfolders(path)
|
||||||
|
|
||||||
|
def add_path(self, path):
|
||||||
|
if path == ITUNES_PATH:
|
||||||
|
if path not in self:
|
||||||
|
self._dirs.append(path)
|
||||||
|
else:
|
||||||
|
directories.Directories.add_path(self, path)
|
||||||
|
|
||||||
|
def has_itunes_path(self):
|
||||||
|
return any(path == ITUNES_PATH for path in self._dirs)
|
||||||
|
|
||||||
|
def has_any_file(self):
|
||||||
|
# If we don't do that, it causes a hangup in the GUI when we click Start Scanning because
|
||||||
|
# checking if there's any file to scan involves reading the whole library. If we have the
|
||||||
|
# iTunes library, we assume we have at least one file.
|
||||||
|
if self.has_itunes_path():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return directories.Directories.has_any_file(self)
|
||||||
|
|
||||||
|
|
||||||
|
class DupeGuruME(DupeGuruBase):
|
||||||
|
def __init__(self, view, appdata):
|
||||||
|
appdata = op.join(appdata, 'dupeGuru Music Edition')
|
||||||
|
DupeGuruBase.__init__(self, view, appdata)
|
||||||
|
# Use fileclasses set in DupeGuruBase.__init__()
|
||||||
|
self.directories = Directories(fileclasses=self.directories.fileclasses)
|
||||||
|
self.dead_tracks = []
|
||||||
|
|
||||||
|
def _do_delete(self, j, replace_with_hardlinks):
|
||||||
|
# XXX If I read correctly, Python 3.3 will allow us to go fetch inner function easily, so
|
||||||
|
# we'll be able to replace "op" below with DupeGuruBase._do_delete.op.
|
||||||
|
def op(dupe):
|
||||||
|
j.add_progress()
|
||||||
|
return self._do_delete_dupe(dupe, replace_with_hardlinks)
|
||||||
|
|
||||||
|
marked = [dupe for dupe in self.results.dupes if self.results.is_marked(dupe)]
|
||||||
|
j.start_job(self.results.mark_count, tr("Sending dupes to the Trash"))
|
||||||
|
if any(isinstance(dupe, ITunesSong) for dupe in marked):
|
||||||
|
j.add_progress(0, desc=tr("Talking to iTunes. Don't touch it!"))
|
||||||
|
try:
|
||||||
|
a = app(ITUNES)
|
||||||
|
a.activate(timeout=0)
|
||||||
|
except (CommandError, RuntimeError, ApplicationNotFoundError):
|
||||||
|
pass
|
||||||
|
self.results.perform_on_marked(op, True)
|
||||||
|
|
||||||
|
def _do_delete_dupe(self, dupe, replace_with_hardlinks):
|
||||||
|
if isinstance(dupe, ITunesSong):
|
||||||
|
dupe.remove_from_library()
|
||||||
|
DupeGuruBase._do_delete_dupe(self, dupe, replace_with_hardlinks)
|
||||||
|
|
||||||
|
def _create_file(self, path):
|
||||||
|
if (self.directories.itunes_libpath is not None) and (path in self.directories.itunes_libpath[:-1]):
|
||||||
|
if not hasattr(self, 'itunes_songs'):
|
||||||
|
songs = get_itunes_songs(self.directories.itunes_libpath)
|
||||||
|
self.itunes_songs = {song.path: song for song in songs}
|
||||||
|
if path in self.itunes_songs:
|
||||||
|
return self.itunes_songs[path]
|
||||||
|
else:
|
||||||
|
pass # We'll return the default file type, as per the last line of this method
|
||||||
|
return DupeGuruBase._create_file(self, path)
|
||||||
|
|
||||||
|
def _job_completed(self, jobid, exc):
|
||||||
|
if (jobid in {JobType.RemoveDeadTracks, JobType.ScanDeadTracks}) and (exc is not None):
|
||||||
|
msg = tr("There were communication problems with iTunes. The operation couldn't be completed.")
|
||||||
|
self.view.show_message(msg)
|
||||||
|
return True
|
||||||
|
if jobid == JobType.ScanDeadTracks:
|
||||||
|
dead_tracks_count = len(self.dead_tracks)
|
||||||
|
if dead_tracks_count > 0:
|
||||||
|
msg = tr("Your iTunes Library contains %d dead tracks ready to be removed. Continue?")
|
||||||
|
if self.view.ask_yes_no(msg % dead_tracks_count):
|
||||||
|
self.remove_dead_tracks()
|
||||||
|
else:
|
||||||
|
msg = tr("You have no dead tracks in your iTunes Library")
|
||||||
|
self.view.show_message(msg)
|
||||||
|
if jobid == JobType.Load:
|
||||||
|
if hasattr(self, 'itunes_songs'):
|
||||||
|
# If we load another file, we want a refresh song list
|
||||||
|
del self.itunes_songs
|
||||||
|
DupeGuruBase._job_completed(self, jobid, exc)
|
||||||
|
|
||||||
|
def copy_or_move(self, dupe, copy, destination, dest_type):
|
||||||
|
if isinstance(dupe, ITunesSong):
|
||||||
|
copy = True
|
||||||
|
return DupeGuruBase.copy_or_move(self, dupe, copy, destination, dest_type)
|
||||||
|
|
||||||
|
def start_scanning(self):
|
||||||
|
if self.directories.has_itunes_path():
|
||||||
|
try:
|
||||||
|
app(ITUNES)
|
||||||
|
except ApplicationNotFoundError:
|
||||||
|
self.view.show_message(tr("The iTunes application couldn't be found."))
|
||||||
|
return
|
||||||
|
DupeGuruBase.start_scanning(self)
|
||||||
|
|
||||||
|
def remove_dead_tracks(self):
|
||||||
|
def do(j):
|
||||||
|
a = app(ITUNES)
|
||||||
|
a.activate(timeout=0)
|
||||||
|
for index, track in enumerate(j.iter_with_progress(self.dead_tracks)):
|
||||||
|
if index % 100 == 0:
|
||||||
|
time.sleep(.1)
|
||||||
|
try:
|
||||||
|
track.delete(timeout=0)
|
||||||
|
except CommandError as e:
|
||||||
|
logging.warning('Error while trying to remove a track from iTunes: %s' % str(e))
|
||||||
|
|
||||||
|
self.view.start_job(JobType.RemoveDeadTracks, do)
|
||||||
|
|
||||||
|
def scan_dead_tracks(self):
|
||||||
|
def do(j):
|
||||||
|
a = app(ITUNES)
|
||||||
|
a.activate(timeout=0)
|
||||||
|
library = get_itunes_library(a)
|
||||||
|
if library is None:
|
||||||
|
return
|
||||||
|
self.dead_tracks = []
|
||||||
|
tracks = as_fetch(library.file_tracks, k.file_track)
|
||||||
|
for index, track in enumerate(j.iter_with_progress(tracks)):
|
||||||
|
if index % 100 == 0:
|
||||||
|
time.sleep(.1)
|
||||||
|
if track.location(timeout=0) == k.missing_value:
|
||||||
|
self.dead_tracks.append(track)
|
||||||
|
logging.info('Found %d dead tracks' % len(self.dead_tracks))
|
||||||
|
|
||||||
|
self.view.start_job(JobType.ScanDeadTracks, do)
|
||||||
|
|
||||||
|
class PyDupeGuru(PyDupeGuruBase):
|
||||||
|
def __init__(self):
|
||||||
|
self._init(DupeGuruME)
|
||||||
|
|
||||||
|
def scanDeadTracks(self):
|
||||||
|
self.model.scan_dead_tracks()
|
||||||
|
|
||||||
|
#---Properties
|
||||||
|
def setMinMatchPercentage_(self, percentage: int):
|
||||||
|
self.model.scanner.min_match_percentage = percentage
|
||||||
|
|
||||||
|
def setScanType_(self, scan_type: int):
|
||||||
|
try:
|
||||||
|
self.model.scanner.scan_type = [
|
||||||
|
ScanType.Filename,
|
||||||
|
ScanType.Fields,
|
||||||
|
ScanType.FieldsNoOrder,
|
||||||
|
ScanType.Tag,
|
||||||
|
ScanType.Contents,
|
||||||
|
ScanType.ContentsAudio,
|
||||||
|
][scan_type]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setWordWeighting_(self, words_are_weighted: bool):
|
||||||
|
self.model.scanner.word_weighting = words_are_weighted
|
||||||
|
|
||||||
|
def setMatchSimilarWords_(self, match_similar_words: bool):
|
||||||
|
self.model.scanner.match_similar_words = match_similar_words
|
||||||
|
|
||||||
|
def enable_scanForTag_(self, enable: bool, scan_tag: str):
|
||||||
|
if enable:
|
||||||
|
self.model.scanner.scanned_tags.add(scan_tag)
|
||||||
|
else:
|
||||||
|
self.model.scanner.scanned_tags.discard(scan_tag)
|
||||||
233
cocoa/inter/app_pe.py
Normal file
233
cocoa/inter/app_pe.py
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2006/11/13
|
||||||
|
# Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
# which should be included with this package. The terms are also available at
|
||||||
|
# http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
|
||||||
|
import os.path as op
|
||||||
|
import plistlib
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from appscript import app, its, CommandError, ApplicationNotFoundError
|
||||||
|
|
||||||
|
from hscommon import io
|
||||||
|
from hscommon.util import remove_invalid_xml
|
||||||
|
from hscommon.path import Path
|
||||||
|
from hscommon.trans import trget
|
||||||
|
from cocoa import proxy
|
||||||
|
|
||||||
|
from core.scanner import ScanType
|
||||||
|
from core import directories
|
||||||
|
from core_pe import _block_osx
|
||||||
|
from core_pe.photo import Photo as PhotoBase
|
||||||
|
from core_pe.app import DupeGuru as DupeGuruBase
|
||||||
|
from .app import PyDupeGuruBase
|
||||||
|
|
||||||
|
tr = trget('ui')
|
||||||
|
|
||||||
|
IPHOTO_PATH = Path('iPhoto Library')
|
||||||
|
|
||||||
|
class Photo(PhotoBase):
|
||||||
|
HANDLED_EXTS = PhotoBase.HANDLED_EXTS.copy()
|
||||||
|
HANDLED_EXTS.update({'psd', 'nef', 'cr2', 'orf'})
|
||||||
|
|
||||||
|
def _plat_get_dimensions(self):
|
||||||
|
return _block_osx.get_image_size(str(self.path))
|
||||||
|
|
||||||
|
def _plat_get_blocks(self, block_count_per_side, orientation):
|
||||||
|
try:
|
||||||
|
blocks = _block_osx.getblocks(str(self.path), block_count_per_side, orientation)
|
||||||
|
except Exception as e:
|
||||||
|
raise IOError('The reading of "%s" failed with "%s"' % (str(self.path), str(e)))
|
||||||
|
if not blocks:
|
||||||
|
raise IOError('The picture %s could not be read' % str(self.path))
|
||||||
|
return blocks
|
||||||
|
|
||||||
|
|
||||||
|
class IPhoto(Photo):
|
||||||
|
@property
|
||||||
|
def display_folder_path(self):
|
||||||
|
return IPHOTO_PATH
|
||||||
|
|
||||||
|
def get_iphoto_database_path():
|
||||||
|
plisturls = proxy.prefValue_inDomain_('iPhotoRecentDatabases', 'com.apple.iApps')
|
||||||
|
if not plisturls:
|
||||||
|
raise directories.InvalidPathError()
|
||||||
|
plistpath = proxy.url2path_(plisturls[0])
|
||||||
|
return Path(plistpath)
|
||||||
|
|
||||||
|
def get_iphoto_pictures(plistpath):
|
||||||
|
if not io.exists(plistpath):
|
||||||
|
return []
|
||||||
|
s = io.open(plistpath, 'rt', encoding='utf-8').read()
|
||||||
|
# There was a case where a guy had 0x10 chars in his plist, causing expat errors on loading
|
||||||
|
s = remove_invalid_xml(s, replace_with='')
|
||||||
|
# It seems that iPhoto sometimes doesn't properly escape & chars. The regexp below is to find
|
||||||
|
# any & char that is not a &-based entity (&, ", etc.). based on TextMate's XML
|
||||||
|
# bundle's regexp
|
||||||
|
s, count = re.subn(r'&(?![a-zA-Z0-9_-]+|#[0-9]+|#x[0-9a-fA-F]+;)', '', s)
|
||||||
|
if count:
|
||||||
|
logging.warning("%d invalid XML entities replacement made", count)
|
||||||
|
plist = plistlib.readPlistFromBytes(s.encode('utf-8'))
|
||||||
|
result = []
|
||||||
|
for photo_data in plist['Master Image List'].values():
|
||||||
|
if photo_data['MediaType'] != 'Image':
|
||||||
|
continue
|
||||||
|
photo_path = Path(photo_data['ImagePath'])
|
||||||
|
photo = IPhoto(photo_path)
|
||||||
|
result.append(photo)
|
||||||
|
return result
|
||||||
|
|
||||||
|
class Directories(directories.Directories):
|
||||||
|
def __init__(self):
|
||||||
|
directories.Directories.__init__(self, fileclasses=[Photo])
|
||||||
|
try:
|
||||||
|
self.iphoto_libpath = get_iphoto_database_path()
|
||||||
|
self.set_state(self.iphoto_libpath[:-1], directories.DirectoryState.Excluded)
|
||||||
|
except directories.InvalidPathError:
|
||||||
|
self.iphoto_libpath = None
|
||||||
|
|
||||||
|
def _get_files(self, from_path, j):
|
||||||
|
if from_path == IPHOTO_PATH:
|
||||||
|
if self.iphoto_libpath is None:
|
||||||
|
return []
|
||||||
|
is_ref = self.get_state(from_path) == directories.DirectoryState.Reference
|
||||||
|
photos = get_iphoto_pictures(self.iphoto_libpath)
|
||||||
|
for photo in photos:
|
||||||
|
photo.is_ref = is_ref
|
||||||
|
return photos
|
||||||
|
else:
|
||||||
|
return directories.Directories._get_files(self, from_path, j)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_subfolders(path):
|
||||||
|
if path == IPHOTO_PATH:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return directories.Directories.get_subfolders(path)
|
||||||
|
|
||||||
|
def add_path(self, path):
|
||||||
|
if path == IPHOTO_PATH:
|
||||||
|
if path not in self:
|
||||||
|
self._dirs.append(path)
|
||||||
|
else:
|
||||||
|
directories.Directories.add_path(self, path)
|
||||||
|
|
||||||
|
def has_iphoto_path(self):
|
||||||
|
return any(path == IPHOTO_PATH for path in self._dirs)
|
||||||
|
|
||||||
|
def has_any_file(self):
|
||||||
|
# If we don't do that, it causes a hangup in the GUI when we click Start Scanning because
|
||||||
|
# checking if there's any file to scan involves reading the whole library. If we have the
|
||||||
|
# iPhoto library, we assume we have at least one file.
|
||||||
|
if self.has_iphoto_path():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return directories.Directories.has_any_file(self)
|
||||||
|
|
||||||
|
|
||||||
|
class DupeGuruPE(DupeGuruBase):
|
||||||
|
def __init__(self, view, appdata):
|
||||||
|
appdata = op.join(appdata, 'dupeGuru Picture Edition')
|
||||||
|
DupeGuruBase.__init__(self, view, appdata)
|
||||||
|
self.directories = Directories()
|
||||||
|
|
||||||
|
def _do_delete(self, j, replace_with_hardlinks):
|
||||||
|
def op(dupe):
|
||||||
|
j.add_progress()
|
||||||
|
return self._do_delete_dupe(dupe, replace_with_hardlinks)
|
||||||
|
|
||||||
|
marked = [dupe for dupe in self.results.dupes if self.results.is_marked(dupe)]
|
||||||
|
j.start_job(self.results.mark_count, tr("Sending dupes to the Trash"))
|
||||||
|
if any(isinstance(dupe, IPhoto) for dupe in marked):
|
||||||
|
j.add_progress(0, desc=tr("Talking to iPhoto. Don't touch it!"))
|
||||||
|
try:
|
||||||
|
a = app('iPhoto')
|
||||||
|
a.activate(timeout=0)
|
||||||
|
a.select(a.photo_library_album(timeout=0), timeout=0)
|
||||||
|
except (CommandError, RuntimeError, ApplicationNotFoundError):
|
||||||
|
pass
|
||||||
|
self.results.perform_on_marked(op, True)
|
||||||
|
|
||||||
|
def _do_delete_dupe(self, dupe, replace_with_hardlinks):
|
||||||
|
if isinstance(dupe, IPhoto):
|
||||||
|
try:
|
||||||
|
a = app('iPhoto')
|
||||||
|
album = a.photo_library_album()
|
||||||
|
if album is None:
|
||||||
|
msg = "There are communication problems with iPhoto. Try opening iPhoto first, it might solve it."
|
||||||
|
raise EnvironmentError(msg)
|
||||||
|
[photo] = album.photos[its.image_path == str(dupe.path)]()
|
||||||
|
a.remove(photo, timeout=0)
|
||||||
|
except ValueError:
|
||||||
|
msg = "Could not find photo '{}' in iPhoto Library".format(str(dupe.path))
|
||||||
|
raise EnvironmentError(msg)
|
||||||
|
except (CommandError, RuntimeError) as e:
|
||||||
|
raise EnvironmentError(str(e))
|
||||||
|
else:
|
||||||
|
DupeGuruBase._do_delete_dupe(self, dupe, replace_with_hardlinks)
|
||||||
|
|
||||||
|
def _create_file(self, path):
|
||||||
|
if (self.directories.iphoto_libpath is not None) and (path in self.directories.iphoto_libpath[:-1]):
|
||||||
|
return IPhoto(path)
|
||||||
|
return DupeGuruBase._create_file(self, path)
|
||||||
|
|
||||||
|
def copy_or_move(self, dupe, copy, destination, dest_type):
|
||||||
|
if isinstance(dupe, IPhoto):
|
||||||
|
copy = True
|
||||||
|
return DupeGuruBase.copy_or_move(self, dupe, copy, destination, dest_type)
|
||||||
|
|
||||||
|
def selected_dupe_path(self):
|
||||||
|
if not self.selected_dupes:
|
||||||
|
return None
|
||||||
|
return self.selected_dupes[0].path
|
||||||
|
|
||||||
|
def selected_dupe_ref_path(self):
|
||||||
|
if not self.selected_dupes:
|
||||||
|
return None
|
||||||
|
ref = self.results.get_group_of_duplicate(self.selected_dupes[0]).ref
|
||||||
|
if ref is self.selected_dupes[0]: # we don't want the same pic to be displayed on both sides
|
||||||
|
return None
|
||||||
|
return ref.path
|
||||||
|
|
||||||
|
def start_scanning(self):
|
||||||
|
if self.directories.has_iphoto_path():
|
||||||
|
try:
|
||||||
|
app('iPhoto')
|
||||||
|
except ApplicationNotFoundError:
|
||||||
|
self.view.show_message(tr("The iPhoto application couldn't be found."))
|
||||||
|
return
|
||||||
|
DupeGuruBase.start_scanning(self)
|
||||||
|
|
||||||
|
class PyDupeGuru(PyDupeGuruBase):
|
||||||
|
def __init__(self):
|
||||||
|
self._init(DupeGuruPE)
|
||||||
|
|
||||||
|
def clearPictureCache(self):
|
||||||
|
self.model.scanner.clear_picture_cache()
|
||||||
|
|
||||||
|
#---Information
|
||||||
|
def getSelectedDupePath(self) -> str:
|
||||||
|
return str(self.model.selected_dupe_path())
|
||||||
|
|
||||||
|
def getSelectedDupeRefPath(self) -> str:
|
||||||
|
return str(self.model.selected_dupe_ref_path())
|
||||||
|
|
||||||
|
#---Properties
|
||||||
|
def setScanType_(self, scan_type: int):
|
||||||
|
try:
|
||||||
|
self.model.scanner.scan_type = [
|
||||||
|
ScanType.FuzzyBlock,
|
||||||
|
ScanType.ExifTimestamp,
|
||||||
|
][scan_type]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setMatchScaled_(self, match_scaled: bool):
|
||||||
|
self.model.scanner.match_scaled = match_scaled
|
||||||
|
|
||||||
|
def setMinMatchPercentage_(self, percentage: int):
|
||||||
|
self.model.scanner.threshold = percentage
|
||||||
102
cocoa/inter/app_se.py
Normal file
102
cocoa/inter/app_se.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2009-05-24
|
||||||
|
# Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
#
|
||||||
|
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
# which should be included with this package. The terms are also available at
|
||||||
|
# http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os.path as op
|
||||||
|
|
||||||
|
from hscommon import io
|
||||||
|
from hscommon.path import Path
|
||||||
|
from cocoa import proxy
|
||||||
|
|
||||||
|
from core.scanner import ScanType
|
||||||
|
from core import fs
|
||||||
|
from core.directories import Directories as DirectoriesBase, DirectoryState
|
||||||
|
from core_se.app import DupeGuru as DupeGuruBase
|
||||||
|
from .app import PyDupeGuruBase
|
||||||
|
|
||||||
|
def is_bundle(str_path):
|
||||||
|
uti = proxy.getUTI_(str_path)
|
||||||
|
if uti is None:
|
||||||
|
logging.warning('There was an error trying to detect the UTI of %s', str_path)
|
||||||
|
return proxy.type_conformsToType_(uti, 'com.apple.bundle') or proxy.type_conformsToType_(uti, 'com.apple.package')
|
||||||
|
|
||||||
|
class Bundle(fs.Folder):
|
||||||
|
@classmethod
|
||||||
|
def can_handle(cls, path):
|
||||||
|
return not io.islink(path) and io.isdir(path) and is_bundle(str(path))
|
||||||
|
|
||||||
|
|
||||||
|
class Directories(DirectoriesBase):
|
||||||
|
ROOT_PATH_TO_EXCLUDE = list(map(Path, ['/Library', '/Volumes', '/System', '/bin', '/sbin', '/opt', '/private', '/dev']))
|
||||||
|
HOME_PATH_TO_EXCLUDE = [Path('Library')]
|
||||||
|
def __init__(self):
|
||||||
|
DirectoriesBase.__init__(self, fileclasses=[Bundle, fs.File])
|
||||||
|
|
||||||
|
def _default_state_for_path(self, path):
|
||||||
|
result = DirectoriesBase._default_state_for_path(self, path)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
if path in self.ROOT_PATH_TO_EXCLUDE:
|
||||||
|
return DirectoryState.Excluded
|
||||||
|
if path[:2] == Path('/Users') and path[3:] in self.HOME_PATH_TO_EXCLUDE:
|
||||||
|
return DirectoryState.Excluded
|
||||||
|
|
||||||
|
def _get_folders(self, from_folder, j):
|
||||||
|
# We don't want to scan bundle's subfolder even in Folders mode. Bundle's integrity has to
|
||||||
|
# stay intact.
|
||||||
|
if is_bundle(str(from_folder.path)):
|
||||||
|
# just yield the current folder and bail
|
||||||
|
state = self.get_state(from_folder.path)
|
||||||
|
if state != DirectoryState.Excluded:
|
||||||
|
from_folder.is_ref = state == DirectoryState.Reference
|
||||||
|
yield from_folder
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
for folder in DirectoriesBase._get_folders(self, from_folder, j):
|
||||||
|
yield folder
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_subfolders(path):
|
||||||
|
result = DirectoriesBase.get_subfolders(path)
|
||||||
|
return [p for p in result if not is_bundle(str(p))]
|
||||||
|
|
||||||
|
|
||||||
|
class DupeGuru(DupeGuruBase):
|
||||||
|
def __init__(self, view, appdata):
|
||||||
|
appdata = op.join(appdata, 'dupeGuru')
|
||||||
|
DupeGuruBase.__init__(self, view, appdata)
|
||||||
|
self.directories = Directories()
|
||||||
|
|
||||||
|
|
||||||
|
class PyDupeGuru(PyDupeGuruBase):
|
||||||
|
def __init__(self):
|
||||||
|
self._init(DupeGuru)
|
||||||
|
|
||||||
|
#---Properties
|
||||||
|
def setMinMatchPercentage_(self, percentage: int):
|
||||||
|
self.model.scanner.min_match_percentage = int(percentage)
|
||||||
|
|
||||||
|
def setScanType_(self, scan_type: int):
|
||||||
|
try:
|
||||||
|
self.model.scanner.scan_type = [
|
||||||
|
ScanType.Filename,
|
||||||
|
ScanType.Contents,
|
||||||
|
ScanType.Folders,
|
||||||
|
][scan_type]
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def setWordWeighting_(self, words_are_weighted: bool):
|
||||||
|
self.model.scanner.word_weighting = words_are_weighted
|
||||||
|
|
||||||
|
def setMatchSimilarWords_(self, match_similar_words: bool):
|
||||||
|
self.model.scanner.match_similar_words = match_similar_words
|
||||||
|
|
||||||
|
def setSizeThreshold_(self, size_threshold: int):
|
||||||
|
self.model.scanner.size_threshold = size_threshold
|
||||||
|
|
||||||
11
cocoa/inter/details_panel.py
Normal file
11
cocoa/inter/details_panel.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from cocoa.inter import PyGUIObject, GUIObjectView
|
||||||
|
|
||||||
|
class DetailsPanelView(GUIObjectView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PyDetailsPanel(PyGUIObject):
|
||||||
|
def numberOfRows(self) -> int:
|
||||||
|
return self.model.row_count()
|
||||||
|
|
||||||
|
def valueForColumn_row_(self, column: str, row: int) -> object:
|
||||||
|
return self.model.row(row)[int(column)]
|
||||||
18
cocoa/inter/directory_outline.py
Normal file
18
cocoa/inter/directory_outline.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from objp.util import dontwrap
|
||||||
|
from cocoa.inter import PyOutline, GUIObjectView
|
||||||
|
|
||||||
|
class DirectoryOutlineView(GUIObjectView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PyDirectoryOutline(PyOutline):
|
||||||
|
def addDirectory_(self, path: str):
|
||||||
|
self.model.add_directory(path)
|
||||||
|
|
||||||
|
def removeSelectedDirectory(self):
|
||||||
|
self.model.remove_selected()
|
||||||
|
|
||||||
|
# python --> cocoa
|
||||||
|
@dontwrap
|
||||||
|
def refresh_states(self):
|
||||||
|
# Under cocoa, both refresh() and refresh_states() do the same thing.
|
||||||
|
self.callback.refresh()
|
||||||
20
cocoa/inter/ignore_list_dialog.py
Normal file
20
cocoa/inter/ignore_list_dialog.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
from objp.util import pyref
|
||||||
|
from cocoa.inter import PyGUIObject, GUIObjectView
|
||||||
|
|
||||||
|
class IgnoreListDialogView(GUIObjectView):
|
||||||
|
def show(self): pass
|
||||||
|
|
||||||
|
class PyIgnoreListDialog(PyGUIObject):
|
||||||
|
def ignoreListTable(self) -> pyref:
|
||||||
|
return self.model.ignore_list_table
|
||||||
|
|
||||||
|
def removeSelected(self):
|
||||||
|
self.model.remove_selected()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.model.clear()
|
||||||
|
|
||||||
|
#--- model --> view
|
||||||
|
def show(self):
|
||||||
|
self.callback.show()
|
||||||
|
|
||||||
29
cocoa/inter/prioritize_dialog.py
Normal file
29
cocoa/inter/prioritize_dialog.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from objp.util import pyref
|
||||||
|
from cocoa.inter import PyGUIObject, GUIObjectView
|
||||||
|
from core.gui.prioritize_dialog import PrioritizeDialog
|
||||||
|
|
||||||
|
class PrioritizeDialogView(GUIObjectView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PyPrioritizeDialog(PyGUIObject):
|
||||||
|
def __init__(self, app: pyref):
|
||||||
|
model = PrioritizeDialog(app.model)
|
||||||
|
PyGUIObject.__init__(self, model)
|
||||||
|
|
||||||
|
def categoryList(self) -> pyref:
|
||||||
|
return self.model.category_list
|
||||||
|
|
||||||
|
def criteriaList(self) -> pyref:
|
||||||
|
return self.model.criteria_list
|
||||||
|
|
||||||
|
def prioritizationList(self) -> pyref:
|
||||||
|
return self.model.prioritization_list
|
||||||
|
|
||||||
|
def addSelected(self):
|
||||||
|
self.model.add_selected()
|
||||||
|
|
||||||
|
def removeSelected(self):
|
||||||
|
self.model.remove_selected()
|
||||||
|
|
||||||
|
def performReprioritization(self):
|
||||||
|
self.model.perform_reprioritization()
|
||||||
8
cocoa/inter/prioritize_list.py
Normal file
8
cocoa/inter/prioritize_list.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from cocoa.inter import PySelectableList, SelectableListView
|
||||||
|
|
||||||
|
class PrioritizeListView(SelectableListView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PyPrioritizeList(PySelectableList):
|
||||||
|
def moveIndexes_toIndex_(self, indexes: list, dest_index: int):
|
||||||
|
self.model.move_indexes(indexes, dest_index)
|
||||||
9
cocoa/inter/problem_dialog.py
Normal file
9
cocoa/inter/problem_dialog.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from objp.util import pyref
|
||||||
|
from cocoa.inter import PyGUIObject
|
||||||
|
|
||||||
|
class PyProblemDialog(PyGUIObject):
|
||||||
|
def problemTable(self) -> pyref:
|
||||||
|
return self.model.problem_table
|
||||||
|
|
||||||
|
def revealSelected(self):
|
||||||
|
self.model.reveal_selected_dupe()
|
||||||
49
cocoa/inter/result_table.py
Normal file
49
cocoa/inter/result_table.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from objp.util import dontwrap
|
||||||
|
from cocoa.inter import PyTable, TableView
|
||||||
|
|
||||||
|
class ResultTableView(TableView):
|
||||||
|
def invalidateMarkings(self): pass
|
||||||
|
|
||||||
|
class PyResultTable(PyTable):
|
||||||
|
def powerMarkerMode(self) -> bool:
|
||||||
|
return self.model.power_marker
|
||||||
|
|
||||||
|
def setPowerMarkerMode_(self, value: bool):
|
||||||
|
self.model.power_marker = value
|
||||||
|
|
||||||
|
def deltaValuesMode(self) -> bool:
|
||||||
|
return self.model.delta_values
|
||||||
|
|
||||||
|
def setDeltaValuesMode_(self, value: bool):
|
||||||
|
self.model.delta_values = value
|
||||||
|
|
||||||
|
def deltaColumns(self) -> list:
|
||||||
|
return list(self.model.DELTA_COLUMNS)
|
||||||
|
|
||||||
|
def valueForRow_column_(self, row_index: int, column: str) -> object:
|
||||||
|
return self.model.get_row_value(row_index, column)
|
||||||
|
|
||||||
|
def renameSelected_(self, newname: str) -> bool:
|
||||||
|
return self.model.rename_selected(newname)
|
||||||
|
|
||||||
|
def sortBy_ascending_(self, key: str, asc: bool):
|
||||||
|
self.model.sort(key, asc)
|
||||||
|
|
||||||
|
def markSelected(self):
|
||||||
|
self.model.app.toggle_selected_mark_state()
|
||||||
|
|
||||||
|
def removeSelected(self):
|
||||||
|
self.model.app.remove_selected()
|
||||||
|
|
||||||
|
def selectedDupeCount(self) -> int:
|
||||||
|
return self.model.selected_dupe_count
|
||||||
|
|
||||||
|
def pathAtIndex_(self, index: int) -> str:
|
||||||
|
row = self.model[index]
|
||||||
|
return str(row._dupe.path)
|
||||||
|
|
||||||
|
# python --> cocoa
|
||||||
|
@dontwrap
|
||||||
|
def invalidate_markings(self):
|
||||||
|
self.callback.invalidateMarkings()
|
||||||
|
|
||||||
9
cocoa/inter/stats_label.py
Normal file
9
cocoa/inter/stats_label.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from cocoa.inter import PyGUIObject, GUIObjectView
|
||||||
|
|
||||||
|
class StatsLabelView(GUIObjectView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PyStatsLabel(PyGUIObject):
|
||||||
|
|
||||||
|
def display(self) -> str:
|
||||||
|
return self.model.display
|
||||||
15
cocoa/me/AppDelegate.h
Normal file
15
cocoa/me/AppDelegate.h
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "../base/AppDelegate.h"
|
||||||
|
#import "ResultWindow.h"
|
||||||
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
|
@interface AppDelegate : AppDelegateBase {}
|
||||||
|
@end
|
||||||
86
cocoa/me/AppDelegate.m
Normal file
86
cocoa/me/AppDelegate.m
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "ProgressController.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "ValueTransformers.h"
|
||||||
|
#import "Dialogs.h"
|
||||||
|
#import "DetailsPanel.h"
|
||||||
|
#import "DirectoryPanel.h"
|
||||||
|
#import "ResultWindow.h"
|
||||||
|
#import "Consts.h"
|
||||||
|
|
||||||
|
@implementation AppDelegate
|
||||||
|
+ (void)initialize
|
||||||
|
{
|
||||||
|
[super initialize];
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:10];
|
||||||
|
[d setObject:i2n(3) forKey:@"scanType"];
|
||||||
|
[d setObject:i2n(80) forKey:@"minMatchPercentage"];
|
||||||
|
[d setObject:i2n(1) forKey:@"recreatePathType"];
|
||||||
|
[d setObject:i2n(11) forKey:TableFontSize];
|
||||||
|
[d setObject:b2n(NO) forKey:@"wordWeighting"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
|
||||||
|
[d setObject:b2n(YES) forKey:@"mixFileKind"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"useRegexpFilter"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"ignoreHardlinkMatches"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"removeEmptyFolders"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"debug"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"scanTagTrack"];
|
||||||
|
[d setObject:b2n(YES) forKey:@"scanTagArtist"];
|
||||||
|
[d setObject:b2n(YES) forKey:@"scanTagAlbum"];
|
||||||
|
[d setObject:b2n(YES) forKey:@"scanTagTitle"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"scanTagGenre"];
|
||||||
|
[d setObject:b2n(NO) forKey:@"scanTagYear"];
|
||||||
|
[d setObject:[NSArray array] forKey:@"recentDirectories"];
|
||||||
|
[d setObject:[NSArray array] forKey:@"columnsOrder"];
|
||||||
|
[d setObject:[NSDictionary dictionary] forKey:@"columnsWidth"];
|
||||||
|
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:d];
|
||||||
|
[ud registerDefaults:d];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)init
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:4];
|
||||||
|
[i addIndex:5];
|
||||||
|
VTIsIntIn *vtScanTypeIsNotContent = [[[VTIsIntIn alloc] initWithValues:i reverse:YES] autorelease];
|
||||||
|
[NSValueTransformer setValueTransformer:vtScanTypeIsNotContent forName:@"vtScanTypeIsNotContent"];
|
||||||
|
VTIsIntIn *vtScanTypeIsTag = [[[VTIsIntIn alloc] initWithValues:[NSIndexSet indexSetWithIndex:3] reverse:NO] autorelease];
|
||||||
|
[NSValueTransformer setValueTransformer:vtScanTypeIsTag forName:@"vtScanTypeIsTag"];
|
||||||
|
_directoryPanel = nil;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)homepageURL
|
||||||
|
{
|
||||||
|
return @"http://www.hardcoded.net/dupeguru_me/";
|
||||||
|
}
|
||||||
|
|
||||||
|
- (ResultWindowBase *)createResultWindow
|
||||||
|
{
|
||||||
|
return [[ResultWindow alloc] initWithParentApp:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (DirectoryPanel *)createDirectoryPanel
|
||||||
|
{
|
||||||
|
return [[DirectoryPanelME alloc] initWithParentApp:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
//Delegate
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
// index 3 is just after "Export Results to XHTML"
|
||||||
|
NSMenuItem *mi = [actionsMenu insertItemWithTitle:TR(@"Remove Dead Tracks in iTunes")
|
||||||
|
action:@selector(removeDeadTracks:) keyEquivalent:@"" atIndex:3];
|
||||||
|
[mi setTarget:[self resultWindow]];
|
||||||
|
[super applicationDidFinishLaunching:aNotification];
|
||||||
|
}
|
||||||
|
@end
|
||||||
11
cocoa/me/Consts.h
Normal file
11
cocoa/me/Consts.h
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2012 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "../base/Consts.h"
|
||||||
|
|
||||||
|
#define jobScanDeadTracks @"jobScanDeadTracks"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user