mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-25 16:11:39 +00:00
Compare commits
711 Commits
before-plu
...
se3.9.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ce0f66601 | ||
|
|
ac3a9e3ba8 | ||
|
|
903d2f9183 | ||
|
|
ca709a60cf | ||
|
|
a9b4ce5529 | ||
|
|
9b82ceca67 | ||
|
|
4c7c279dd2 | ||
|
|
79db31685e | ||
|
|
ba13b700b0 | ||
|
|
640561a534 | ||
|
|
e4f81cbf04 | ||
|
|
4be4825112 | ||
|
|
7d107d8efa | ||
|
|
10d1363334 | ||
|
|
b76820ebde | ||
|
|
72b3cfb364 | ||
|
|
8b83ed0e5c | ||
|
|
781f13ae1a | ||
|
|
8193bbae6e | ||
|
|
4cafeaff91 | ||
|
|
95c6a7d41f | ||
|
|
a29e007475 | ||
|
|
d924d7797a | ||
|
|
33c217ecc8 | ||
|
|
c9035046ae | ||
|
|
ad31016825 | ||
|
|
c809066a93 | ||
|
|
60ca27b5e1 | ||
|
|
1104e24408 | ||
|
|
f66db94ffd | ||
|
|
d98b5b22da | ||
|
|
937748e838 | ||
|
|
37ebf36cee | ||
|
|
1c84bdd198 | ||
|
|
4a2fa7cd2c | ||
|
|
7d4110f6d3 | ||
|
|
8497343d7f | ||
|
|
235a2c2904 | ||
|
|
25169cfc20 | ||
|
|
152f5f37ce | ||
|
|
3e42ad8469 | ||
|
|
7ba2e38cd6 | ||
|
|
c7c7a73384 | ||
|
|
46f8984bdc | ||
|
|
c7d306b7d5 | ||
|
|
47e636e949 | ||
|
|
0562729d8b | ||
|
|
4a36227a18 | ||
|
|
28b8b2e415 | ||
|
|
fd82464564 | ||
|
|
418acf6e5e | ||
|
|
d14d076989 | ||
|
|
cb8bb5a70e | ||
|
|
563c9aeff3 | ||
|
|
a0cc1f2e03 | ||
|
|
01403a3f92 | ||
|
|
7116674663 | ||
|
|
b6bc5de79c | ||
|
|
5a275db67d | ||
|
|
31395d8794 | ||
|
|
3734bd6f6c | ||
|
|
da06ef8cad | ||
|
|
0b00171655 | ||
|
|
c1cfa86ad1 | ||
|
|
c34c9562d3 | ||
|
|
0e542577b0 | ||
|
|
42be49da83 | ||
|
|
398ac9b7c6 | ||
|
|
508e9a5d94 | ||
|
|
cc5ea1dbc1 | ||
|
|
3b8d355b9e | ||
|
|
10dbfa9b38 | ||
|
|
e8c42740cf | ||
|
|
4b6c4f048d | ||
|
|
7594cccf8c | ||
|
|
1d9573cf6f | ||
|
|
76f45fb5a6 | ||
|
|
12cf9b800b | ||
|
|
ba7e6494c6 | ||
|
|
72d8160b28 | ||
|
|
6d53511cee | ||
|
|
64d3c211e6 | ||
|
|
fad112f554 | ||
|
|
a563327723 | ||
|
|
096e2bb78a | ||
|
|
5a8cb6f5e3 | ||
|
|
664d630b96 | ||
|
|
a4256d3d2b | ||
|
|
8e65f15e1a | ||
|
|
9ea9f60e92 | ||
|
|
8efefaf0bf | ||
|
|
33d9569427 | ||
|
|
2fdfacb34e | ||
|
|
97fcf1ffa8 | ||
|
|
350b2c64e0 | ||
|
|
dcc57a7afb | ||
|
|
8b510994ad | ||
|
|
4a4d1bbfcd | ||
|
|
78c3c8ec2d | ||
|
|
e99e2b18e0 | ||
|
|
ae1283f2e1 | ||
|
|
cc76f3ca87 | ||
|
|
be8efea081 | ||
|
|
7e8f9036d8 | ||
|
|
8a8ac027f5 | ||
|
|
1d9d09fdf7 | ||
|
|
5dc956870d | ||
|
|
d8f48cbd42 | ||
|
|
39d24817f6 | ||
|
|
2364e44707 | ||
|
|
3e2249bf89 | ||
|
|
38acb6f91c | ||
|
|
9bcb28d5e2 | ||
|
|
d0a3f081da | ||
|
|
d11ec557e7 | ||
|
|
b9124a497c | ||
|
|
502715cfd6 | ||
|
|
e1f532e2fd | ||
|
|
a71033d9d6 | ||
|
|
a15a62f55c | ||
|
|
2fe5cdcf02 | ||
|
|
21c64545e5 | ||
|
|
c93a88f8b0 | ||
|
|
86a81eab4e | ||
|
|
1c779cb3ec | ||
|
|
04949c853d | ||
|
|
ff782a09f5 | ||
|
|
e5ce6680ca | ||
|
|
8e15d89a2e | ||
|
|
d874f26f06 | ||
|
|
80a99ff29e | ||
|
|
b11b97dd7c | ||
|
|
386a5f2c64 | ||
|
|
c13a2f207c | ||
|
|
d36710ef38 | ||
|
|
bbc9b003c6 | ||
|
|
3edba28f0b | ||
|
|
9304f42f69 | ||
|
|
375963ebfd | ||
|
|
7891fb5396 | ||
|
|
bdd5f0a515 | ||
|
|
db0901b1de | ||
|
|
9225697053 | ||
|
|
097b949763 | ||
|
|
60701c2a5c | ||
|
|
3ef1281450 | ||
|
|
af4e74a130 | ||
|
|
422fb2670d | ||
|
|
94a469205a | ||
|
|
95623f9b47 | ||
|
|
a65c246a2e | ||
|
|
045d496a98 | ||
|
|
5ed98b3d92 | ||
|
|
5799e3548b | ||
|
|
b01ed1e9f3 | ||
|
|
49839b8b8e | ||
|
|
500468ed1c | ||
|
|
de6f50ab12 | ||
|
|
2c6339f7a2 | ||
|
|
96c3d63557 | ||
|
|
e86b23259c | ||
|
|
89955aa96a | ||
|
|
39a601e393 | ||
|
|
3b9e11d14f | ||
|
|
81a7c4d6a6 | ||
|
|
35a162faf4 | ||
|
|
70e505ad92 | ||
|
|
aa3cf9700d | ||
|
|
e4b949abf6 | ||
|
|
2e23303d7e | ||
|
|
2fcaacd285 | ||
|
|
6627f0dbea | ||
|
|
a64b42de65 | ||
|
|
6dddcb1a47 | ||
|
|
4a8ce9b6c4 | ||
|
|
d4e6632e7e | ||
|
|
0ced3e39c8 | ||
|
|
26d923e175 | ||
|
|
592eba9eaa | ||
|
|
11450ae56a | ||
|
|
008bd1414e | ||
|
|
62f6cc3705 | ||
|
|
e16041c703 | ||
|
|
1103b58ec5 | ||
|
|
dc8b1b3b02 | ||
|
|
36b214443a | ||
|
|
d11e20f6ba | ||
|
|
477f73ffa4 | ||
|
|
0b5cd61540 | ||
|
|
17b5703885 | ||
|
|
7cac0b5d6e | ||
|
|
a4003b6072 | ||
|
|
fb26d7d077 | ||
|
|
f2cbb513d3 | ||
|
|
8c36218150 | ||
|
|
7637e493a6 | ||
|
|
62be8da6f9 | ||
|
|
b1c2941616 | ||
|
|
8efd3033a3 | ||
|
|
d417dbd2e3 | ||
|
|
3a717a86d8 | ||
|
|
e1f7260774 | ||
|
|
bfc1ee90ec | ||
|
|
93a5fd01b5 | ||
|
|
b028670250 | ||
|
|
dff141e800 | ||
|
|
f49c7dee96 | ||
|
|
5da793b029 | ||
|
|
a56258f8b3 | ||
|
|
ab6e0945a7 | ||
|
|
8ac035c8a9 | ||
|
|
1c15b0114b | ||
|
|
e78b14f9a2 | ||
|
|
573d088088 | ||
|
|
75b08125c0 | ||
|
|
20320f539f | ||
|
|
24771af955 | ||
|
|
2bfe9960f1 | ||
|
|
215bcb0d76 | ||
|
|
2dbf8b80ae | ||
|
|
470cd92030 | ||
|
|
111edc3ce5 | ||
|
|
df30a31782 | ||
|
|
91f3a59523 | ||
|
|
3441e51c0e | ||
|
|
a99c40b5d8 | ||
|
|
5b4de58c38 | ||
|
|
cd83b16dbd | ||
|
|
b67db988ab | ||
|
|
7ebea44cb0 | ||
|
|
4de40af1b0 | ||
|
|
54988650d7 | ||
|
|
a47c208f45 | ||
|
|
c321427a8f | ||
|
|
26c77a18fd | ||
|
|
9f19451ac7 | ||
|
|
bcd9d7e7d0 | ||
|
|
b42b0be512 | ||
|
|
d90764a9ea | ||
|
|
c5c4e02bf4 | ||
|
|
fae3a6ac3a | ||
|
|
09b91aab66 | ||
|
|
b5a219cc00 | ||
|
|
9cb62e0544 | ||
|
|
653668dd96 | ||
|
|
5a5a74d0e1 | ||
|
|
5247ac8abd | ||
|
|
f992599beb | ||
|
|
51f8c51ef3 | ||
|
|
fcdc692b61 | ||
|
|
deb5260c6a | ||
|
|
81df280ea6 | ||
|
|
4f097a3a89 | ||
|
|
8cd1e13814 | ||
|
|
7e81e6c93f | ||
|
|
b19d6c9a27 | ||
|
|
977fb606eb | ||
|
|
9e7d27dcda | ||
|
|
caf04f0d3f | ||
|
|
a2553da578 | ||
|
|
3cd44705f8 | ||
|
|
0cf6987083 | ||
|
|
a67f7e2c9e | ||
|
|
5a3b6883fa | ||
|
|
b0f9a94375 | ||
|
|
ad5c4a954c | ||
|
|
292d993dce | ||
|
|
1fe42f673f | ||
|
|
244af5b652 | ||
|
|
abe9041a67 | ||
|
|
a2d73b216c | ||
|
|
f08b593acb | ||
|
|
cb35dc7897 | ||
|
|
903ecd9eae | ||
|
|
79e9251511 | ||
|
|
81daef6145 | ||
|
|
6a7af81685 | ||
|
|
b3db7c6842 | ||
|
|
85e5b4cfa7 | ||
|
|
79e6020982 | ||
|
|
b7e7e67c99 | ||
|
|
1017e3c730 | ||
|
|
b74e33f4b0 | ||
|
|
2d0facdb14 | ||
|
|
c34004ed94 | ||
|
|
4db5fae38b | ||
|
|
5d5670d4be | ||
|
|
e21a7e18b4 | ||
|
|
a29ed235f6 | ||
|
|
fd706e752f | ||
|
|
729db49183 | ||
|
|
a68e4310ee | ||
|
|
500314859d | ||
|
|
b6d457f908 | ||
|
|
6850c7e2f8 | ||
|
|
9e5630fe99 | ||
|
|
f1a21e62cd | ||
|
|
7cc2defa35 | ||
|
|
24a11ee4bd | ||
|
|
e2b23ca961 | ||
|
|
8fabb14b8b | ||
|
|
07de7d6f0e | ||
|
|
e0b844f617 | ||
|
|
a7bc76bf7c | ||
|
|
89fb531f3d | ||
|
|
f22baa8d5a | ||
|
|
7c2e601a30 | ||
|
|
16e4a5fddd | ||
|
|
4200f2a090 | ||
|
|
45c8291645 | ||
|
|
89f8214bce | ||
|
|
16e1ee93d0 | ||
|
|
222ae73590 | ||
|
|
21c0292154 | ||
|
|
a7eb90894a | ||
|
|
64baf2a10c | ||
|
|
392a802ef1 | ||
|
|
8efeab7b40 | ||
|
|
8a86ecee38 | ||
|
|
3e79b57409 | ||
|
|
362e020585 | ||
|
|
df5c8ddf22 | ||
|
|
70cc48d51f | ||
|
|
dccffd9516 | ||
|
|
04056c1597 | ||
|
|
69b2e37368 | ||
|
|
05478591a4 | ||
|
|
ead3b1e651 | ||
|
|
27f4c290c4 | ||
|
|
4f248ee981 | ||
|
|
d7397c0125 | ||
|
|
e4430168f7 | ||
|
|
78c2ae150d | ||
|
|
806e3917e3 | ||
|
|
6c62c2d563 | ||
|
|
7a768bd0e6 | ||
|
|
a6c01f6868 | ||
|
|
721591d3e3 | ||
|
|
1171705921 | ||
|
|
65d2581f74 | ||
|
|
2128d1787a | ||
|
|
cbf2ab82ce | ||
|
|
adef5a9dfa | ||
|
|
0c9e5c90a3 | ||
|
|
787f546c17 | ||
|
|
2d4ecf1122 | ||
|
|
1836003506 | ||
|
|
44d34f56f7 | ||
|
|
24c3d7ed00 | ||
|
|
8f7657573d | ||
|
|
c03c7f2be1 | ||
|
|
91a157cb2b | ||
|
|
7ced1e7b9d | ||
|
|
f4b8efff50 | ||
|
|
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 |
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
.DS_Store
|
||||||
|
*.pyc
|
||||||
|
*.so
|
||||||
|
*.mo
|
||||||
|
*.pyd
|
||||||
|
*.waf*
|
||||||
|
.lock-waf*
|
||||||
|
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
install
|
||||||
|
installer_tmp-cache
|
||||||
|
env
|
||||||
|
/deps
|
||||||
|
cocoa/autogen
|
||||||
|
|
||||||
|
/run.py
|
||||||
|
/conf.json
|
||||||
|
/cocoa/*/Info.plist
|
||||||
|
/cocoa/*/build
|
||||||
|
/qt/base/*_rc.py
|
||||||
|
/help/*/conf.py
|
||||||
|
/help/*/changelog.rst
|
||||||
23
.hgignore
23
.hgignore
@@ -1,23 +0,0 @@
|
|||||||
syntax: glob
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
run.py
|
|
||||||
*.pyc
|
|
||||||
*.so
|
|
||||||
*.mode1v3
|
|
||||||
*.pbxuser
|
|
||||||
*.tm_build_errors
|
|
||||||
*.pyd
|
|
||||||
conf.json
|
|
||||||
build
|
|
||||||
dist
|
|
||||||
install
|
|
||||||
installer_tmp-cache
|
|
||||||
cocoa/*/Info.plist
|
|
||||||
cocoa/*/build
|
|
||||||
cocoa/*/dg_cocoa.plugin
|
|
||||||
qt/base/*_rc.py
|
|
||||||
qt/lang/fr.qm
|
|
||||||
qt/lang/en.qm
|
|
||||||
help/*/conf.py
|
|
||||||
help/*/changelog.rst
|
|
||||||
46
.hgtags
46
.hgtags
@@ -1,46 +0,0 @@
|
|||||||
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
|
|
||||||
21
.tx/config
Normal file
21
.tx/config
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
|
||||||
|
[dupeguru.core]
|
||||||
|
file_filter = locale/<lang>/LC_MESSAGES/core.po
|
||||||
|
source_file = locale/core.pot
|
||||||
|
source_lang = en
|
||||||
|
type = PO
|
||||||
|
|
||||||
|
[dupeguru.columns]
|
||||||
|
file_filter = locale/<lang>/LC_MESSAGES/columns.po
|
||||||
|
source_file = locale/columns.pot
|
||||||
|
source_lang = en
|
||||||
|
type = PO
|
||||||
|
|
||||||
|
[dupeguru.ui]
|
||||||
|
file_filter = locale/<lang>/LC_MESSAGES/ui.po
|
||||||
|
source_file = locale/ui.pot
|
||||||
|
source_lang = en
|
||||||
|
type = PO
|
||||||
|
|
||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2010 Hardcoded Software Inc. (http://www.hardcoded.net)
|
Copyright 2014 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:
|
||||||
|
|||||||
73
README
73
README
@@ -1,73 +0,0 @@
|
|||||||
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.1 (http://www.python.org)
|
|
||||||
- Send2Trash3k (http://hg.hardcoded.net/send2trash)
|
|
||||||
- hsaudiotag3k 1.1.0 (for ME) (http://hg.hardcoded.net/hsaudiotag)
|
|
||||||
- jobprogress (http://hg.hardcoded.net/jobprogress)
|
|
||||||
- Sphinx 1.0.6 (http://sphinx.pocoo.org/)
|
|
||||||
- pytest 2.0.0, to run unit tests. (http://pytest.org/)
|
|
||||||
|
|
||||||
Note: Sphinx doesn't officially support Python 3.x yet, but it doesn't matter because it is invoked
|
|
||||||
by the build system through command line, so you can build dupeGuru even if Sphinx is installed in
|
|
||||||
your Python 2.x install.
|
|
||||||
|
|
||||||
OS X prerequisites
|
|
||||||
-----
|
|
||||||
|
|
||||||
- XCode 3.1 (http://developer.apple.com/TOOLS/xcode/)
|
|
||||||
- Sparkle (http://sparkle.andymatuschak.org/)
|
|
||||||
- PyObjC 2.3 (http://pyobjc.sourceforge.net/)
|
|
||||||
- py2app 0.5.4 (http://bitbucket.org/ronaldoussoren/py2app)
|
|
||||||
- appscript 0.22.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/)
|
|
||||||
|
|
||||||
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
|
|
||||||
98
README.md
Normal file
98
README.md
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
# dupeGuru
|
||||||
|
|
||||||
|
[dupeGuru][dupeguru] is a cross-platform (Linux, OS X, Windows) GUI tool to find duplicate files in
|
||||||
|
a system. It's written mostly in Python 3 and has the peculiarity of using
|
||||||
|
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer
|
||||||
|
is written in Objective-C and uses Cocoa. On Linux and Windows, it's written in Python and uses Qt4.
|
||||||
|
|
||||||
|
dupeGuru comes in 3 editions (standard, music and picture) which are all buildable from this same
|
||||||
|
source tree. You choose the edition you want to build in a ``configure.py`` flag.
|
||||||
|
|
||||||
|
# Contents of this folder
|
||||||
|
|
||||||
|
This folder contains the source for dupeGuru. Its documentation is in ``help``, but is also
|
||||||
|
[available online][documentation] in its built form. Here's how this source tree is organised:
|
||||||
|
|
||||||
|
* core: Contains the core logic code for dupeGuru. It's Python code.
|
||||||
|
* 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.
|
||||||
|
* pkg: Skeleton files required to create different packages
|
||||||
|
* help: Help document, written for Sphinx.
|
||||||
|
* locale: .po files for localisation.
|
||||||
|
|
||||||
|
There are also other sub-folder that comes from external repositories and are part of this repo as
|
||||||
|
git subtrees:
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
# How to build dupeGuru from source
|
||||||
|
|
||||||
|
## The very, very, very easy way
|
||||||
|
|
||||||
|
If you're on Linux or Mac, there's a bootstrap script that will make building very, very easy. There
|
||||||
|
might be some things that you need to install manually on your system, but the bootstrap script will
|
||||||
|
tell you when what you need to install. You can run the bootstrap with:
|
||||||
|
|
||||||
|
./bootstrap.sh
|
||||||
|
|
||||||
|
and follow instructions from the script. You can then ignore the rest of the build documentation.
|
||||||
|
|
||||||
|
## Prerequisites installation
|
||||||
|
|
||||||
|
Prerequisites are installed through `pip`. However, some of them are not "pip installable" and have
|
||||||
|
to be installed manually.
|
||||||
|
|
||||||
|
* All systems: [Python 3.3+][python] and [setuptools][setuptools]
|
||||||
|
* Mac OS X: The last XCode to have the 10.6 SDK included.
|
||||||
|
* Windows: Visual Studio 2010, [PyQt 5.0+][pyqt], [cx_Freeze][cxfreeze] and
|
||||||
|
[Advanced Installer][advinst] (you only need the last two if you want to create an installer)
|
||||||
|
|
||||||
|
On Ubuntu (14.04+), the apt-get command to install all pre-requisites is:
|
||||||
|
|
||||||
|
$ apt-get install python3-dev python3-pyqt5 pyqt5-dev-tools
|
||||||
|
|
||||||
|
On Arch, it's:
|
||||||
|
|
||||||
|
$ pacman -S python-pyqt5
|
||||||
|
|
||||||
|
## Setting up the virtual environment
|
||||||
|
|
||||||
|
Use Python's built-in `pyvenv` to create a virtual environment in which we're going to install our.
|
||||||
|
Python-related dependencies. `pyvenv` is built-in Python but, unlike its `virtualenv` predecessor,
|
||||||
|
it doesn't install setuptools and pip, so it has to be installed manually:
|
||||||
|
|
||||||
|
$ pyvenv --system-site-packages env
|
||||||
|
$ source env/bin/activate
|
||||||
|
$ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | python
|
||||||
|
$ easy_install pip
|
||||||
|
|
||||||
|
Then, you can install pip requirements in your virtualenv:
|
||||||
|
|
||||||
|
$ pip install -r requirements-[osx|win].txt
|
||||||
|
|
||||||
|
([osx|win] depends, of course, on your platform. On other platforms, just use requirements.txt).
|
||||||
|
|
||||||
|
## Actual building and running
|
||||||
|
|
||||||
|
With your virtualenv activated, you can build and run dupeGuru with these commands:
|
||||||
|
|
||||||
|
$ python configure.py
|
||||||
|
$ python build.py
|
||||||
|
$ python run.py
|
||||||
|
|
||||||
|
You can also package dupeGuru into an installable package with:
|
||||||
|
|
||||||
|
$ python package.py
|
||||||
|
|
||||||
|
[dupeguru]: http://www.hardcoded.net/dupeguru/
|
||||||
|
[cross-toolkit]: http://www.hardcoded.net/articles/cross-toolkit-software
|
||||||
|
[documentation]: http://www.hardcoded.net/dupeguru/help/en/
|
||||||
|
[python]: http://www.python.org/
|
||||||
|
[setuptools]: https://pypi.python.org/pypi/setuptools
|
||||||
|
[pyqt]: http://www.riverbankcomputing.com
|
||||||
|
[cxfreeze]: http://cx-freeze.sourceforge.net/
|
||||||
|
[advinst]: http://www.advancedinstaller.com
|
||||||
41
bootstrap.sh
Executable file
41
bootstrap.sh
Executable file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
command -v python3 -m venv >/dev/null 2>&1 || { echo >&2 "Python 3.3 required. Install it and try again. Aborting"; exit 1; }
|
||||||
|
|
||||||
|
if [ -d "deps" ]; then
|
||||||
|
# We have a collection of dependencies in our source package. We might as well use it instead
|
||||||
|
# of downloading it from PyPI.
|
||||||
|
PIPARGS="--no-index --find-links=deps"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "env" ]; then
|
||||||
|
echo "No virtualenv. Creating one"
|
||||||
|
# We need a "system-site-packages" env to have PyQt, but we also need to ensure a local pip
|
||||||
|
# install. To achieve our latter goal, we start with a normal venv, which we later upgrade to
|
||||||
|
# a system-site-packages once pip is installed.
|
||||||
|
python3 -m venv env
|
||||||
|
source env/bin/activate
|
||||||
|
if python -m ensurepip; then
|
||||||
|
echo "We're under Python 3.4+, no need to try to install pip!"
|
||||||
|
else
|
||||||
|
python get-pip.py $PIPARGS --force-reinstall
|
||||||
|
fi
|
||||||
|
deactivate
|
||||||
|
if [ "$(uname)" != "Darwin" ]; then
|
||||||
|
# We only need system site packages for PyQt, so under OS X, we don't enable it
|
||||||
|
python3 -m venv env --upgrade --system-site-packages
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
source env/bin/activate
|
||||||
|
|
||||||
|
echo "Installing pip requirements"
|
||||||
|
if [ "$(uname)" == "Darwin" ]; then
|
||||||
|
pip install $PIPARGS -r requirements-osx.txt
|
||||||
|
else
|
||||||
|
python3 -c "import PyQt5" >/dev/null 2>&1 || { echo >&2 "PyQt 5.1+ required. Install it and try again. Aborting"; exit 1; }
|
||||||
|
pip install $PIPARGS -r requirements.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Bootstrapping complete! You can now configure, build and run dupeGuru with:"
|
||||||
|
echo ". env/bin/activate && python configure.py && python build.py && python run.py"
|
||||||
357
build.py
357
build.py
@@ -1,100 +1,159 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Created By: Virgil Dupras
|
# Created By: Virgil Dupras
|
||||||
# Created On: 2009-12-30
|
# Created On: 2009-12-30
|
||||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
# Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
#
|
#
|
||||||
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
# 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
|
# which should be included with this package. The terms are also available at
|
||||||
# http://www.hardcoded.net/licenses/bsd_license
|
# http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
|
||||||
|
import sys
|
||||||
import os
|
import os
|
||||||
import os.path as op
|
import os.path as op
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
import shutil
|
import shutil
|
||||||
import json
|
import json
|
||||||
|
import importlib
|
||||||
|
import compileall
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup, Extension
|
||||||
from distutils.extension import Extension
|
|
||||||
|
|
||||||
from hscommon import sphinxgen
|
from hscommon import sphinxgen
|
||||||
from hscommon.build import (add_to_pythonpath, print_and_do, copy_packages,
|
from hscommon.build import (add_to_pythonpath, print_and_do, copy_packages, filereplace,
|
||||||
filereplace, get_module_version, build_all_cocoa_locs, build_all_qt_locs)
|
get_module_version, move_all, copy_all, OSXAppStructure,
|
||||||
|
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
|
||||||
|
collect_stdlib_dependencies, copy)
|
||||||
|
from hscommon import loc
|
||||||
|
from hscommon.plat import ISOSX, ISLINUX
|
||||||
|
from hscommon.util import ensure_folder, delete_files_with_pattern
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
usage = "usage: %prog [options]"
|
usage = "usage: %prog [options]"
|
||||||
parser = OptionParser(usage=usage)
|
parser = OptionParser(usage=usage)
|
||||||
parser.add_option('--clean', action='store_true', dest='clean',
|
parser.add_option('--clean', action='store_true', dest='clean',
|
||||||
help="Clean build folder before building")
|
help="Clean build folder before building")
|
||||||
parser.add_option('--only-help', action='store_true', dest='only_help',
|
parser.add_option('--doc', action='store_true', dest='doc',
|
||||||
help="Build only help file")
|
help="Build only the help file")
|
||||||
|
parser.add_option('--loc', action='store_true', dest='loc',
|
||||||
|
help="Build only localization")
|
||||||
|
parser.add_option('--cocoa-ext', action='store_true', dest='cocoa_ext',
|
||||||
|
help="Build only Cocoa extensions")
|
||||||
|
parser.add_option('--cocoa-compile', action='store_true', dest='cocoa_compile',
|
||||||
|
help="Build only Cocoa executable")
|
||||||
|
parser.add_option('--xibless', action='store_true', dest='xibless',
|
||||||
|
help="Build only xibless UIs")
|
||||||
|
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.")
|
||||||
|
parser.add_option('--normpo', action='store_true', dest='normpo',
|
||||||
|
help="Normalize all PO files (do this before commit).")
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def build_cocoa(edition, dev):
|
def cocoa_compile_command(edition):
|
||||||
build_all_cocoa_locs('cocoalib')
|
return '{0} waf configure --edition {1} && {0} waf'.format(sys.executable, edition)
|
||||||
build_all_cocoa_locs(op.join('cocoa', 'base'))
|
|
||||||
build_all_cocoa_locs(op.join('cocoa', edition))
|
|
||||||
|
|
||||||
print("Building dg_cocoa.plugin")
|
def cocoa_app(edition):
|
||||||
if not dev:
|
app_path = {
|
||||||
|
'se': 'build/dupeGuru.app',
|
||||||
|
'me': 'build/dupeGuru ME.app',
|
||||||
|
'pe': 'build/dupeGuru PE.app',
|
||||||
|
}[edition]
|
||||||
|
return OSXAppStructure(app_path)
|
||||||
|
|
||||||
|
def build_xibless(edition, dest='cocoa/autogen'):
|
||||||
|
import xibless
|
||||||
|
ensure_folder(dest)
|
||||||
|
FNPAIRS = [
|
||||||
|
('ignore_list_dialog.py', 'IgnoreListDialog_UI'),
|
||||||
|
('deletion_options.py', 'DeletionOptions_UI'),
|
||||||
|
('problem_dialog.py', 'ProblemDialog_UI'),
|
||||||
|
('directory_panel.py', 'DirectoryPanel_UI'),
|
||||||
|
('prioritize_dialog.py', 'PrioritizeDialog_UI'),
|
||||||
|
('result_window.py', 'ResultWindow_UI'),
|
||||||
|
('main_menu.py', 'MainMenu_UI'),
|
||||||
|
('preferences_panel.py', 'PreferencesPanel_UI'),
|
||||||
|
]
|
||||||
|
for srcname, dstname in FNPAIRS:
|
||||||
|
xibless.generate(op.join('cocoa', 'base', 'ui', srcname), op.join(dest, dstname),
|
||||||
|
localizationTable='Localizable', args={'edition': edition})
|
||||||
|
if edition == 'pe':
|
||||||
|
xibless.generate('cocoa/pe/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'), localizationTable='Localizable')
|
||||||
|
else:
|
||||||
|
xibless.generate('cocoa/base/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'), localizationTable='Localizable')
|
||||||
|
|
||||||
|
def build_cocoa(edition, dev):
|
||||||
|
print("Creating OS X app structure")
|
||||||
|
ed = lambda s: s.format(edition)
|
||||||
|
app = cocoa_app(edition)
|
||||||
|
app_version = get_module_version(ed('core_{}'))
|
||||||
|
cocoa_project_path = ed('cocoa/{}')
|
||||||
|
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version)
|
||||||
|
app.create(op.join('build', 'Info.plist'))
|
||||||
|
print("Building localizations")
|
||||||
|
build_localizations('cocoa', edition)
|
||||||
|
print("Building xibless UIs")
|
||||||
|
build_cocoalib_xibless()
|
||||||
|
build_xibless(edition)
|
||||||
|
print("Building Python extensions")
|
||||||
|
build_cocoa_proxy_module()
|
||||||
|
build_cocoa_bridging_interfaces(edition)
|
||||||
|
print("Building the cocoa layer")
|
||||||
|
copy_embeddable_python_dylib('build')
|
||||||
|
pydep_folder = op.join(app.resources, 'py')
|
||||||
|
if not op.exists(pydep_folder):
|
||||||
|
os.mkdir(pydep_folder)
|
||||||
|
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
||||||
|
appscript_pkgs = ['appscript', 'aem', 'mactypes', 'osax']
|
||||||
specific_packages = {
|
specific_packages = {
|
||||||
'se': ['core_se'],
|
'se': ['core_se'],
|
||||||
'me': ['core_me'],
|
'me': ['core_me'] + appscript_pkgs + ['hsaudiotag'],
|
||||||
'pe': ['core_pe'],
|
'pe': ['core_pe'] + appscript_pkgs,
|
||||||
}[edition]
|
}[edition]
|
||||||
copy_packages(['core', 'hscommon'] + specific_packages, 'build')
|
tocopy = ['core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'jobprogress', 'objp',
|
||||||
cocoa_project_path = 'cocoa/{0}'.format(edition)
|
'send2trash'] + specific_packages
|
||||||
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
copy_packages(tocopy, pydep_folder, create_links=dev)
|
||||||
os.chdir('build')
|
sys.path.insert(0, 'build')
|
||||||
script_args = ['py2app', '-A'] if dev else ['py2app']
|
extra_deps = None
|
||||||
setup(
|
if edition == 'pe':
|
||||||
script_args = script_args,
|
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have
|
||||||
plugin = ['dg_cocoa.py'],
|
# to manually specify it.
|
||||||
setup_requires = ['py2app'],
|
extra_deps=['multiprocessing']
|
||||||
)
|
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder, extra_deps=extra_deps)
|
||||||
|
del sys.path[0]
|
||||||
|
# Views are not referenced by python code, so they're not found by the collector.
|
||||||
|
copy_all('build/inter/*.so', op.join(pydep_folder, 'inter'))
|
||||||
|
if not dev:
|
||||||
|
# Important: Don't ever run delete_files_with_pattern('*.py') on dev builds because you'll
|
||||||
|
# be deleting all py files in symlinked folders.
|
||||||
|
compileall.compile_dir(pydep_folder, force=True, legacy=True)
|
||||||
|
delete_files_with_pattern(pydep_folder, '*.py')
|
||||||
|
delete_files_with_pattern(pydep_folder, '__pycache__')
|
||||||
|
print("Compiling with WAF")
|
||||||
|
os.chdir('cocoa')
|
||||||
|
print_and_do(cocoa_compile_command(edition))
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
pluginpath = op.join(cocoa_project_path, 'dg_cocoa.plugin')
|
app.copy_executable('cocoa/build/dupeGuru')
|
||||||
if op.exists(pluginpath):
|
build_help(edition)
|
||||||
shutil.rmtree(pluginpath)
|
print("Copying resources and frameworks")
|
||||||
shutil.move('build/dist/dg_cocoa.plugin', pluginpath)
|
image_path = ed('cocoa/{}/dupeguru.icns')
|
||||||
if dev:
|
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
|
||||||
# In alias mode, the tweakings we do to the pythonpath aren't counted in. We have to
|
app.copy_resources(*resources, use_symlinks=dev)
|
||||||
# manually put a .pth in the plugin
|
app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework')
|
||||||
pthpath = op.join(pluginpath, 'Contents/Resources/dev.pth')
|
|
||||||
open(pthpath, 'w').write(op.abspath('.'))
|
|
||||||
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 = []
|
|
||||||
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")
|
print("Creating the run.py file")
|
||||||
subfolder = 'dev' if dev else 'release'
|
tmpl = open('cocoa/run_template.py', 'rt').read()
|
||||||
app_path = {
|
run_contents = tmpl.replace('{{app_path}}', app.dest)
|
||||||
'se': 'cocoa/se/build/{0}/dupeGuru.app',
|
|
||||||
'me': 'cocoa/me/build/{0}/dupeGuru\\ ME.app',
|
|
||||||
'pe': 'cocoa/pe/build/{0}/dupeGuru\\ PE.app',
|
|
||||||
}[edition].format(subfolder)
|
|
||||||
tmpl = open('run_template_cocoa.py', 'rt').read()
|
|
||||||
run_contents = tmpl.replace('{{app_path}}', app_path)
|
|
||||||
open('run.py', 'wt').write(run_contents)
|
open('run.py', 'wt').write(run_contents)
|
||||||
|
|
||||||
def build_qt(edition, dev):
|
def build_qt(edition, dev, conf):
|
||||||
print("Building .ts files")
|
print("Building localizations")
|
||||||
build_all_qt_locs(op.join('qt', 'lang'), extradirs=[op.join('qtlib', 'lang')])
|
build_localizations('qt', edition)
|
||||||
print("Building Qt stuff")
|
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_and_do("pyrcc5 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
|
||||||
|
fix_qt_resource_file(op.join('qt', 'base', 'dg_rc.py'))
|
||||||
|
build_help(edition)
|
||||||
print("Creating the run.py file")
|
print("Creating the run.py file")
|
||||||
tmpl = open('run_template_qt.py', 'rt').read()
|
filereplace(op.join('qt', 'run_template.py'), 'run.py', edition=edition)
|
||||||
run_contents = tmpl.replace('{{edition}}', edition)
|
|
||||||
open('run.py', 'wt').write(run_contents)
|
|
||||||
|
|
||||||
def build_help(edition):
|
def build_help(edition):
|
||||||
print("Generating Help")
|
print("Generating Help")
|
||||||
@@ -102,21 +161,128 @@ def build_help(edition):
|
|||||||
help_basepath = op.join(current_path, 'help', 'en')
|
help_basepath = op.join(current_path, 'help', 'en')
|
||||||
help_destpath = op.join(current_path, 'build', 'help'.format(edition))
|
help_destpath = op.join(current_path, 'build', 'help'.format(edition))
|
||||||
changelog_path = op.join(current_path, 'help', 'changelog_{}'.format(edition))
|
changelog_path = op.join(current_path, 'help', 'changelog_{}'.format(edition))
|
||||||
tixurl = "https://hardcoded.lighthouseapp.com/projects/31699-dupeguru/tickets/{0}"
|
tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
|
||||||
appname = {'se': 'dupeGuru', 'me': 'dupeGuru Music Edition', 'pe': 'dupeGuru Picture Edition'}[edition]
|
appname = {'se': 'dupeGuru', 'me': 'dupeGuru Music Edition', 'pe': 'dupeGuru Picture Edition'}[edition]
|
||||||
homepage = 'http://www.hardcoded.net/dupeguru{}/'.format('_' + edition if edition != 'se' else '')
|
homepage = 'http://www.hardcoded.net/dupeguru{}/'.format('_' + edition if edition != 'se' else '')
|
||||||
confrepl = {'edition': edition, 'appname': appname, 'homepage': homepage}
|
confrepl = {'edition': edition, 'appname': appname, 'homepage': homepage, 'language': 'en'}
|
||||||
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl)
|
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_qt_localizations():
|
||||||
|
loc.compile_all_po(op.join('qtlib', 'locale'))
|
||||||
|
loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale')
|
||||||
|
|
||||||
|
def build_localizations(ui, edition):
|
||||||
|
loc.compile_all_po('locale')
|
||||||
|
if ui == 'cocoa':
|
||||||
|
app = cocoa_app(edition)
|
||||||
|
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'))
|
||||||
|
locale_dest = op.join(app.resources, 'locale')
|
||||||
|
elif ui == 'qt':
|
||||||
|
build_qt_localizations()
|
||||||
|
locale_dest = op.join('build', 'locale')
|
||||||
|
if op.exists(locale_dest):
|
||||||
|
shutil.rmtree(locale_dest)
|
||||||
|
shutil.copytree('locale', locale_dest, ignore=shutil.ignore_patterns('*.po', '*.pot'))
|
||||||
|
if ui == 'qt' and not ISLINUX:
|
||||||
|
print("Copying qt_*.qm files into the 'locale' folder")
|
||||||
|
from PyQt5.QtCore import QLibraryInfo
|
||||||
|
trfolder = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
|
||||||
|
for lang in loc.get_langs('locale'):
|
||||||
|
qmname = 'qt_%s.qm' % lang
|
||||||
|
src = op.join(trfolder, qmname)
|
||||||
|
if op.exists(src):
|
||||||
|
copy(src, op.join('build', 'locale', qmname))
|
||||||
|
|
||||||
|
def build_updatepot():
|
||||||
|
if ISOSX:
|
||||||
|
print("Updating Cocoa strings file.")
|
||||||
|
# We need to have strings from *all* editions in here, so we'll call xibless for all editions
|
||||||
|
# in dummy subfolders.
|
||||||
|
build_cocoalib_xibless('cocoalib/autogen')
|
||||||
|
loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj')
|
||||||
|
for edition in ('se', 'me', 'pe'):
|
||||||
|
build_xibless(edition, op.join('cocoa', 'autogen', edition))
|
||||||
|
loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/base/en.lproj')
|
||||||
|
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")
|
||||||
|
# When we're not under OS X, we don't want to overwrite ui.pot because it contains Cocoa locs
|
||||||
|
# We want to merge the generated pot with the old pot in the most preserving way possible.
|
||||||
|
ui_packages = ['qt', op.join('cocoa', 'inter')]
|
||||||
|
loc.generate_pot(ui_packages, op.join('locale', 'ui.pot'), ['tr'], merge=(not ISOSX))
|
||||||
|
print("Building qtlib.pot")
|
||||||
|
loc.generate_pot(['qtlib'], op.join('qtlib', 'locale', 'qtlib.pot'), ['tr'])
|
||||||
|
if ISOSX:
|
||||||
|
print("Building cocoalib.pot")
|
||||||
|
cocoalib_pot = op.join('cocoalib', 'locale', 'cocoalib.pot')
|
||||||
|
os.remove(cocoalib_pot)
|
||||||
|
loc.strings2pot(op.join('cocoalib', 'en.lproj', 'cocoalib.strings'), cocoalib_pot)
|
||||||
|
print("Enhancing ui.pot with Cocoa's strings files")
|
||||||
|
loc.strings2pot(op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'),
|
||||||
|
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('qtlib', 'locale'))
|
||||||
|
loc.merge_pots_into_pos(op.join('cocoalib', 'locale'))
|
||||||
|
|
||||||
|
def build_normpo():
|
||||||
|
loc.normalize_all_pos('locale')
|
||||||
|
loc.normalize_all_pos(op.join('qtlib', 'locale'))
|
||||||
|
loc.normalize_all_pos(op.join('cocoalib', 'locale'))
|
||||||
|
|
||||||
|
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', 'cocoa/autogen/HSErrorReportWindow_UI.m'],
|
||||||
|
['AppKit', 'CoreServices'],
|
||||||
|
['cocoalib', 'cocoa/autogen'])
|
||||||
|
|
||||||
|
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, PyBaseApp,
|
||||||
|
PyTextField, ProgressWindowView, PyProgressWindow)
|
||||||
|
from inter.deletion_options import PyDeletionOptions, DeletionOptionsView
|
||||||
|
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, PyBaseApp,
|
||||||
|
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
|
||||||
|
PyIgnoreListDialog, PyDeletionOptions, PyResultTable, PyStatsLabel, PyDupeGuruBase,
|
||||||
|
PyTextField, PyProgressWindow, 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, DeletionOptionsView, ResultTableView, StatsLabelView,
|
||||||
|
ProgressWindowView, 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):
|
def build_pe_modules(ui):
|
||||||
def move(src, dst):
|
|
||||||
if not op.exists(src):
|
|
||||||
return
|
|
||||||
if op.exists(dst):
|
|
||||||
os.remove(dst)
|
|
||||||
print('Moving %s --> %s' % (src, dst))
|
|
||||||
os.rename(src, dst)
|
|
||||||
|
|
||||||
print("Building PE Modules")
|
print("Building PE Modules")
|
||||||
exts = [
|
exts = [
|
||||||
Extension("_block", [op.join('core_pe', 'modules', 'block.c'), op.join('core_pe', 'modules', 'common.c')]),
|
Extension("_block", [op.join('core_pe', 'modules', 'block.c'), op.join('core_pe', 'modules', 'common.c')]),
|
||||||
@@ -136,25 +302,20 @@ def build_pe_modules(ui):
|
|||||||
script_args = ['build_ext', '--inplace'],
|
script_args = ['build_ext', '--inplace'],
|
||||||
ext_modules = exts,
|
ext_modules = exts,
|
||||||
)
|
)
|
||||||
move('_block.so', op.join('core_pe', '_block.so'))
|
move_all('_block_qt*', op.join('qt', 'pe'))
|
||||||
move('_block.pyd', op.join('core_pe', '_block.pyd'))
|
move_all('_block*', 'core_pe')
|
||||||
move('_block_osx.so', op.join('core_pe', '_block_osx.so'))
|
move_all('_cache*', 'core_pe')
|
||||||
move('_cache.so', op.join('core_pe', '_cache.so'))
|
|
||||||
move('_cache.pyd', op.join('core_pe', '_cache.pyd'))
|
|
||||||
move('_block_qt.so', op.join('qt', 'pe', '_block_qt.so'))
|
|
||||||
move('_block_qt.pyd', op.join('qt', 'pe', '_block_qt.pyd'))
|
|
||||||
|
|
||||||
def build_normal(edition, ui, dev):
|
def build_normal(edition, ui, dev, conf):
|
||||||
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
||||||
add_to_pythonpath('.')
|
add_to_pythonpath('.')
|
||||||
build_help(edition)
|
|
||||||
print("Building dupeGuru")
|
print("Building dupeGuru")
|
||||||
if edition == 'pe':
|
if edition == 'pe':
|
||||||
build_pe_modules(ui)
|
build_pe_modules(ui)
|
||||||
if ui == 'cocoa':
|
if ui == 'cocoa':
|
||||||
build_cocoa(edition, dev)
|
build_cocoa(edition, dev)
|
||||||
elif ui == 'qt':
|
elif ui == 'qt':
|
||||||
build_qt(edition, dev)
|
build_qt(edition, dev, conf)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
options = parse_args()
|
options = parse_args()
|
||||||
@@ -165,14 +326,34 @@ def main():
|
|||||||
if dev:
|
if dev:
|
||||||
print("Building in Dev mode")
|
print("Building in Dev mode")
|
||||||
if options.clean:
|
if options.clean:
|
||||||
if op.exists('build'):
|
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
|
||||||
shutil.rmtree('build')
|
if op.exists(path):
|
||||||
|
shutil.rmtree(path)
|
||||||
if not op.exists('build'):
|
if not op.exists('build'):
|
||||||
os.mkdir('build')
|
os.mkdir('build')
|
||||||
if options.only_help:
|
if options.doc:
|
||||||
build_help(edition)
|
build_help(edition)
|
||||||
|
elif options.loc:
|
||||||
|
build_localizations(ui, edition)
|
||||||
|
elif options.updatepot:
|
||||||
|
build_updatepot()
|
||||||
|
elif options.mergepot:
|
||||||
|
build_mergepot()
|
||||||
|
elif options.normpo:
|
||||||
|
build_normpo()
|
||||||
|
elif options.cocoa_ext:
|
||||||
|
build_cocoa_proxy_module()
|
||||||
|
build_cocoa_bridging_interfaces(edition)
|
||||||
|
elif options.cocoa_compile:
|
||||||
|
os.chdir('cocoa')
|
||||||
|
print_and_do(cocoa_compile_command(edition))
|
||||||
|
os.chdir('..')
|
||||||
|
cocoa_app(edition).copy_executable('cocoa/build/dupeGuru')
|
||||||
|
elif options.xibless:
|
||||||
|
build_cocoalib_xibless()
|
||||||
|
build_xibless(edition)
|
||||||
else:
|
else:
|
||||||
build_normal(edition, ui, dev)
|
build_normal(edition, ui, dev, conf)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 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 "HSAboutBox.h"
|
|
||||||
#import "HSRecentFiles.h"
|
|
||||||
|
|
||||||
@interface AppDelegateBase : NSObject
|
|
||||||
{
|
|
||||||
IBOutlet PyDupeGuruBase *py;
|
|
||||||
IBOutlet NSMenu *recentResultsMenu;
|
|
||||||
IBOutlet NSMenu *actionsMenu;
|
|
||||||
IBOutlet NSMenu *columnsMenu;
|
|
||||||
|
|
||||||
ResultWindowBase *_resultWindow;
|
|
||||||
DirectoryPanel *_directoryPanel;
|
|
||||||
DetailsPanel *_detailsPanel;
|
|
||||||
NSWindowController *_preferencesPanel;
|
|
||||||
HSAboutBox *_aboutBox;
|
|
||||||
HSRecentFiles *_recentResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Virtual */
|
|
||||||
- (PyDupeGuruBase *)py;
|
|
||||||
- (ResultWindowBase *)createResultWindow;
|
|
||||||
- (DirectoryPanel *)createDirectoryPanel;
|
|
||||||
- (DetailsPanel *)createDetailsPanel;
|
|
||||||
- (NSString *)homepageURL;
|
|
||||||
|
|
||||||
/* Public */
|
|
||||||
- (ResultWindowBase *)resultWindow;
|
|
||||||
- (DirectoryPanel *)directoryPanel;
|
|
||||||
- (DetailsPanel *)detailsPanel;
|
|
||||||
- (HSRecentFiles *)recentResults;
|
|
||||||
- (NSMenu *)columnsMenu;
|
|
||||||
|
|
||||||
/* 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)startScanning:(id)sender;
|
|
||||||
@end
|
|
||||||
@@ -1,206 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 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 <Sparkle/SUUpdater.h>
|
|
||||||
|
|
||||||
@implementation AppDelegateBase
|
|
||||||
- (void)awakeFromNib
|
|
||||||
{
|
|
||||||
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 = nil; // Lazily loaded
|
|
||||||
_aboutBox = nil; // Lazily loaded
|
|
||||||
_preferencesPanel = nil; // Lazily loaded
|
|
||||||
[[[self directoryPanel] window] makeKeyAndOrderFront:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Virtual */
|
|
||||||
|
|
||||||
- (PyDupeGuruBase *)py { return py; }
|
|
||||||
|
|
||||||
- (ResultWindowBase *)createResultWindow
|
|
||||||
{
|
|
||||||
return nil; // must be overriden by all editions
|
|
||||||
}
|
|
||||||
|
|
||||||
- (DirectoryPanel *)createDirectoryPanel
|
|
||||||
{
|
|
||||||
return [[DirectoryPanel alloc] initWithParentApp:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (DetailsPanel *)createDetailsPanel
|
|
||||||
{
|
|
||||||
return [[DetailsPanel alloc] initWithPy:py];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)homepageURL
|
|
||||||
{
|
|
||||||
return @""; // must be overriden by all editions
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Public */
|
|
||||||
- (ResultWindowBase *)resultWindow
|
|
||||||
{
|
|
||||||
return _resultWindow;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (DirectoryPanel *)directoryPanel
|
|
||||||
{
|
|
||||||
return _directoryPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (DetailsPanel *)detailsPanel
|
|
||||||
{
|
|
||||||
if (!_detailsPanel)
|
|
||||||
_detailsPanel = [self createDetailsPanel];
|
|
||||||
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(@"SelectResultToLoadMsg")];
|
|
||||||
if ([op runModal] == NSOKButton) {
|
|
||||||
NSString *filename = [[op filenames] objectAtIndex:0];
|
|
||||||
[py 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:py];
|
|
||||||
}
|
|
||||||
[[_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)startScanning:(id)sender
|
|
||||||
{
|
|
||||||
[[self resultWindow] startDuplicateScan:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Delegate */
|
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[[ProgressController mainProgressController] setWorker:py];
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
//Restore Columns
|
|
||||||
NSArray *columnsOrder = [ud arrayForKey:@"columnsOrder"];
|
|
||||||
NSDictionary *columnsWidth = [ud dictionaryForKey:@"columnsWidth"];
|
|
||||||
if ([columnsOrder count])
|
|
||||||
[[self resultWindow] restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
|
||||||
else
|
|
||||||
[[self resultWindow] resetColumnsToDefault:nil];
|
|
||||||
[HSFairwareReminder showNagWithApp:[self py]];
|
|
||||||
[py loadSession];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
if (![[[self directoryPanel] window] isVisible]) {
|
|
||||||
[[self directoryPanel] showWindow:NSApp];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
|
|
||||||
{
|
|
||||||
if ([py resultsAreModified]) {
|
|
||||||
NSString *msg = TR(@"ReallyWantToQuitMsg");
|
|
||||||
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) { // NO
|
|
||||||
return NSTerminateCancel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return NSTerminateNow;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)applicationWillTerminate:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[ud setObject: [[self resultWindow] getColumnsOrder] forKey:@"columnsOrder"];
|
|
||||||
[ud setObject: [[self resultWindow] getColumnsWidth] forKey:@"columnsWidth"];
|
|
||||||
NSInteger sc = [ud integerForKey:@"sessionCountSinceLastIgnorePurge"];
|
|
||||||
if (sc >= 10) {
|
|
||||||
sc = -1;
|
|
||||||
[py purgeIgnoreList];
|
|
||||||
}
|
|
||||||
sc++;
|
|
||||||
[py 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
|
|
||||||
{
|
|
||||||
[py loadResultsFrom:path];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
76
cocoa/base/AppDelegateBase.h
Normal file
76
cocoa/base/AppDelegateBase.h
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 <Sparkle/SUUpdater.h>
|
||||||
|
#import "PyDupeGuru.h"
|
||||||
|
#import "ResultWindow.h"
|
||||||
|
#import "DetailsPanel.h"
|
||||||
|
#import "DirectoryPanel.h"
|
||||||
|
#import "IgnoreListDialog.h"
|
||||||
|
#import "HSAboutBox.h"
|
||||||
|
#import "HSRecentFiles.h"
|
||||||
|
#import "HSProgressWindow.h"
|
||||||
|
|
||||||
|
@interface AppDelegateBase : NSObject
|
||||||
|
{
|
||||||
|
NSMenu *recentResultsMenu;
|
||||||
|
NSMenu *columnsMenu;
|
||||||
|
SUUpdater *updater;
|
||||||
|
|
||||||
|
PyDupeGuru *model;
|
||||||
|
ResultWindowBase *_resultWindow;
|
||||||
|
DirectoryPanel *_directoryPanel;
|
||||||
|
DetailsPanel *_detailsPanel;
|
||||||
|
IgnoreListDialog *_ignoreListDialog;
|
||||||
|
HSProgressWindow *_progressWindow;
|
||||||
|
NSWindowController *_preferencesPanel;
|
||||||
|
HSAboutBox *_aboutBox;
|
||||||
|
HSRecentFiles *_recentResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (readwrite, retain) NSMenu *recentResultsMenu;
|
||||||
|
@property (readwrite, retain) NSMenu *columnsMenu;
|
||||||
|
@property (readwrite, retain) SUUpdater *updater;
|
||||||
|
|
||||||
|
/* Virtual */
|
||||||
|
+ (NSDictionary *)defaultPreferences;
|
||||||
|
- (PyDupeGuru *)model;
|
||||||
|
- (ResultWindowBase *)createResultWindow;
|
||||||
|
- (DirectoryPanel *)createDirectoryPanel;
|
||||||
|
- (DetailsPanel *)createDetailsPanel;
|
||||||
|
- (NSString *)homepageURL;
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
- (void)finalizeInit;
|
||||||
|
- (ResultWindowBase *)resultWindow;
|
||||||
|
- (DirectoryPanel *)directoryPanel;
|
||||||
|
- (DetailsPanel *)detailsPanel;
|
||||||
|
- (HSRecentFiles *)recentResults;
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
|
||||||
|
- (void)applicationWillBecomeActive:(NSNotification *)aNotification;
|
||||||
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
|
||||||
|
- (void)applicationWillTerminate:(NSNotification *)aNotification;
|
||||||
|
- (void)recentFileClicked:(NSString *)path;
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
- (void)loadResults;
|
||||||
|
- (void)openWebsite;
|
||||||
|
- (void)openHelp;
|
||||||
|
- (void)showAboutBox;
|
||||||
|
- (void)showDirectoryWindow;
|
||||||
|
- (void)showPreferencesPanel;
|
||||||
|
- (void)showResultWindow;
|
||||||
|
- (void)showIgnoreList;
|
||||||
|
- (void)startScanning;
|
||||||
|
|
||||||
|
/* model --> view */
|
||||||
|
- (void)showMessage:(NSString *)msg;
|
||||||
|
@end
|
||||||
292
cocoa/base/AppDelegateBase.m
Normal file
292
cocoa/base/AppDelegateBase.m
Normal file
@@ -0,0 +1,292 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 "AppDelegateBase.h"
|
||||||
|
#import "ProgressController.h"
|
||||||
|
#import "HSPyUtil.h"
|
||||||
|
#import "Consts.h"
|
||||||
|
#import "Dialogs.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "ValueTransformers.h"
|
||||||
|
#import "PreferencesPanel_UI.h"
|
||||||
|
|
||||||
|
@implementation AppDelegateBase
|
||||||
|
|
||||||
|
@synthesize recentResultsMenu;
|
||||||
|
@synthesize columnsMenu;
|
||||||
|
@synthesize updater;
|
||||||
|
|
||||||
|
+ (NSDictionary *)defaultPreferences
|
||||||
|
{
|
||||||
|
NSMutableDictionary *d = [NSMutableDictionary dictionary];
|
||||||
|
[d setObject:i2n(1) forKey:@"recreatePathType"];
|
||||||
|
[d setObject:i2n(11) forKey:TableFontSize];
|
||||||
|
[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:@"DebugMode"];
|
||||||
|
[d setObject:@"" forKey:@"CustomCommand"];
|
||||||
|
[d setObject:[NSArray array] forKey:@"recentDirectories"];
|
||||||
|
[d setObject:[NSArray array] forKey:@"columnsOrder"];
|
||||||
|
[d setObject:[NSDictionary dictionary] forKey:@"columnsWidth"];
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ (void)initialize
|
||||||
|
{
|
||||||
|
HSVTAdd *vt = [[[HSVTAdd alloc] initWithValue:4] autorelease];
|
||||||
|
[NSValueTransformer setValueTransformer:vt forName:@"vtRowHeightOffset"];
|
||||||
|
NSDictionary *d = [self defaultPreferences];
|
||||||
|
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:d];
|
||||||
|
[[NSUserDefaults standardUserDefaults] registerDefaults:d];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)init
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
model = [[PyDupeGuru alloc] init];
|
||||||
|
[model bindCallback:createCallback(@"DupeGuruView", self)];
|
||||||
|
[self setUpdater:[SUUpdater sharedUpdater]];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)finalizeInit
|
||||||
|
{
|
||||||
|
// We can only finalize initialization once the main menu has been created, which cannot happen
|
||||||
|
// before AppDelegate is created.
|
||||||
|
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]];
|
||||||
|
_progressWindow = [[HSProgressWindow alloc] initWithPyRef:[[self model] progressWindow] view:nil];
|
||||||
|
[_progressWindow setParentWindow:[_resultWindow window]];
|
||||||
|
_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
- (void)loadResults
|
||||||
|
{
|
||||||
|
NSOpenPanel *op = [NSOpenPanel openPanel];
|
||||||
|
[op setCanChooseFiles:YES];
|
||||||
|
[op setCanChooseDirectories:NO];
|
||||||
|
[op setCanCreateDirectories:NO];
|
||||||
|
[op setAllowsMultipleSelection:NO];
|
||||||
|
[op setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
|
||||||
|
[op setTitle:NSLocalizedString(@"Select a results file to load", @"")];
|
||||||
|
if ([op runModal] == NSOKButton) {
|
||||||
|
NSString *filename = [[[op URLs] objectAtIndex:0] path];
|
||||||
|
[model loadResultsFrom:filename];
|
||||||
|
[[self recentResults] addFile:filename];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openWebsite
|
||||||
|
{
|
||||||
|
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:[self homepageURL]]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openHelp
|
||||||
|
{
|
||||||
|
NSBundle *b = [NSBundle mainBundle];
|
||||||
|
NSString *p = [b pathForResource:@"index" ofType:@"html" inDirectory:@"help"];
|
||||||
|
NSURL *u = [NSURL fileURLWithPath:p];
|
||||||
|
[[NSWorkspace sharedWorkspace] openURL:u];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showAboutBox
|
||||||
|
{
|
||||||
|
if (_aboutBox == nil) {
|
||||||
|
_aboutBox = [[HSAboutBox alloc] initWithApp:model];
|
||||||
|
}
|
||||||
|
[[_aboutBox window] makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showDirectoryWindow
|
||||||
|
{
|
||||||
|
[[[self directoryPanel] window] makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showPreferencesPanel
|
||||||
|
{
|
||||||
|
if (_preferencesPanel == nil) {
|
||||||
|
_preferencesPanel = [[NSWindowController alloc] initWithWindow:createPreferencesPanel_UI(nil)];
|
||||||
|
}
|
||||||
|
[_preferencesPanel showWindow:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showResultWindow
|
||||||
|
{
|
||||||
|
[[[self resultWindow] window] makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)showIgnoreList
|
||||||
|
{
|
||||||
|
[model showIgnoreList];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)startScanning
|
||||||
|
{
|
||||||
|
[[self resultWindow] startDuplicateScan];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
[model loadSession];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
if (![[[self directoryPanel] window] isVisible]) {
|
||||||
|
[[self directoryPanel] showWindow:NSApp];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
|
||||||
|
{
|
||||||
|
if ([model resultsAreModified]) {
|
||||||
|
NSString *msg = NSLocalizedString(@"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];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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 URLs] objectAtIndex:0] path];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSString *)selectDestFileWithPrompt:(NSString *)prompt extension:(NSString *)extension
|
||||||
|
{
|
||||||
|
NSSavePanel *sp = [NSSavePanel savePanel];
|
||||||
|
[sp setCanCreateDirectories:YES];
|
||||||
|
[sp setAllowedFileTypes:[NSArray arrayWithObject:extension]];
|
||||||
|
[sp setTitle:prompt];
|
||||||
|
if ([sp runModal] == NSOKButton) {
|
||||||
|
return [[sp URL] path];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@end
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
http://www.hardcoded.net/licenses/bsd_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
#define JobStarted @"JobStarted"
|
#define JobStarted @"JobStarted"
|
||||||
#define JobInProgress @"JobInProgress"
|
#define JobInProgress @"JobInProgress"
|
||||||
|
#define TableFontSize @"TableFontSize"
|
||||||
|
|
||||||
#define jobLoad @"job_load"
|
#define jobLoad @"job_load"
|
||||||
#define jobScan @"job_scan"
|
#define jobScan @"job_scan"
|
||||||
@@ -17,4 +16,4 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
#define jobMove @"job_move"
|
#define jobMove @"job_move"
|
||||||
#define jobDelete @"job_delete"
|
#define jobDelete @"job_delete"
|
||||||
|
|
||||||
#define TR(s) NSLocalizedString(s, @"")
|
#define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType"
|
||||||
|
|||||||
33
cocoa/base/DeletionOptions.h
Normal file
33
cocoa/base/DeletionOptions.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 "PyDeletionOptions.h"
|
||||||
|
|
||||||
|
@interface DeletionOptions : NSWindowController
|
||||||
|
{
|
||||||
|
|
||||||
|
PyDeletionOptions *model;
|
||||||
|
|
||||||
|
NSTextField *messageTextField;
|
||||||
|
NSButton *linkButton;
|
||||||
|
NSMatrix *linkTypeRadio;
|
||||||
|
NSButton *directButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (readwrite, retain) NSTextField *messageTextField;
|
||||||
|
@property (readwrite, retain) NSButton *linkButton;
|
||||||
|
@property (readwrite, retain) NSMatrix *linkTypeRadio;
|
||||||
|
@property (readwrite, retain) NSButton *directButton;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||||
|
|
||||||
|
- (void)updateOptions;
|
||||||
|
- (void)proceed;
|
||||||
|
- (void)cancel;
|
||||||
|
@end
|
||||||
72
cocoa/base/DeletionOptions.m
Normal file
72
cocoa/base/DeletionOptions.m
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 "DeletionOptions.h"
|
||||||
|
#import "DeletionOptions_UI.h"
|
||||||
|
#import "HSPyUtil.h"
|
||||||
|
|
||||||
|
@implementation DeletionOptions
|
||||||
|
|
||||||
|
@synthesize messageTextField;
|
||||||
|
@synthesize linkButton;
|
||||||
|
@synthesize linkTypeRadio;
|
||||||
|
@synthesize directButton;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||||
|
{
|
||||||
|
self = [super initWithWindow:nil];
|
||||||
|
model = [[PyDeletionOptions alloc] initWithModel:aPyRef];
|
||||||
|
[self setWindow:createDeletionOptions_UI(self)];
|
||||||
|
[model bindCallback:createCallback(@"DeletionOptionsView", self)];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[model release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)updateOptions
|
||||||
|
{
|
||||||
|
[model setLinkDeleted:[linkButton state] == NSOnState];
|
||||||
|
[model setUseHardlinks:[linkTypeRadio selectedColumn] == 1];
|
||||||
|
[model setDirect:[directButton state] == NSOnState];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)proceed
|
||||||
|
{
|
||||||
|
[NSApp stopModalWithCode:NSOKButton];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)cancel
|
||||||
|
{
|
||||||
|
[NSApp stopModalWithCode:NSCancelButton];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* model --> view */
|
||||||
|
- (void)updateMsg:(NSString *)msg
|
||||||
|
{
|
||||||
|
[messageTextField setStringValue:msg];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)show
|
||||||
|
{
|
||||||
|
[linkButton setState:NSOffState];
|
||||||
|
[directButton setState:NSOffState];
|
||||||
|
[linkTypeRadio selectCellAtRow:0 column:0];
|
||||||
|
NSInteger r = [NSApp runModalForWindow:[self window]];
|
||||||
|
[[self window] close];
|
||||||
|
return r == NSOKButton;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setHardlinkOptionEnabled:(BOOL)enabled
|
||||||
|
{
|
||||||
|
[linkTypeRadio setEnabled:enabled];
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 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 "HSWindowController.h"
|
|
||||||
#import "PyApp.h"
|
|
||||||
#import "PyDetailsPanel.h"
|
|
||||||
|
|
||||||
@interface DetailsPanel : HSWindowController
|
|
||||||
{
|
|
||||||
IBOutlet NSTableView *detailsTable;
|
|
||||||
}
|
|
||||||
- (id)initWithPy:(PyApp *)aPy;
|
|
||||||
- (PyDetailsPanel *)py;
|
|
||||||
|
|
||||||
- (BOOL)isVisible;
|
|
||||||
- (void)toggleVisibility;
|
|
||||||
|
|
||||||
/* Python --> Cocoa */
|
|
||||||
- (void)refresh;
|
|
||||||
@end
|
|
||||||
31
cocoa/base/DetailsPanelBase.h
Normal file
31
cocoa/base/DetailsPanelBase.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 DetailsPanelBase : NSWindowController <NSTableViewDataSource>
|
||||||
|
{
|
||||||
|
NSTableView *detailsTable;
|
||||||
|
|
||||||
|
PyDetailsPanel *model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (readwrite, retain) NSTableView *detailsTable;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||||
|
- (PyDetailsPanel *)model;
|
||||||
|
|
||||||
|
- (NSWindow *)createWindow;
|
||||||
|
- (BOOL)isVisible;
|
||||||
|
- (void)toggleVisibility;
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)refresh;
|
||||||
|
@end
|
||||||
@@ -1,32 +1,41 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
http://www.hardcoded.net/licenses/bsd_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "DetailsPanel.h"
|
#import "DetailsPanelBase.h"
|
||||||
#import "Utils.h"
|
#import "HSPyUtil.h"
|
||||||
|
|
||||||
@implementation DetailsPanel
|
@implementation DetailsPanelBase
|
||||||
- (id)initWithPy:(PyApp *)aPy
|
|
||||||
|
@synthesize detailsTable;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||||
{
|
{
|
||||||
self = [super initWithNibName:@"DetailsPanel" pyClassName:@"PyDetailsPanel" pyParent:aPy];
|
self = [super initWithWindow:nil];
|
||||||
[self window]; //So the detailsTable is initialized.
|
[self setWindow:[self createWindow]];
|
||||||
[self connect];
|
model = [[PyDetailsPanel alloc] initWithModel:aPyRef];
|
||||||
|
[model bindCallback:createCallback(@"DetailsPanelView", self)];
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
[self disconnect];
|
[model release];
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (PyDetailsPanel *)py
|
- (PyDetailsPanel *)model
|
||||||
{
|
{
|
||||||
return (PyDetailsPanel *)py;
|
return (PyDetailsPanel *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSWindow *)createWindow
|
||||||
|
{
|
||||||
|
return nil; // Virtual
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)refreshDetails
|
- (void)refreshDetails
|
||||||
@@ -53,12 +62,12 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
/* NSTableView Delegate */
|
/* NSTableView Delegate */
|
||||||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||||
{
|
{
|
||||||
return [[self py] numberOfRows];
|
return [[self model] numberOfRows];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
{
|
{
|
||||||
return [[self py] valueForColumn:[column identifier] row:row];
|
return [[self model] valueForColumn:[column identifier] row:row];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Python --> Cocoa */
|
/* Python --> Cocoa */
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -7,12 +7,15 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <Python.h>
|
||||||
#import "HSOutline.h"
|
#import "HSOutline.h"
|
||||||
#import "PyDirectoryOutline.h"
|
#import "PyDirectoryOutline.h"
|
||||||
|
|
||||||
#define DGAddedFoldersNotification @"DGAddedFoldersNotification"
|
#define DGAddedFoldersNotification @"DGAddedFoldersNotification"
|
||||||
|
|
||||||
@interface DirectoryOutline : HSOutline {}
|
@interface DirectoryOutline : HSOutline {}
|
||||||
- (id)initWithPyParent:(id)aPyParent view:(HSOutlineView *)aOutlineView;
|
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView;
|
||||||
- (PyDirectoryOutline *)py;
|
- (PyDirectoryOutline *)model;
|
||||||
|
|
||||||
|
- (void)selectAll;
|
||||||
@end;
|
@end;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -9,23 +9,23 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
#import "DirectoryOutline.h"
|
#import "DirectoryOutline.h"
|
||||||
|
|
||||||
@implementation DirectoryOutline
|
@implementation DirectoryOutline
|
||||||
- (id)initWithPyParent:(id)aPyParent view:(HSOutlineView *)aOutlineView
|
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView
|
||||||
{
|
{
|
||||||
self = [super initWithPyClassName:@"PyDirectoryOutline" pyParent:aPyParent view:aOutlineView];
|
self = [super initWithPyRef:aPyRef wrapperClass:[PyDirectoryOutline class]
|
||||||
[outlineView registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
|
callbackClassName:@"DirectoryOutlineView" view:aOutlineView];
|
||||||
[self connect];
|
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (PyDirectoryOutline *)model
|
||||||
{
|
{
|
||||||
[self disconnect];
|
return (PyDirectoryOutline *)model;
|
||||||
[super dealloc];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (PyDirectoryOutline *)py
|
/* Public */
|
||||||
|
- (void)selectAll
|
||||||
{
|
{
|
||||||
return (PyDirectoryOutline *)py;
|
[[self model] selectAll];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Delegate */
|
/* Delegate */
|
||||||
@@ -53,7 +53,7 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
if (!(sourceDragMask & NSDragOperationLink))
|
if (!(sourceDragMask & NSDragOperationLink))
|
||||||
return NO;
|
return NO;
|
||||||
for (NSString *foldername in foldernames) {
|
for (NSString *foldername in foldernames) {
|
||||||
[[self py] addDirectory:foldername];
|
[[self model] addDirectory:foldername];
|
||||||
}
|
}
|
||||||
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:foldernames forKey:@"foldernames"];
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:foldernames forKey:@"foldernames"];
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:DGAddedFoldersNotification
|
[[NSNotificationCenter defaultCenter] postNotificationName:DGAddedFoldersNotification
|
||||||
@@ -67,7 +67,7 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
||||||
NSTextFieldCell *textCell = cell;
|
NSTextFieldCell *textCell = cell;
|
||||||
NSIndexPath *path = item;
|
NSIndexPath *path = item;
|
||||||
BOOL selected = [path isEqualTo:[outlineView selectedPath]];
|
BOOL selected = [path isEqualTo:[[self view] selectedPath]];
|
||||||
if (selected) {
|
if (selected) {
|
||||||
[textCell setTextColor:[NSColor blackColor]];
|
[textCell setTextColor:[NSColor blackColor]];
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -14,28 +14,38 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
|
|
||||||
@class AppDelegateBase;
|
@class AppDelegateBase;
|
||||||
|
|
||||||
@interface DirectoryPanel : NSWindowController
|
@interface DirectoryPanel : NSWindowController <NSOpenSavePanelDelegate>
|
||||||
{
|
{
|
||||||
IBOutlet NSPopUpButton *addButtonPopUp;
|
|
||||||
IBOutlet NSPopUpButton *loadRecentButtonPopUp;
|
|
||||||
IBOutlet HSOutlineView *outlineView;
|
|
||||||
IBOutlet NSButton *removeButton;
|
|
||||||
|
|
||||||
AppDelegateBase *_app;
|
AppDelegateBase *_app;
|
||||||
PyDupeGuruBase *_py;
|
PyDupeGuru *model;
|
||||||
HSRecentFiles *_recentDirectories;
|
HSRecentFiles *_recentDirectories;
|
||||||
DirectoryOutline *outline;
|
DirectoryOutline *outline;
|
||||||
BOOL _alwaysShowPopUp;
|
BOOL _alwaysShowPopUp;
|
||||||
|
NSPopUpButton *addButtonPopUp;
|
||||||
|
NSPopUpButton *loadRecentButtonPopUp;
|
||||||
|
HSOutlineView *outlineView;
|
||||||
|
NSButton *removeButton;
|
||||||
|
NSButton *loadResultsButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property (readwrite, retain) NSPopUpButton *addButtonPopUp;
|
||||||
|
@property (readwrite, retain) NSPopUpButton *loadRecentButtonPopUp;
|
||||||
|
@property (readwrite, retain) HSOutlineView *outlineView;
|
||||||
|
@property (readwrite, retain) NSButton *removeButton;
|
||||||
|
@property (readwrite, retain) NSButton *loadResultsButton;
|
||||||
|
|
||||||
- (id)initWithParentApp:(AppDelegateBase *)aParentApp;
|
- (id)initWithParentApp:(AppDelegateBase *)aParentApp;
|
||||||
|
|
||||||
- (void)fillPopUpMenu; // Virtual
|
- (void)fillPopUpMenu; // Virtual
|
||||||
|
- (void)adjustUIToLocalization;
|
||||||
|
|
||||||
- (IBAction)askForDirectory:(id)sender;
|
- (void)askForDirectory;
|
||||||
- (IBAction)popupAddDirectoryMenu:(id)sender;
|
- (void)popupAddDirectoryMenu:(id)sender;
|
||||||
- (IBAction)popupLoadRecentMenu:(id)sender;
|
- (void)popupLoadRecentMenu:(id)sender;
|
||||||
- (IBAction)removeSelectedDirectory:(id)sender;
|
- (void)removeSelectedDirectory;
|
||||||
|
|
||||||
- (void)addDirectory:(NSString *)directory;
|
- (void)addDirectory:(NSString *)directory;
|
||||||
- (void)refreshRemoveButtonText;
|
- (void)refreshRemoveButtonText;
|
||||||
|
- (void)markAll;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -7,24 +7,34 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import "DirectoryPanel.h"
|
#import "DirectoryPanel.h"
|
||||||
|
#import "DirectoryPanel_UI.h"
|
||||||
#import "Dialogs.h"
|
#import "Dialogs.h"
|
||||||
#import "Utils.h"
|
#import "Utils.h"
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation DirectoryPanel
|
@implementation DirectoryPanel
|
||||||
|
|
||||||
|
@synthesize addButtonPopUp;
|
||||||
|
@synthesize loadRecentButtonPopUp;
|
||||||
|
@synthesize outlineView;
|
||||||
|
@synthesize removeButton;
|
||||||
|
@synthesize loadResultsButton;
|
||||||
|
|
||||||
- (id)initWithParentApp:(AppDelegateBase *)aParentApp
|
- (id)initWithParentApp:(AppDelegateBase *)aParentApp
|
||||||
{
|
{
|
||||||
self = [super initWithWindowNibName:@"DirectoryPanel"];
|
self = [super initWithWindow:nil];
|
||||||
[self window];
|
[self setWindow:createDirectoryPanel_UI(self)];
|
||||||
_app = aParentApp;
|
_app = aParentApp;
|
||||||
_py = [_app py];
|
model = [_app model];
|
||||||
|
[[self window] setTitle:[model appName]];
|
||||||
_alwaysShowPopUp = NO;
|
_alwaysShowPopUp = NO;
|
||||||
[self fillPopUpMenu];
|
[self fillPopUpMenu];
|
||||||
_recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]];
|
_recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]];
|
||||||
[_recentDirectories setDelegate:self];
|
[_recentDirectories setDelegate:self];
|
||||||
outline = [[DirectoryOutline alloc] initWithPyParent:_py view:outlineView];
|
outline = [[DirectoryOutline alloc] initWithPyRef:[model directoryTree] outlineView:outlineView];
|
||||||
[self refreshRemoveButtonText];
|
[self refreshRemoveButtonText];
|
||||||
|
[self adjustUIToLocalization];
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:)
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:)
|
||||||
name:NSOutlineViewSelectionDidChangeNotification object:outlineView];
|
name:NSOutlineViewSelectionDidChangeNotification object:outlineView];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(outlineAddedFolders:)
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(outlineAddedFolders:)
|
||||||
@@ -39,37 +49,58 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Virtual */
|
/* Private */
|
||||||
|
|
||||||
- (void)fillPopUpMenu
|
- (void)fillPopUpMenu
|
||||||
{
|
{
|
||||||
NSMenu *m = [addButtonPopUp menu];
|
NSMenu *m = [addButtonPopUp menu];
|
||||||
NSMenuItem *mi = [m addItemWithTitle:TR(@"Add New Folder...") action:@selector(askForDirectory:) keyEquivalent:@""];
|
NSMenuItem *mi = [m addItemWithTitle:NSLocalizedString(@"Add New Folder...", @"") action:@selector(askForDirectory) keyEquivalent:@""];
|
||||||
[mi setTarget:self];
|
[mi setTarget:self];
|
||||||
[m addItem:[NSMenuItem separatorItem]];
|
[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 */
|
/* Actions */
|
||||||
|
|
||||||
- (IBAction)askForDirectory:(id)sender
|
- (void)askForDirectory
|
||||||
{
|
{
|
||||||
NSOpenPanel *op = [NSOpenPanel openPanel];
|
NSOpenPanel *op = [NSOpenPanel openPanel];
|
||||||
[op setCanChooseFiles:YES];
|
[op setCanChooseFiles:YES];
|
||||||
[op setCanChooseDirectories:YES];
|
[op setCanChooseDirectories:YES];
|
||||||
[op setAllowsMultipleSelection:YES];
|
[op setAllowsMultipleSelection:YES];
|
||||||
[op setTitle:TR(@"SelectFolderToAddMsg")];
|
[op setTitle:NSLocalizedString(@"Select a folder to add to the scanning list", @"")];
|
||||||
[op setDelegate:self];
|
[op setDelegate:self];
|
||||||
if ([op runModal] == NSOKButton) {
|
if ([op runModal] == NSOKButton) {
|
||||||
for (NSString *directory in [op filenames]) {
|
for (NSURL *directoryURL in [op URLs]) {
|
||||||
[self addDirectory:directory];
|
[self addDirectory:[directoryURL path]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)popupAddDirectoryMenu:(id)sender
|
- (void)popupAddDirectoryMenu:(id)sender
|
||||||
{
|
{
|
||||||
if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) {
|
if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) {
|
||||||
[self askForDirectory:sender];
|
[self askForDirectory];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
[addButtonPopUp selectItem:nil];
|
[addButtonPopUp selectItem:nil];
|
||||||
@@ -77,14 +108,14 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)popupLoadRecentMenu:(id)sender
|
- (void)popupLoadRecentMenu:(id)sender
|
||||||
{
|
{
|
||||||
if ([[[_app recentResults] filepaths] count] > 0) {
|
if ([[[_app recentResults] filepaths] count] > 0) {
|
||||||
NSMenu *m = [loadRecentButtonPopUp menu];
|
NSMenu *m = [loadRecentButtonPopUp menu];
|
||||||
while ([m numberOfItems] > 0) {
|
while ([m numberOfItems] > 0) {
|
||||||
[m removeItemAtIndex:0];
|
[m removeItemAtIndex:0];
|
||||||
}
|
}
|
||||||
NSMenuItem *mi = [m addItemWithTitle:TR(@"Load from file...") action:@selector(loadResults:) keyEquivalent:@""];
|
NSMenuItem *mi = [m addItemWithTitle:NSLocalizedString(@"Load from file...", @"") action:@selector(loadResults) keyEquivalent:@""];
|
||||||
[mi setTarget:_app];
|
[mi setTarget:_app];
|
||||||
[m addItem:[NSMenuItem separatorItem]];
|
[m addItem:[NSMenuItem separatorItem]];
|
||||||
[[_app recentResults] fillMenu:m];
|
[[_app recentResults] fillMenu:m];
|
||||||
@@ -92,42 +123,21 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
[[loadRecentButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
[[loadRecentButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
[_app loadResults:nil];
|
[_app loadResults];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)removeSelectedDirectory:(id)sender
|
- (void)removeSelectedDirectory
|
||||||
{
|
{
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
if ([outlineView selectedRow] < 0)
|
[[outline model] removeSelectedDirectory];
|
||||||
return;
|
|
||||||
NSIndexPath *path = [outline selectedIndexPath];
|
|
||||||
NSInteger state = [outline intProperty:@"state" valueAtPath:path];
|
|
||||||
if (([path length] == 1) && (state != 2)) {
|
|
||||||
[_py removeDirectory:i2n([path indexAtPosition:0])];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
NSInteger newState = state == 2 ? 0 : 2; // If excluded, put it back
|
|
||||||
[outline setIntProperty:@"state" value:newState atPath:path];
|
|
||||||
[outlineView display];
|
|
||||||
}
|
|
||||||
[self refreshRemoveButtonText];
|
[self refreshRemoveButtonText];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Public */
|
/* Public */
|
||||||
- (void)addDirectory:(NSString *)directory
|
- (void)addDirectory:(NSString *)directory
|
||||||
{
|
{
|
||||||
NSInteger r = [[_py addDirectory:directory] intValue];
|
[model addDirectory:directory];
|
||||||
if (r) {
|
|
||||||
NSString *m = @"";
|
|
||||||
if (r == 1) {
|
|
||||||
m = TR(@"FolderAlreadyInListMsg");
|
|
||||||
}
|
|
||||||
else if (r == 2) {
|
|
||||||
m = TR(@"FolderDoesNotExistMsg");
|
|
||||||
}
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:m,directory]];
|
|
||||||
}
|
|
||||||
[_recentDirectories addFile:directory];
|
[_recentDirectories addFile:directory];
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
}
|
}
|
||||||
@@ -139,10 +149,22 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[removeButton setEnabled:YES];
|
[removeButton setEnabled:YES];
|
||||||
NSInteger state = [outline intProperty:@"state" valueAtPath:[outline selectedIndexPath]];
|
NSIndexPath *path = [outline selectedIndexPath];
|
||||||
NSString *imgName = state == 2 ? @"NSGoLeftTemplate" : @"NSRemoveTemplate";
|
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]];
|
[removeButton setImage:[NSImage imageNamed:imgName]];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)markAll
|
||||||
|
{
|
||||||
|
/* markAll isn't very descriptive of what we do, but since we re-use the Mark All button from
|
||||||
|
the result window, we don't have much choice.
|
||||||
|
*/
|
||||||
|
[outline selectAll];
|
||||||
|
}
|
||||||
|
|
||||||
/* Delegate */
|
/* Delegate */
|
||||||
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
|
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
|
||||||
@@ -157,6 +179,14 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
[self addDirectory:path];
|
[self addDirectory:path];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (BOOL)validateMenuItem:(NSMenuItem *)item
|
||||||
|
{
|
||||||
|
if ([item action] == @selector(markAll)) {
|
||||||
|
[item setTitle:NSLocalizedString(@"Select All", @"")];
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
/* Notifications */
|
/* Notifications */
|
||||||
|
|
||||||
- (void)directorySelectionChanged:(NSNotification *)aNotification
|
- (void)directorySelectionChanged:(NSNotification *)aNotification
|
||||||
|
|||||||
25
cocoa/base/IgnoreListDialog.h
Normal file
25
cocoa/base/IgnoreListDialog.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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
|
||||||
|
{
|
||||||
|
PyIgnoreListDialog *model;
|
||||||
|
HSTable *ignoreListTable;
|
||||||
|
NSTableView *ignoreListTableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (readwrite, retain) PyIgnoreListDialog *model;
|
||||||
|
@property (readwrite, retain) NSTableView *ignoreListTableView;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||||
|
- (void)initializeColumns;
|
||||||
|
@end
|
||||||
51
cocoa/base/IgnoreListDialog.m
Normal file
51
cocoa/base/IgnoreListDialog.m
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 "IgnoreListDialog_UI.h"
|
||||||
|
#import "HSPyUtil.h"
|
||||||
|
|
||||||
|
@implementation IgnoreListDialog
|
||||||
|
|
||||||
|
@synthesize model;
|
||||||
|
@synthesize ignoreListTableView;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||||
|
{
|
||||||
|
self = [super initWithWindow:nil];
|
||||||
|
self.model = [[[PyIgnoreListDialog alloc] initWithModel:aPyRef] autorelease];
|
||||||
|
[self.model bindCallback:createCallback(@"IgnoreListDialogView", self)];
|
||||||
|
[self setWindow:createIgnoreListDialog_UI(self)];
|
||||||
|
ignoreListTable = [[HSTable alloc] initWithPyRef:[model ignoreListTable] tableView:ignoreListTableView];
|
||||||
|
[self initializeColumns];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[ignoreListTable 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];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* model --> view */
|
||||||
|
- (void)show
|
||||||
|
{
|
||||||
|
[self showWindow:self];
|
||||||
|
}
|
||||||
|
@end
|
||||||
37
cocoa/base/PrioritizeDialog.h
Normal file
37
cocoa/base/PrioritizeDialog.h
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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
|
||||||
|
{
|
||||||
|
NSPopUpButton *categoryPopUpView;
|
||||||
|
NSTableView *criteriaTableView;
|
||||||
|
NSTableView *prioritizationTableView;
|
||||||
|
|
||||||
|
PyPrioritizeDialog *model;
|
||||||
|
HSPopUpList *categoryPopUp;
|
||||||
|
HSSelectableList *criteriaList;
|
||||||
|
PrioritizeList *prioritizationList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (readwrite, retain) NSPopUpButton *categoryPopUpView;
|
||||||
|
@property (readwrite, retain) NSTableView *criteriaTableView;
|
||||||
|
@property (readwrite, retain) NSTableView *prioritizationTableView;
|
||||||
|
|
||||||
|
- (id)initWithApp:(PyDupeGuru *)aApp;
|
||||||
|
- (PyPrioritizeDialog *)model;
|
||||||
|
|
||||||
|
- (void)ok;
|
||||||
|
- (void)cancel;
|
||||||
|
@end;
|
||||||
56
cocoa/base/PrioritizeDialog.m
Normal file
56
cocoa/base/PrioritizeDialog.m
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 "PrioritizeDialog_UI.h"
|
||||||
|
#import "HSPyUtil.h"
|
||||||
|
|
||||||
|
@implementation PrioritizeDialog
|
||||||
|
|
||||||
|
@synthesize categoryPopUpView;
|
||||||
|
@synthesize criteriaTableView;
|
||||||
|
@synthesize prioritizationTableView;
|
||||||
|
|
||||||
|
- (id)initWithApp:(PyDupeGuru *)aApp
|
||||||
|
{
|
||||||
|
self = [super initWithWindowNibName:@"PrioritizeDialog"];
|
||||||
|
model = [[PyPrioritizeDialog alloc] initWithApp:[aApp pyRef]];
|
||||||
|
[self setWindow:createPrioritizeDialog_UI(self)];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)ok
|
||||||
|
{
|
||||||
|
[NSApp stopModal];
|
||||||
|
[self close];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)cancel
|
||||||
|
{
|
||||||
|
[NSApp abortModal];
|
||||||
|
[self close];
|
||||||
|
}
|
||||||
|
@end
|
||||||
16
cocoa/base/PrioritizeList.h
Normal file
16
cocoa/base/PrioritizeList.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 2014 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
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -7,19 +7,20 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "HSWindowController.h"
|
|
||||||
#import "PyApp.h"
|
|
||||||
#import "PyProblemDialog.h"
|
#import "PyProblemDialog.h"
|
||||||
#import "HSTable.h"
|
#import "HSTable.h"
|
||||||
|
|
||||||
@interface ProblemDialog : HSWindowController
|
@interface ProblemDialog : NSWindowController
|
||||||
{
|
{
|
||||||
IBOutlet NSTableView *problemTableView;
|
PyProblemDialog *model;
|
||||||
|
|
||||||
HSTable *problemTable;
|
HSTable *problemTable;
|
||||||
|
NSTableView *problemTableView;
|
||||||
}
|
}
|
||||||
- (id)initWithPy:(PyApp *)aPy;
|
|
||||||
- (PyProblemDialog *)py;
|
|
||||||
|
|
||||||
- (IBAction)revealSelected:(id)sender;
|
@property (readwrite, retain) PyProblemDialog *model;
|
||||||
|
@property (readwrite, retain) NSTableView *problemTableView;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||||
|
|
||||||
|
- (void)initializeColumns;
|
||||||
@end
|
@end
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -7,34 +7,38 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import "ProblemDialog.h"
|
#import "ProblemDialog.h"
|
||||||
|
#import "ProblemDialog_UI.h"
|
||||||
#import "Utils.h"
|
#import "Utils.h"
|
||||||
|
|
||||||
@implementation ProblemDialog
|
@implementation ProblemDialog
|
||||||
- (id)initWithPy:(PyApp *)aPy
|
|
||||||
|
@synthesize model;
|
||||||
|
@synthesize problemTableView;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||||
{
|
{
|
||||||
self = [super initWithNibName:@"ProblemDialog" pyClassName:@"PyProblemDialog" pyParent:aPy];
|
self = [super initWithWindow:nil];
|
||||||
[self window]; //So the detailsTable is initialized.
|
self.model = [[PyProblemDialog alloc] initWithModel:aPyRef];
|
||||||
problemTable = [[HSTable alloc] initWithPyClassName:@"PyProblemTable" pyParent:[self py] view:problemTableView];
|
[self setWindow:createProblemDialog_UI(self)];
|
||||||
[self connect];
|
problemTable = [[HSTable alloc] initWithPyRef:[self.model problemTable] tableView:problemTableView];
|
||||||
[problemTable connect];
|
[self initializeColumns];
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (void)dealloc
|
||||||
{
|
{
|
||||||
[problemTable disconnect];
|
|
||||||
[self disconnect];
|
|
||||||
[problemTable release];
|
[problemTable release];
|
||||||
[super dealloc];
|
[super dealloc];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (PyProblemDialog *)py
|
- (void)initializeColumns
|
||||||
{
|
{
|
||||||
return (PyProblemDialog *)py;
|
HSColumnDef defs[] = {
|
||||||
}
|
{@"path", 202, 40, 0, NO, nil},
|
||||||
|
{@"msg", 228, 40, 0, NO, nil},
|
||||||
- (IBAction)revealSelected:(id)sender
|
nil
|
||||||
{
|
};
|
||||||
[[self py] revealSelected];
|
[[problemTable columns] initializeColumns:defs];
|
||||||
|
[[problemTable columns] setColumnsAsReadOnly];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 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 "PyApp.h"
|
|
||||||
|
|
||||||
@interface PyDupeGuruBase : PyApp
|
|
||||||
//Actions
|
|
||||||
- (NSNumber *)addDirectory:(NSString *)name;
|
|
||||||
- (void)removeDirectory:(NSNumber *)index;
|
|
||||||
- (void)loadResultsFrom:(NSString *)filename;
|
|
||||||
- (void)saveResultsAs:(NSString *)filename;
|
|
||||||
- (void)loadSession;
|
|
||||||
- (void)saveSession;
|
|
||||||
- (void)clearIgnoreList;
|
|
||||||
- (void)purgeIgnoreList;
|
|
||||||
- (NSString *)exportToXHTMLwithColumns:(NSArray *)aColIds;
|
|
||||||
- (void)invokeCommand:(NSString *)cmd;
|
|
||||||
|
|
||||||
- (NSNumber *)doScan;
|
|
||||||
|
|
||||||
- (void)toggleSelectedMark;
|
|
||||||
- (void)markAll;
|
|
||||||
- (void)markInvert;
|
|
||||||
- (void)markNone;
|
|
||||||
|
|
||||||
- (void)addSelectedToIgnoreList;
|
|
||||||
- (void)openSelected;
|
|
||||||
- (void)revealSelected;
|
|
||||||
- (void)makeSelectedReference;
|
|
||||||
- (void)applyFilter:(NSString *)filter;
|
|
||||||
|
|
||||||
- (void)copyOrMove:(NSNumber *)aCopy markedTo:(NSString *)destination recreatePath:(NSNumber *)aRecreateType;
|
|
||||||
- (void)deleteMarked;
|
|
||||||
- (void)hardlinkMarked;
|
|
||||||
- (void)removeMarked;
|
|
||||||
|
|
||||||
//Data
|
|
||||||
- (NSNumber *)getIgnoreListCount;
|
|
||||||
- (NSNumber *)getMarkCount;
|
|
||||||
- (BOOL)scanWasProblematic;
|
|
||||||
- (BOOL)resultsAreModified;
|
|
||||||
- (NSArray *)deltaColumns;
|
|
||||||
|
|
||||||
//Scanning options
|
|
||||||
- (void)setMinMatchPercentage:(NSNumber *)percentage;
|
|
||||||
- (void)setMixFileKind:(BOOL)mix_file_kind;
|
|
||||||
- (void)setEscapeFilterRegexp:(BOOL)escape_filter_regexp;
|
|
||||||
- (void)setRemoveEmptyFolders:(BOOL)remove_empty_folders;
|
|
||||||
- (void)setIgnoreHardlinkMatches:(BOOL)ignore_hardlink_matches;
|
|
||||||
- (void)setSizeThreshold:(NSInteger)size_threshold;
|
|
||||||
@end
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 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 "PyTable.h"
|
|
||||||
|
|
||||||
@interface PyResultTable : PyTable
|
|
||||||
- (BOOL)powerMarkerMode;
|
|
||||||
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
|
|
||||||
- (BOOL)deltaValuesMode;
|
|
||||||
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
|
|
||||||
|
|
||||||
- (NSString *)valueForRow:(NSInteger)rowIndex column:(NSInteger)aColumn;
|
|
||||||
- (BOOL)renameSelected:(NSString *)aNewName;
|
|
||||||
- (void)sortBy:(NSInteger)aIdentifier ascending:(BOOL)aAscending;
|
|
||||||
- (void)markSelected;
|
|
||||||
- (void)removeSelected;
|
|
||||||
- (NSInteger)selectedDupeCount;
|
|
||||||
@end
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -7,20 +7,17 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <Quartz/Quartz.h>
|
||||||
#import "HSTable.h"
|
#import "HSTable.h"
|
||||||
#import "PyResultTable.h"
|
#import "PyResultTable.h"
|
||||||
|
|
||||||
@interface ResultTable : HSTable
|
@interface ResultTable : HSTable <QLPreviewPanelDataSource, QLPreviewPanelDelegate>
|
||||||
{
|
{
|
||||||
NSIndexSet *_deltaColumns;
|
|
||||||
}
|
}
|
||||||
- (id)initWithPyParent:(id)aPyParent view:(NSTableView *)aTableView;
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView;
|
||||||
- (PyResultTable *)py;
|
- (PyResultTable *)model;
|
||||||
- (BOOL)powerMarkerMode;
|
- (BOOL)powerMarkerMode;
|
||||||
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
|
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
|
||||||
- (BOOL)deltaValuesMode;
|
- (BOOL)deltaValuesMode;
|
||||||
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
|
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
|
||||||
- (void)setDeltaColumns:(NSIndexSet *)aDeltaColumns;
|
|
||||||
- (NSInteger)selectedDupeCount;
|
|
||||||
- (void)removeSelected;
|
|
||||||
@end;
|
@end;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -9,70 +9,64 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
#import "ResultTable.h"
|
#import "ResultTable.h"
|
||||||
#import "Dialogs.h"
|
#import "Dialogs.h"
|
||||||
#import "Utils.h"
|
#import "Utils.h"
|
||||||
#import "Consts.h"
|
#import "HSQuicklook.h"
|
||||||
|
|
||||||
|
@interface HSTable (private)
|
||||||
|
- (void)setPySelection;
|
||||||
|
- (void)setViewSelection;
|
||||||
|
@end
|
||||||
|
|
||||||
@implementation ResultTable
|
@implementation ResultTable
|
||||||
- (id)initWithPyParent:(id)aPyParent view:(NSTableView *)aTableView
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView
|
||||||
{
|
{
|
||||||
self = [super initWithPyClassName:@"PyResultTable" pyParent:aPyParent view:aTableView];
|
self = [super initWithPyRef:aPyRef wrapperClass:[PyResultTable class] callbackClassName:@"ResultTableView" view:aTableView];
|
||||||
[self connect];
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (PyResultTable *)model
|
||||||
{
|
{
|
||||||
[self disconnect];
|
return (PyResultTable *)model;
|
||||||
[_deltaColumns release];
|
|
||||||
[super dealloc];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (PyResultTable *)py
|
/* Private */
|
||||||
|
- (void)updateQuicklookIfNeeded
|
||||||
{
|
{
|
||||||
return (PyResultTable *)py;
|
if ([[QLPreviewPanel sharedPreviewPanel] dataSource] == self) {
|
||||||
|
[[QLPreviewPanel sharedPreviewPanel] reloadData];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPySelection
|
||||||
|
{
|
||||||
|
[super setPySelection];
|
||||||
|
[self updateQuicklookIfNeeded];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setViewSelection
|
||||||
|
{
|
||||||
|
[super setViewSelection];
|
||||||
|
[self updateQuicklookIfNeeded];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Public */
|
/* Public */
|
||||||
- (BOOL)powerMarkerMode
|
- (BOOL)powerMarkerMode
|
||||||
{
|
{
|
||||||
return [[self py] powerMarkerMode];
|
return [[self model] powerMarkerMode];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode
|
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode
|
||||||
{
|
{
|
||||||
[[self py] setPowerMarkerMode:aPowerMarkerMode];
|
[[self model] setPowerMarkerMode:aPowerMarkerMode];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)deltaValuesMode
|
- (BOOL)deltaValuesMode
|
||||||
{
|
{
|
||||||
return [[self py] deltaValuesMode];
|
return [[self model] deltaValuesMode];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode
|
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode
|
||||||
{
|
{
|
||||||
[[self py] setDeltaValuesMode:aDeltaValuesMode];
|
[[self model] setDeltaValuesMode:aDeltaValuesMode];
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setDeltaColumns:(NSIndexSet *)aDeltaColumns
|
|
||||||
{
|
|
||||||
[_deltaColumns release];
|
|
||||||
_deltaColumns = [aDeltaColumns retain];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSInteger)selectedDupeCount
|
|
||||||
{
|
|
||||||
return [[self py] selectedDupeCount];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeSelected
|
|
||||||
{
|
|
||||||
NSInteger selectedDupeCount = [self selectedDupeCount];
|
|
||||||
if (!selectedDupeCount)
|
|
||||||
return;
|
|
||||||
NSString *msgFmt = TR(@"FileRemovalConfirmMsg");
|
|
||||||
NSString *msg = [NSString stringWithFormat:msgFmt,selectedDupeCount];
|
|
||||||
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[[self py] removeSelected];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Datasource */
|
/* Datasource */
|
||||||
@@ -80,28 +74,27 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
{
|
{
|
||||||
NSString *identifier = [column identifier];
|
NSString *identifier = [column identifier];
|
||||||
if ([identifier isEqual:@"marked"]) {
|
if ([identifier isEqual:@"marked"]) {
|
||||||
return [[self py] valueForColumn:@"marked" row:row];
|
return [[self model] valueForColumn:@"marked" row:row];
|
||||||
}
|
}
|
||||||
NSInteger columnId = [identifier integerValue];
|
return [[self model] valueForRow:row column:identifier];
|
||||||
return [[self py] valueForRow:row column:columnId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
{
|
{
|
||||||
NSString *identifier = [column identifier];
|
NSString *identifier = [column identifier];
|
||||||
if ([identifier isEqual:@"marked"]) {
|
if ([identifier isEqual:@"marked"]) {
|
||||||
[[self py] setValue:object forColumn:identifier row:row];
|
[[self model] setValue:object forColumn:identifier row:row];
|
||||||
}
|
}
|
||||||
else if ([identifier isEqual:@"0"]) {
|
else if ([identifier isEqual:@"name"]) {
|
||||||
NSString *oldName = [[self py] valueForRow:row column:0];
|
NSString *oldName = [[self model] valueForRow:row column:identifier];
|
||||||
NSString *newName = object;
|
NSString *newName = object;
|
||||||
if (![newName isEqual:oldName]) {
|
if (![newName isEqual:oldName]) {
|
||||||
BOOL renamed = [[self py] renameSelected:newName];
|
BOOL renamed = [[self model] renameSelected:newName];
|
||||||
if (!renamed) {
|
if (!renamed) {
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:TR(@"FilenameAlreadyExistsMsg"), newName]];
|
[Dialogs showMessage:[NSString stringWithFormat:NSLocalizedString(@"The name '%@' already exists.", @""), newName]];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
[tableView setNeedsDisplay:YES];
|
[[self view] setNeedsDisplay:YES];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,15 +103,16 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
/* Delegate */
|
/* Delegate */
|
||||||
- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)tableColumn
|
- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)tableColumn
|
||||||
{
|
{
|
||||||
if ([[tableView sortDescriptors] count] < 1)
|
if ([[[self view] sortDescriptors] count] < 1)
|
||||||
return;
|
return;
|
||||||
NSSortDescriptor *sd = [[tableView sortDescriptors] objectAtIndex:0];
|
NSSortDescriptor *sd = [[[self view] sortDescriptors] objectAtIndex:0];
|
||||||
[[self py] sortBy:[[sd key] integerValue] ascending:[sd ascending]];
|
[[self model] sortBy:[sd key] ascending:[sd ascending]];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
{
|
{
|
||||||
BOOL isMarkable = n2b([[self py] valueForColumn:@"markable" row:row]);
|
BOOL isSelected = [[self view] isRowSelected:row];
|
||||||
|
BOOL isMarkable = n2b([[self model] valueForColumn:@"markable" row:row]);
|
||||||
if ([[column identifier] isEqual:@"marked"]) {
|
if ([[column identifier] isEqual:@"marked"]) {
|
||||||
[cell setEnabled:isMarkable];
|
[cell setEnabled:isMarkable];
|
||||||
// Low-tech solution, for indentation, but it works...
|
// Low-tech solution, for indentation, but it works...
|
||||||
@@ -126,38 +120,61 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
[cell setImagePosition:pos];
|
[cell setImagePosition:pos];
|
||||||
}
|
}
|
||||||
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
||||||
// Determine if the text color will be blue due to directory being reference.
|
NSColor *color = [NSColor textColor];
|
||||||
NSTextFieldCell *textCell = cell;
|
if (isSelected) {
|
||||||
if (isMarkable) {
|
color = [NSColor selectedTextColor];
|
||||||
[textCell setTextColor:[NSColor blackColor]];
|
|
||||||
if ([self deltaValuesMode]) {
|
|
||||||
NSInteger i = [[column identifier] integerValue];
|
|
||||||
if ([_deltaColumns containsIndex:i]) {
|
|
||||||
[textCell setTextColor:[NSColor orangeColor]];
|
|
||||||
}
|
}
|
||||||
|
else if (isMarkable) {
|
||||||
|
if ([[self model] isDeltaAtRow:row column:[column identifier]]) {
|
||||||
|
color = [NSColor orangeColor];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
[textCell setTextColor:[NSColor blueColor]];
|
color = [NSColor blueColor];
|
||||||
}
|
}
|
||||||
|
[(NSTextFieldCell *)cell setTextColor:color];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)tableViewHadDeletePressed:(NSTableView *)tableView
|
- (BOOL)tableViewHadDeletePressed:(NSTableView *)tableView
|
||||||
{
|
{
|
||||||
[self removeSelected];
|
[[self model] removeSelected];
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL)tableViewHadSpacePressed:(NSTableView *)tableView
|
- (BOOL)tableViewHadSpacePressed:(NSTableView *)tableView
|
||||||
{
|
{
|
||||||
[[self py] markSelected];
|
[[self model] markSelected];
|
||||||
return YES;
|
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 */
|
/* Python --> Cocoa */
|
||||||
- (void)invalidateMarkings
|
- (void)invalidateMarkings
|
||||||
{
|
{
|
||||||
[tableView setNeedsDisplay:YES];
|
[[self view] setNeedsDisplay:YES];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 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 "StatsLabel.h"
|
|
||||||
#import "ResultTable.h"
|
|
||||||
#import "ProblemDialog.h"
|
|
||||||
#import "HSTableView.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@class AppDelegateBase;
|
|
||||||
|
|
||||||
@interface ResultWindowBase : NSWindowController
|
|
||||||
{
|
|
||||||
@protected
|
|
||||||
IBOutlet NSSegmentedControl *optionsSwitch;
|
|
||||||
IBOutlet HSTableView *matches;
|
|
||||||
IBOutlet NSTextField *stats;
|
|
||||||
IBOutlet NSSearchField *filterField;
|
|
||||||
|
|
||||||
AppDelegateBase *app;
|
|
||||||
PyDupeGuruBase *py;
|
|
||||||
NSMenu *columnsMenu;
|
|
||||||
NSMutableArray *_resultColumns;
|
|
||||||
ResultTable *table;
|
|
||||||
StatsLabel *statsLabel;
|
|
||||||
ProblemDialog *problemDialog;
|
|
||||||
}
|
|
||||||
- (id)initWithParentApp:(AppDelegateBase *)app;
|
|
||||||
|
|
||||||
/* Virtual */
|
|
||||||
- (void)initResultColumns;
|
|
||||||
- (void)setScanOptions;
|
|
||||||
- (NSString *)getScanErrorMessageForCode:(NSInteger)errorCode;
|
|
||||||
|
|
||||||
/* Helpers */
|
|
||||||
- (void)fillColumnsMenu;
|
|
||||||
- (NSTableColumn *)getColumnForIdentifier:(NSInteger)aIdentifier title:(NSString *)aTitle width:(NSInteger)aWidth refCol:(NSTableColumn *)aColumn;
|
|
||||||
- (NSArray *)getColumnsOrder;
|
|
||||||
- (NSDictionary *)getColumnsWidth;
|
|
||||||
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
|
|
||||||
- (void)sendMarkedToTrash:(BOOL)hardlinkDeleted;
|
|
||||||
- (void)updateOptionSegments;
|
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
- (IBAction)clearIgnoreList:(id)sender;
|
|
||||||
- (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)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;
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)jobCompleted:(NSNotification *)aNotification;
|
|
||||||
@end
|
|
||||||
@@ -1,479 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 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"
|
|
||||||
|
|
||||||
@implementation ResultWindowBase
|
|
||||||
- (id)initWithParentApp:(AppDelegateBase *)aApp;
|
|
||||||
{
|
|
||||||
self = [super initWithWindowNibName:@"ResultWindow"];
|
|
||||||
app = aApp;
|
|
||||||
py = [app py];
|
|
||||||
columnsMenu = [app columnsMenu];
|
|
||||||
/* Put a cute iTunes-like bottom bar */
|
|
||||||
[[self window] setContentBorderThickness:28 forEdge:NSMinYEdge];
|
|
||||||
table = [[ResultTable alloc] initWithPyParent:py view:matches];
|
|
||||||
statsLabel = [[StatsLabel alloc] initWithPyParent:py labelView:stats];
|
|
||||||
problemDialog = [[ProblemDialog alloc] initWithPy:py];
|
|
||||||
[self initResultColumns];
|
|
||||||
[self fillColumnsMenu];
|
|
||||||
[matches setTarget:self];
|
|
||||||
[matches setDoubleAction:@selector(openClicked:)];
|
|
||||||
[table setDeltaColumns:[Utils array2IndexSet:[py deltaColumns]]];
|
|
||||||
|
|
||||||
[[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];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[table release];
|
|
||||||
[statsLabel release];
|
|
||||||
[problemDialog release];
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Virtual */
|
|
||||||
- (void)initResultColumns
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setScanOptions
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)getScanErrorMessageForCode:(NSInteger)errorCode
|
|
||||||
{
|
|
||||||
if (errorCode == 0) {
|
|
||||||
return nil;
|
|
||||||
}
|
|
||||||
if (errorCode == 3) {
|
|
||||||
return TR(@"NoScannableFileMsg");
|
|
||||||
}
|
|
||||||
return TR(@"UnknownErrorMsg");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Helpers */
|
|
||||||
- (void)fillColumnsMenu
|
|
||||||
{
|
|
||||||
// The columns menu is supposed to be empty and initResultColumns must have been called
|
|
||||||
for (NSTableColumn *col in _resultColumns)
|
|
||||||
{
|
|
||||||
NSMenuItem *mi = [columnsMenu addItemWithTitle:[[col headerCell] stringValue] action:@selector(toggleColumn:) keyEquivalent:@""];
|
|
||||||
[mi setTag:[[col identifier] integerValue]];
|
|
||||||
[mi setTarget:self];
|
|
||||||
if ([[matches tableColumns] containsObject:col])
|
|
||||||
[mi setState:NSOnState];
|
|
||||||
}
|
|
||||||
[columnsMenu addItem:[NSMenuItem separatorItem]];
|
|
||||||
NSMenuItem *mi = [columnsMenu addItemWithTitle:TR(@"Reset to Default")
|
|
||||||
action:@selector(resetColumnsToDefault:) keyEquivalent:@""];
|
|
||||||
[mi setTarget:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSTableColumn *)getColumnForIdentifier:(NSInteger)aIdentifier title:(NSString *)aTitle width:(NSInteger)aWidth refCol:(NSTableColumn *)aColumn
|
|
||||||
{
|
|
||||||
NSNumber *n = [NSNumber numberWithInteger:aIdentifier];
|
|
||||||
NSTableColumn *col = [[NSTableColumn alloc] initWithIdentifier:[n stringValue]];
|
|
||||||
[col setWidth:aWidth];
|
|
||||||
[col setEditable:NO];
|
|
||||||
[[col dataCell] setFont:[[aColumn dataCell] font]];
|
|
||||||
[[col headerCell] setStringValue:aTitle];
|
|
||||||
[col setResizingMask:NSTableColumnUserResizingMask];
|
|
||||||
[col setSortDescriptorPrototype:[[NSSortDescriptor alloc] initWithKey:[n stringValue] ascending:YES]];
|
|
||||||
return col;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Returns an array of identifiers, in order.
|
|
||||||
- (NSArray *)getColumnsOrder
|
|
||||||
{
|
|
||||||
NSMutableArray *result = [NSMutableArray array];
|
|
||||||
for (NSTableColumn *col in [matches tableColumns]) {
|
|
||||||
NSString *colId = [col identifier];
|
|
||||||
[result addObject:colId];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary *)getColumnsWidth
|
|
||||||
{
|
|
||||||
NSMutableDictionary *result = [NSMutableDictionary dictionary];
|
|
||||||
for (NSTableColumn *col in [matches tableColumns]) {
|
|
||||||
NSString *colId = [col identifier];
|
|
||||||
NSNumber *width = [NSNumber numberWithDouble:[col width]];
|
|
||||||
[result setObject:width forKey:colId];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth
|
|
||||||
{
|
|
||||||
for (NSMenuItem *mi in [columnsMenu itemArray]) {
|
|
||||||
if ([mi state] == NSOnState) {
|
|
||||||
[self toggleColumn:mi];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//Add columns and set widths
|
|
||||||
for (NSString *colId in aColumnsOrder) {
|
|
||||||
NSInteger colIndex = [colId integerValue];
|
|
||||||
if ((colIndex == 0) && (![colId isEqual:@"0"])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
NSTableColumn *col = [_resultColumns objectAtIndex:colIndex];
|
|
||||||
NSNumber *width = [aColumnsWidth objectForKey:[col identifier]];
|
|
||||||
NSMenuItem *mi = [columnsMenu itemWithTag:colIndex];
|
|
||||||
if (width) {
|
|
||||||
[col setWidth:[width floatValue]];
|
|
||||||
}
|
|
||||||
[self toggleColumn:mi];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)sendMarkedToTrash:(BOOL)hardlinkDeleted
|
|
||||||
{
|
|
||||||
NSInteger mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NSString *msg = TR(@"SendToTrashConfirmMsg");
|
|
||||||
if (hardlinkDeleted) {
|
|
||||||
msg = TR(@"HardlinkConfirmMsg");
|
|
||||||
}
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:msg,mark_count]] == NSAlertSecondButtonReturn) { // NO
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
|
||||||
if (hardlinkDeleted) {
|
|
||||||
[py hardlinkMarked];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[py deleteMarked];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateOptionSegments
|
|
||||||
{
|
|
||||||
[optionsSwitch setSelected:[[app detailsPanel] isVisible] forSegment:0];
|
|
||||||
[optionsSwitch setSelected:[table powerMarkerMode] forSegment:1];
|
|
||||||
[optionsSwitch setSelected:[table deltaValuesMode] forSegment:2];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
- (IBAction)clearIgnoreList:(id)sender
|
|
||||||
{
|
|
||||||
NSInteger i = n2i([py getIgnoreListCount]);
|
|
||||||
if (!i)
|
|
||||||
return;
|
|
||||||
NSString *msg = [NSString stringWithFormat:TR(@"ClearIgnoreListConfirmMsg"),i];
|
|
||||||
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[py clearIgnoreList];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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
|
|
||||||
{
|
|
||||||
NSInteger 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:TR(@"SelectCopyDestinationMsg")];
|
|
||||||
if ([op runModal] == 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
|
|
||||||
{
|
|
||||||
[self sendMarkedToTrash:NO];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)hardlinkMarked:(id)sender
|
|
||||||
{
|
|
||||||
[self sendMarkedToTrash:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)exportToXHTML:(id)sender
|
|
||||||
{
|
|
||||||
NSString *exported = [py exportToXHTMLwithColumns:[self getColumnsOrder]];
|
|
||||||
[[NSWorkspace sharedWorkspace] openFile:exported];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)filter:(id)sender
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py setEscapeFilterRegexp:!n2b([ud objectForKey:@"useRegexpFilter"])];
|
|
||||||
[py applyFilter:[filterField stringValue]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)ignoreSelected:(id)sender
|
|
||||||
{
|
|
||||||
NSInteger selectedDupeCount = [table selectedDupeCount];
|
|
||||||
if (!selectedDupeCount)
|
|
||||||
return;
|
|
||||||
NSString *msg = [NSString stringWithFormat:TR(@"IgnoreConfirmMsg"),selectedDupeCount];
|
|
||||||
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[py addSelectedToIgnoreList];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)invokeCustomCommand:(id)sender
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
NSString *cmd = [ud stringForKey:@"CustomCommand"];
|
|
||||||
if ((cmd != nil) && ([cmd length] > 0)) {
|
|
||||||
[py invokeCommand:cmd];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[Dialogs showMessage:TR(@"NoCustomCommandMsg")];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markAll:(id)sender
|
|
||||||
{
|
|
||||||
[py markAll];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markInvert:(id)sender
|
|
||||||
{
|
|
||||||
[py markInvert];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markNone:(id)sender
|
|
||||||
{
|
|
||||||
[py markNone];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markSelected:(id)sender
|
|
||||||
{
|
|
||||||
[py toggleSelectedMark];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)moveMarked:(id)sender
|
|
||||||
{
|
|
||||||
NSInteger 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:TR(@"SelectMoveDestinationMsg")];
|
|
||||||
if ([op runModal] == NSOKButton) {
|
|
||||||
NSString *directory = [[op filenames] objectAtIndex:0];
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
|
||||||
[py copyOrMove:b2n(NO) markedTo:directory recreatePath:[ud objectForKey:@"recreatePathType"]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)openClicked:(id)sender
|
|
||||||
{
|
|
||||||
if ([matches clickedRow] < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[matches selectRowIndexes:[NSIndexSet indexSetWithIndex:[matches clickedRow]] byExtendingSelection:NO];
|
|
||||||
[py openSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)openSelected:(id)sender
|
|
||||||
{
|
|
||||||
[py openSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)removeMarked:(id)sender
|
|
||||||
{
|
|
||||||
int mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count)
|
|
||||||
return;
|
|
||||||
NSString *msg = [NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",mark_count];
|
|
||||||
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[py removeMarked];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)removeSelected:(id)sender
|
|
||||||
{
|
|
||||||
[table 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)resetColumnsToDefault:(id)sender
|
|
||||||
{
|
|
||||||
// Virtual
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)revealSelected:(id)sender
|
|
||||||
{
|
|
||||||
[py revealSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)saveResults:(id)sender
|
|
||||||
{
|
|
||||||
NSSavePanel *sp = [NSSavePanel savePanel];
|
|
||||||
[sp setCanCreateDirectories:YES];
|
|
||||||
[sp setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
|
|
||||||
[sp setTitle:TR(@"SelectResultToSaveMsg")];
|
|
||||||
if ([sp runModal] == NSOKButton) {
|
|
||||||
[py saveResultsAs:[sp filename]];
|
|
||||||
[[app recentResults] addFile:[sp filename]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)startDuplicateScan:(id)sender
|
|
||||||
{
|
|
||||||
if ([py resultsAreModified]) {
|
|
||||||
if ([Dialogs askYesNo:TR(@"ReallyWantToContinueMsg")] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[self setScanOptions];
|
|
||||||
NSInteger r = n2i([py doScan]);
|
|
||||||
NSString *errorMsg = [self getScanErrorMessageForCode:r];
|
|
||||||
if (errorMsg != nil) {
|
|
||||||
[[ProgressController mainProgressController] hide];
|
|
||||||
[Dialogs showMessage:errorMsg];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)switchSelected:(id)sender
|
|
||||||
{
|
|
||||||
[py makeSelectedReference];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleColumn:(id)sender
|
|
||||||
{
|
|
||||||
NSMenuItem *mi = sender;
|
|
||||||
NSString *colId = [NSString stringWithFormat:@"%d",[mi tag]];
|
|
||||||
NSTableColumn *col = [matches tableColumnWithIdentifier:colId];
|
|
||||||
if (col == nil) {
|
|
||||||
//Add Column
|
|
||||||
col = [_resultColumns objectAtIndex:[mi tag]];
|
|
||||||
[matches addTableColumn:col];
|
|
||||||
[mi setState:NSOnState];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//Remove column
|
|
||||||
[matches removeTableColumn:col];
|
|
||||||
[mi setState: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];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)jobCompleted:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
id lastAction = [[ProgressController mainProgressController] jobId];
|
|
||||||
if ([lastAction isEqualTo:jobCopy]) {
|
|
||||||
if ([py scanWasProblematic]) {
|
|
||||||
[problemDialog showWindow:self];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[Dialogs showMessage:TR(@"CopySuccessMsg")];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ([lastAction isEqualTo:jobMove]) {
|
|
||||||
if ([py scanWasProblematic]) {
|
|
||||||
[problemDialog showWindow:self];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[Dialogs showMessage:TR(@"MoveSuccessMsg")];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ([lastAction isEqualTo:jobDelete]) {
|
|
||||||
if ([py scanWasProblematic]) {
|
|
||||||
[problemDialog showWindow:self];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[Dialogs showMessage:TR(@"SendToTrashSuccessMsg")];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ([lastAction isEqualTo:jobScan]) {
|
|
||||||
NSInteger rowCount = [[table py] numberOfRows];
|
|
||||||
if (rowCount == 0) {
|
|
||||||
[Dialogs showMessage:TR(@"NoDuplicateFoundMsg")];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)jobInProgress:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[Dialogs showMessage:TR(@"TaskHangingMsg")];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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
|
|
||||||
85
cocoa/base/ResultWindowBase.h
Normal file
85
cocoa/base/ResultWindowBase.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 "DeletionOptions.h"
|
||||||
|
#import "HSTableView.h"
|
||||||
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
|
@class AppDelegateBase;
|
||||||
|
|
||||||
|
@interface ResultWindowBase : NSWindowController
|
||||||
|
{
|
||||||
|
@protected
|
||||||
|
NSSegmentedControl *optionsSwitch;
|
||||||
|
NSToolbarItem *optionsToolbarItem;
|
||||||
|
HSTableView *matches;
|
||||||
|
NSTextField *stats;
|
||||||
|
NSSearchField *filterField;
|
||||||
|
|
||||||
|
AppDelegateBase *app;
|
||||||
|
PyDupeGuru *model;
|
||||||
|
ResultTable *table;
|
||||||
|
StatsLabel *statsLabel;
|
||||||
|
ProblemDialog *problemDialog;
|
||||||
|
DeletionOptions *deletionOptions;
|
||||||
|
QLPreviewPanel* previewPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (readwrite, retain) NSSegmentedControl *optionsSwitch;
|
||||||
|
@property (readwrite, retain) NSToolbarItem *optionsToolbarItem;
|
||||||
|
@property (readwrite, retain) HSTableView *matches;
|
||||||
|
@property (readwrite, retain) NSTextField *stats;
|
||||||
|
@property (readwrite, retain) NSSearchField *filterField;
|
||||||
|
|
||||||
|
- (id)initWithParentApp:(AppDelegateBase *)app;
|
||||||
|
|
||||||
|
/* Virtual */
|
||||||
|
- (void)initResultColumns;
|
||||||
|
- (void)setScanOptions;
|
||||||
|
|
||||||
|
/* Helpers */
|
||||||
|
- (void)fillColumnsMenu;
|
||||||
|
- (void)updateOptionSegments;
|
||||||
|
- (void)showProblemDialog;
|
||||||
|
- (void)adjustUIToLocalization;
|
||||||
|
|
||||||
|
/* Actions */
|
||||||
|
- (void)changeOptions;
|
||||||
|
- (void)copyMarked;
|
||||||
|
- (void)trashMarked;
|
||||||
|
- (void)filter;
|
||||||
|
- (void)focusOnFilterField;
|
||||||
|
- (void)ignoreSelected;
|
||||||
|
- (void)invokeCustomCommand;
|
||||||
|
- (void)markAll;
|
||||||
|
- (void)markInvert;
|
||||||
|
- (void)markNone;
|
||||||
|
- (void)markSelected;
|
||||||
|
- (void)moveMarked;
|
||||||
|
- (void)openClicked;
|
||||||
|
- (void)openSelected;
|
||||||
|
- (void)removeMarked;
|
||||||
|
- (void)removeSelected;
|
||||||
|
- (void)renameSelected;
|
||||||
|
- (void)reprioritizeResults;
|
||||||
|
- (void)resetColumnsToDefault;
|
||||||
|
- (void)revealSelected;
|
||||||
|
- (void)saveResults;
|
||||||
|
- (void)startDuplicateScan;
|
||||||
|
- (void)switchSelected;
|
||||||
|
- (void)toggleColumn:(id)sender;
|
||||||
|
- (void)toggleDelta;
|
||||||
|
- (void)toggleDetailsPanel;
|
||||||
|
- (void)togglePowerMarker;
|
||||||
|
- (void)toggleQuicklookPanel;
|
||||||
|
@end
|
||||||
352
cocoa/base/ResultWindowBase.m
Normal file
352
cocoa/base/ResultWindowBase.m
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 "ResultWindowBase.h"
|
||||||
|
#import "ResultWindow_UI.h"
|
||||||
|
#import "Dialogs.h"
|
||||||
|
#import "ProgressController.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "Consts.h"
|
||||||
|
#import "PrioritizeDialog.h"
|
||||||
|
|
||||||
|
@implementation ResultWindowBase
|
||||||
|
|
||||||
|
@synthesize optionsSwitch;
|
||||||
|
@synthesize optionsToolbarItem;
|
||||||
|
@synthesize matches;
|
||||||
|
@synthesize stats;
|
||||||
|
@synthesize filterField;
|
||||||
|
|
||||||
|
- (id)initWithParentApp:(AppDelegateBase *)aApp;
|
||||||
|
{
|
||||||
|
self = [super initWithWindow:nil];
|
||||||
|
app = aApp;
|
||||||
|
model = [app model];
|
||||||
|
[self setWindow:createResultWindow_UI(self)];
|
||||||
|
[[self window] setTitle:fmt(NSLocalizedString(@"%@ Results", @""), [model appName])];
|
||||||
|
/* 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]];
|
||||||
|
deletionOptions = [[DeletionOptions alloc] initWithPyRef:[model deletionOptions]];
|
||||||
|
[self initResultColumns];
|
||||||
|
[[table columns] setColumnsAsReadOnly];
|
||||||
|
[self fillColumnsMenu];
|
||||||
|
[matches setTarget:self];
|
||||||
|
[matches setDoubleAction:@selector(openClicked)];
|
||||||
|
[self adjustUIToLocalization];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[table release];
|
||||||
|
[statsLabel release];
|
||||||
|
[problemDialog release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Virtual */
|
||||||
|
- (void)initResultColumns
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setScanOptions
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Helpers */
|
||||||
|
- (void)fillColumnsMenu
|
||||||
|
{
|
||||||
|
[[app columnsMenu] removeAllItems];
|
||||||
|
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 = [[app columnsMenu] addItemWithTitle:display action:@selector(toggleColumn:) keyEquivalent:@""];
|
||||||
|
[mi setTarget:self];
|
||||||
|
[mi setState:marked ? NSOnState : NSOffState];
|
||||||
|
[mi setTag:i];
|
||||||
|
}
|
||||||
|
[[app columnsMenu] addItem:[NSMenuItem separatorItem]];
|
||||||
|
NSMenuItem *mi = [[app columnsMenu] addItemWithTitle:NSLocalizedString(@"Reset to Default", @"")
|
||||||
|
action:@selector(resetColumnsToDefault) keyEquivalent:@""];
|
||||||
|
[mi setTarget:self];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (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 */
|
||||||
|
- (void)changeOptions
|
||||||
|
{
|
||||||
|
NSInteger seg = [optionsSwitch selectedSegment];
|
||||||
|
if (seg == 0) {
|
||||||
|
[self toggleDetailsPanel];
|
||||||
|
}
|
||||||
|
else if (seg == 1) {
|
||||||
|
[self togglePowerMarker];
|
||||||
|
}
|
||||||
|
else if (seg == 2) {
|
||||||
|
[self toggleDelta];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)copyMarked
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
||||||
|
[model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])];
|
||||||
|
[model copyMarked];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)trashMarked
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
||||||
|
[model deleteMarked];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)filter
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[model setEscapeFilterRegexp:!n2b([ud objectForKey:@"useRegexpFilter"])];
|
||||||
|
[model applyFilter:[filterField stringValue]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)focusOnFilterField
|
||||||
|
{
|
||||||
|
[[self window] makeFirstResponder:filterField];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)ignoreSelected
|
||||||
|
{
|
||||||
|
[model addSelectedToIgnoreList];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)invokeCustomCommand
|
||||||
|
{
|
||||||
|
[model invokeCustomCommand];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)markAll
|
||||||
|
{
|
||||||
|
[model markAll];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)markInvert
|
||||||
|
{
|
||||||
|
[model markInvert];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)markNone
|
||||||
|
{
|
||||||
|
[model markNone];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)markSelected
|
||||||
|
{
|
||||||
|
[model toggleSelectedMark];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)moveMarked
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
||||||
|
[model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])];
|
||||||
|
[model moveMarked];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openClicked
|
||||||
|
{
|
||||||
|
if ([matches clickedRow] < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[matches selectRowIndexes:[NSIndexSet indexSetWithIndex:[matches clickedRow]] byExtendingSelection:NO];
|
||||||
|
[model openSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)openSelected
|
||||||
|
{
|
||||||
|
[model openSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)removeMarked
|
||||||
|
{
|
||||||
|
[model removeMarked];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)removeSelected
|
||||||
|
{
|
||||||
|
[model removeSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)renameSelected
|
||||||
|
{
|
||||||
|
NSInteger col = [matches columnWithIdentifier:@"name"];
|
||||||
|
NSInteger row = [matches selectedRow];
|
||||||
|
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)reprioritizeResults
|
||||||
|
{
|
||||||
|
PrioritizeDialog *dlg = [[PrioritizeDialog alloc] initWithApp:model];
|
||||||
|
NSInteger result = [NSApp runModalForWindow:[dlg window]];
|
||||||
|
if (result == NSRunStoppedResponse) {
|
||||||
|
[[dlg model] performReprioritization];
|
||||||
|
}
|
||||||
|
[dlg release];
|
||||||
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)resetColumnsToDefault
|
||||||
|
{
|
||||||
|
[[[table columns] model] resetToDefaults];
|
||||||
|
[self fillColumnsMenu];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)revealSelected
|
||||||
|
{
|
||||||
|
[model revealSelected];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)saveResults
|
||||||
|
{
|
||||||
|
NSSavePanel *sp = [NSSavePanel savePanel];
|
||||||
|
[sp setCanCreateDirectories:YES];
|
||||||
|
[sp setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
|
||||||
|
[sp setTitle:NSLocalizedString(@"Select a file to save your results to", @"")];
|
||||||
|
if ([sp runModal] == NSOKButton) {
|
||||||
|
[model saveResultsAs:[[sp URL] path]];
|
||||||
|
[[app recentResults] addFile:[[sp URL] path]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)startDuplicateScan
|
||||||
|
{
|
||||||
|
if ([model resultsAreModified]) {
|
||||||
|
if ([Dialogs askYesNo:NSLocalizedString(@"You have unsaved results, do you really want to continue?", @"")] == NSAlertSecondButtonReturn) // NO
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[self setScanOptions];
|
||||||
|
[model doScan];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)switchSelected
|
||||||
|
{
|
||||||
|
[model makeSelectedReference];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)toggleColumn:(id)sender
|
||||||
|
{
|
||||||
|
NSMenuItem *mi = sender;
|
||||||
|
BOOL checked = [[[table columns] model] toggleMenuItem:[mi tag]];
|
||||||
|
[mi setState:checked ? NSOnState : NSOffState];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)toggleDetailsPanel
|
||||||
|
{
|
||||||
|
[[app detailsPanel] toggleVisibility];
|
||||||
|
[self updateOptionSegments];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)toggleDelta
|
||||||
|
{
|
||||||
|
[table setDeltaValuesMode:![table deltaValuesMode]];
|
||||||
|
[self updateOptionSegments];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)togglePowerMarker
|
||||||
|
{
|
||||||
|
[table setPowerMarkerMode:![table powerMarkerMode]];
|
||||||
|
[self updateOptionSegments];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)toggleQuicklookPanel
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
|
||||||
|
{
|
||||||
|
return ![[ProgressController mainProgressController] isShown];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)validateMenuItem:(NSMenuItem *)item
|
||||||
|
{
|
||||||
|
if ([item action] == @selector(markAll)) {
|
||||||
|
[item setTitle:NSLocalizedString(@"Mark All", @"")];
|
||||||
|
}
|
||||||
|
return ![[ProgressController mainProgressController] isShown];
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -10,10 +10,8 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
#import "HSGUIController.h"
|
#import "HSGUIController.h"
|
||||||
#import "PyStatsLabel.h"
|
#import "PyStatsLabel.h"
|
||||||
|
|
||||||
@interface StatsLabel : HSGUIController
|
@interface StatsLabel : HSGUIController {}
|
||||||
{
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aLabelView;
|
||||||
NSTextField *labelView;
|
- (PyStatsLabel *)model;
|
||||||
}
|
- (NSTextField *)labelView;
|
||||||
- (id)initWithPyParent:(id)aPyParent labelView:(NSTextField *)aLabelView;
|
|
||||||
- (PyStatsLabel *)py;
|
|
||||||
@end
|
@end
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -10,29 +10,25 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
#import "Utils.h"
|
#import "Utils.h"
|
||||||
|
|
||||||
@implementation StatsLabel
|
@implementation StatsLabel
|
||||||
- (id)initWithPyParent:(id)aPyParent labelView:(NSTextField *)aLabelView
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aLabelView
|
||||||
{
|
{
|
||||||
self = [super initWithPyClassName:@"PyStatsLabel" pyParent:aPyParent];
|
return [super initWithPyRef:aPyRef wrapperClass:[PyStatsLabel class]
|
||||||
labelView = [aLabelView retain];
|
callbackClassName:@"StatsLabelView" view:aLabelView];
|
||||||
[self connect];
|
|
||||||
return self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)dealloc
|
- (PyStatsLabel *)model
|
||||||
{
|
{
|
||||||
[self disconnect];
|
return (PyStatsLabel *)model;
|
||||||
[labelView release];
|
|
||||||
[super dealloc];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (PyStatsLabel *)py
|
- (NSTextField *)labelView
|
||||||
{
|
{
|
||||||
return (PyStatsLabel *)py;
|
return (NSTextField *)view;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Python --> Cocoa */
|
/* Python --> Cocoa */
|
||||||
- (void)refresh
|
- (void)refresh
|
||||||
{
|
{
|
||||||
[labelView setStringValue:[[self py] display]];
|
[[self labelView] setStringValue:[[self model] display]];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
/* 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";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
|||||||
/* 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";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,63 +1,151 @@
|
|||||||
"Add New Folder..." = "Add New Folder...";
|
|
||||||
"Load from file..." = "Load from file...";
|
|
||||||
"Reset to Default" = "Reset to Default";
|
|
||||||
|
|
||||||
"Add iTunes Directory" = "Add iTunes Directory";
|
|
||||||
"Remove Dead Tracks in iTunes" = "Remove Dead Tracks in iTunes";
|
|
||||||
|
|
||||||
|
"%@ Results" = "%@ Results";
|
||||||
|
"About dupeGuru" = "About dupeGuru";
|
||||||
|
"Action" = "Action";
|
||||||
|
"Actions" = "Actions";
|
||||||
|
"Add Aperture Library" = "Add Aperture Library";
|
||||||
|
"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." = "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.";
|
||||||
"Add iPhoto Library" = "Add iPhoto Library";
|
"Add iPhoto Library" = "Add iPhoto Library";
|
||||||
"Clear Picture Cache" = "Clear Picture Cache";
|
"Add iTunes Library" = "Add iTunes Library";
|
||||||
|
"Add New Folder..." = "Add New Folder...";
|
||||||
/* Columns */
|
"Add Selected to Ignore List" = "Add Selected to Ignore List";
|
||||||
"Folder" = "Folder";
|
"Advanced" = "Advanced";
|
||||||
"Size (KB)" = "Size (KB)";
|
"After having deleted a duplicate, place a link targeting the reference file to replace the deleted file." = "After having deleted a duplicate, place a link targeting the reference file to replace the deleted file.";
|
||||||
"Size (MB)" = "Size (MB)";
|
|
||||||
"Kind" = "Kind";
|
|
||||||
"Modification" = "Modification";
|
|
||||||
"Match %" = "Match %";
|
|
||||||
"Words Used" = "Words Used";
|
|
||||||
"Dupe Count" = "Dupe Count";
|
|
||||||
|
|
||||||
"Time" = "Time";
|
|
||||||
"Bitrate" = "Bitrate";
|
|
||||||
"Sample Rate" = "Sample Rate";
|
|
||||||
"Title" = "Title";
|
|
||||||
"Artist" = "Artist";
|
|
||||||
"Album" = "Album";
|
"Album" = "Album";
|
||||||
|
"Artist" = "Artist";
|
||||||
|
"Attribute" = "Attribute";
|
||||||
|
"Audio Content" = "Audio Content";
|
||||||
|
"Automatically check for updates" = "Automatically check for updates";
|
||||||
|
"Basic" = "Basic";
|
||||||
|
"Bring All to Front" = "Bring All to Front";
|
||||||
|
"Can mix file kind" = "Can mix file kind";
|
||||||
|
"Cancel" = "Cancel";
|
||||||
|
"Check for update..." = "Check for update...";
|
||||||
|
"Clear" = "Clear";
|
||||||
|
"Clear Picture Cache" = "Clear Picture Cache";
|
||||||
|
"Close" = "Close";
|
||||||
|
"Close Window" = "Close Window";
|
||||||
|
"Columns" = "Columns";
|
||||||
|
"Content" = "Content";
|
||||||
|
"Contents" = "Contents";
|
||||||
|
"Copy" = "Copy";
|
||||||
|
"Copy and Move:" = "Copy and Move:";
|
||||||
|
"Copy Marked to..." = "Copy Marked to...";
|
||||||
|
"Custom command (arguments: %d for dupe, %r for ref):" = "Custom command (arguments: %d for dupe, %r for ref):";
|
||||||
|
"Cut" = "Cut";
|
||||||
|
"Debug mode (restart required)" = "Debug mode (restart required)";
|
||||||
|
"Deletion Options" = "Deletion Options";
|
||||||
|
"Delta" = "Delta";
|
||||||
|
"Details" = "Details";
|
||||||
|
"Details of Selected File" = "Details of Selected File";
|
||||||
|
"Details Panel" = "Details Panel";
|
||||||
|
"Directly delete files" = "Directly delete files";
|
||||||
|
"Directories" = "Directories";
|
||||||
|
"Do you really want to remove all your cached picture analysis?" = "Do you really want to remove all your cached picture analysis?";
|
||||||
|
"dupeGuru" = "dupeGuru";
|
||||||
|
"dupeGuru Help" = "dupeGuru Help";
|
||||||
|
"dupeGuru ME Preferences" = "dupeGuru ME Preferences";
|
||||||
|
"dupeGuru PE Preferences" = "dupeGuru PE Preferences";
|
||||||
|
"dupeGuru Preferences" = "dupeGuru Preferences";
|
||||||
|
"dupeGuru Results" = "dupeGuru Results";
|
||||||
|
"dupeGuru Website" = "dupeGuru Website";
|
||||||
|
"Dupes Only" = "Dupes Only";
|
||||||
|
"Edit" = "Edit";
|
||||||
|
"Excluded" = "Excluded";
|
||||||
|
"EXIF Timestamp" = "EXIF Timestamp";
|
||||||
|
"Export Results to CSV" = "Export Results to CSV";
|
||||||
|
"Export Results to XHTML" = "Export Results to XHTML";
|
||||||
|
"Fewer results" = "Fewer results";
|
||||||
|
"File" = "File";
|
||||||
|
"Filename" = "Filename";
|
||||||
|
"Filename - Fields" = "Filename - Fields";
|
||||||
|
"Filename - Fields (No Order)" = "Filename - Fields (No Order)";
|
||||||
|
"Filter" = "Filter";
|
||||||
|
"Filter hardness:" = "Filter hardness:";
|
||||||
|
"Filter Results..." = "Filter Results...";
|
||||||
|
"Folder Selection Window" = "Folder Selection Window";
|
||||||
|
"Folders" = "Folders";
|
||||||
|
"Font Size:" = "Font Size:";
|
||||||
"Genre" = "Genre";
|
"Genre" = "Genre";
|
||||||
|
"Help" = "Help";
|
||||||
|
"Hide dupeGuru" = "Hide dupeGuru";
|
||||||
|
"Hide Others" = "Hide Others";
|
||||||
|
"Ignore duplicates hardlinking to the same file" = "Ignore duplicates hardlinking to the same file";
|
||||||
|
"Ignore files smaller than:" = "Ignore files smaller than:";
|
||||||
|
"Ignore List" = "Ignore List";
|
||||||
|
"Instead of sending files to trash, delete them directly. This option is usually used as a workaround when the normal deletion method doesn't work." = "Instead of sending files to trash, delete them directly. This option is usually used as a workaround when the normal deletion method doesn't work.";
|
||||||
|
"Invert Marking" = "Invert Marking";
|
||||||
|
"Invoke Custom Command" = "Invoke Custom Command";
|
||||||
|
"KB" = "KB";
|
||||||
|
"Link deleted files" = "Link deleted files";
|
||||||
|
"Load from file..." = "Load from file...";
|
||||||
|
"Load Recent Results" = "Load Recent Results";
|
||||||
|
"Load Results" = "Load Results";
|
||||||
|
"Load Results..." = "Load Results...";
|
||||||
|
"Make Selected into Reference" = "Make Selected into Reference";
|
||||||
|
"Mark All" = "Mark All";
|
||||||
|
"Mark None" = "Mark None";
|
||||||
|
"Mark Selected" = "Mark Selected";
|
||||||
|
"Match pictures of different dimensions" = "Match pictures of different dimensions";
|
||||||
|
"Match similar words" = "Match similar words";
|
||||||
|
"Minimize" = "Minimize";
|
||||||
|
"Mode" = "Mode";
|
||||||
|
"More results" = "More results";
|
||||||
|
"Move Marked to..." = "Move Marked to...";
|
||||||
|
"Name" = "Name";
|
||||||
|
"Normal" = "Normal";
|
||||||
|
"Ok" = "Ok";
|
||||||
|
"Open Selected with Default Application" = "Open Selected with Default Application";
|
||||||
|
"Options" = "Options";
|
||||||
|
"Paste" = "Paste";
|
||||||
|
"Preferences..." = "Preferences...";
|
||||||
|
"Problems!" = "Problems!";
|
||||||
|
"Proceed" = "Proceed";
|
||||||
|
"Quick Look" = "Quick Look";
|
||||||
|
"Quit dupeGuru" = "Quit dupeGuru";
|
||||||
|
"Re-Prioritize duplicates" = "Re-Prioritize duplicates";
|
||||||
|
"Re-Prioritize Results..." = "Re-Prioritize Results...";
|
||||||
|
"Recreate absolute path" = "Recreate absolute path";
|
||||||
|
"Recreate relative path" = "Recreate relative path";
|
||||||
|
"Reference" = "Reference";
|
||||||
|
"Remove Dead Tracks in iTunes" = "Remove Dead Tracks in iTunes";
|
||||||
|
"Remove empty folders on delete or move" = "Remove empty folders on delete or move";
|
||||||
|
"Remove Marked from Results" = "Remove Marked from Results";
|
||||||
|
"Remove Selected" = "Remove Selected";
|
||||||
|
"Remove Selected from Results" = "Remove Selected from Results";
|
||||||
|
"Rename Selected" = "Rename Selected";
|
||||||
|
"Reset to Default" = "Reset to Default";
|
||||||
|
"Reset To Defaults" = "Reset To Defaults";
|
||||||
|
"Results Window" = "Results Window";
|
||||||
|
"Reveal" = "Reveal";
|
||||||
|
"Reveal Selected in Finder" = "Reveal Selected in Finder";
|
||||||
|
"Right in destination" = "Right in destination";
|
||||||
|
"Save Results..." = "Save Results...";
|
||||||
|
"Scan" = "Scan";
|
||||||
|
"Scan Type:" = "Scan Type:";
|
||||||
|
"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";
|
||||||
|
"Select a results file to load" = "Select a results file to load";
|
||||||
|
"Select All" = "Select All";
|
||||||
|
"Select folders to scan and press \"Scan\"." = "Select folders to scan and press \"Scan\".";
|
||||||
|
"Selected" = "Selected";
|
||||||
|
"Send Marked to Trash..." = "Send Marked to Trash...";
|
||||||
|
"Services" = "Services";
|
||||||
|
"Show All" = "Show All";
|
||||||
|
"Show Delta Values" = "Show Delta Values";
|
||||||
|
"Show Dupes Only" = "Show Dupes Only";
|
||||||
|
"Start Duplicate Scan" = "Start Duplicate Scan";
|
||||||
|
"State" = "State";
|
||||||
|
"Tags" = "Tags";
|
||||||
|
"Tags to scan:" = "Tags to scan:";
|
||||||
|
"The name '%@' already exists." = "The name '%@' already exists.";
|
||||||
|
"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." = "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.";
|
||||||
|
"Title" = "Title";
|
||||||
|
"Track" = "Track";
|
||||||
|
"Use regular expressions when filtering" = "Use regular expressions when filtering";
|
||||||
|
"Window" = "Window";
|
||||||
|
"Word weighting" = "Word weighting";
|
||||||
"Year" = "Year";
|
"Year" = "Year";
|
||||||
"Track Number" = "Track Number";
|
"You have unsaved results, do you really want to continue?" = "You have unsaved results, do you really want to continue?";
|
||||||
"Comment" = "Comment";
|
"You have unsaved results, do you really want to quit?" = "You have unsaved results, do you really want to quit?";
|
||||||
|
"Zoom" = "Zoom";
|
||||||
"Dimensions" = "Dimensions";
|
|
||||||
|
|
||||||
/* Messages */
|
|
||||||
"SelectResultToLoadMsg" = "Select a results file to load";
|
|
||||||
"SelectCopyDestinationMsg" = "Select a directory to copy marked files to";
|
|
||||||
"SelectMoveDestinationMsg" = "Select a directory to move marked files to";
|
|
||||||
"SelectResultToSaveMsg" = "Select a file to save your results to";
|
|
||||||
"SelectFolderToAddMsg" = "Select a folder to add to the scanning list";
|
|
||||||
"ReallyWantToQuitMsg" = "You have unsaved results, do you really want to quit?";
|
|
||||||
"ReallyWantToContinueMsg" = "You have unsaved results, do you really want to continue?";
|
|
||||||
"FolderAlreadyInListMsg" = "'%@' already is in the list.";
|
|
||||||
"FolderDoesNotExistMsg" = "'%@' does not exist.";
|
|
||||||
"FileRemovalConfirmMsg" = "You are about to remove %d files from results. Continue?";
|
|
||||||
"FilenameAlreadyExistsMsg" = "The name '%@' already exists.";
|
|
||||||
"NoScannableFileMsg" = "The selected directories contain no scannable file.";
|
|
||||||
"UnknownErrorMsg" = "Unknown Error.";
|
|
||||||
"SendToTrashConfirmMsg" = "You are about to send %d files to Trash. Continue?";
|
|
||||||
"HardlinkConfirmMsg" = "You are about to send %d files to Trash (and hardlink them afterwards). Continue?";
|
|
||||||
"ClearIgnoreListConfirmMsg" = "Do you really want to remove all %d items from the ignore list?";
|
|
||||||
"IgnoreConfirmMsg" = "All selected %d matches are going to be ignored in all subsequent scans. Continue?";
|
|
||||||
"NoCustomCommandMsg" = "You have no custom command set up. Set it up in your preferences.";
|
|
||||||
"CopySuccessMsg" = "All marked files were copied sucessfully.";
|
|
||||||
"MoveSuccessMsg" = "All marked files were moved sucessfully.";
|
|
||||||
"SendToTrashSuccessMsg" = "All marked files were sucessfully sent to Trash.";
|
|
||||||
"NoDuplicateFoundMsg" = "No duplicates found.";
|
|
||||||
"TaskHangingMsg" = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again.";
|
|
||||||
|
|
||||||
"RemoveDeadTracksConfirmMsg" = "Your iTunes Library contains %d dead tracks ready to be removed. Continue?";
|
|
||||||
"NoDeadTrackMsg" = "You have no dead tracks in your iTunes Library";
|
|
||||||
|
|
||||||
"IPhotoAppNotFoundMsg" = "The iPhoto application couldn't be found.";
|
|
||||||
"ClearPictureCacheConfirmMsg" = "Do you really want to remove all your cached picture analysis?";
|
|
||||||
|
|||||||
@@ -1,174 +0,0 @@
|
|||||||
|
|
||||||
/* 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 = "Clear Ignore List"; ObjectID = "927"; */
|
|
||||||
"927.title" = "Clear Ignore List";
|
|
||||||
|
|
||||||
/* 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";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,18 +0,0 @@
|
|||||||
|
|
||||||
/* 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 = "NSTableColumn"; headerCell.title = "File Path"; ObjectID = "10"; */
|
|
||||||
"10.headerCell.title" = "File Path";
|
|
||||||
|
|
||||||
/* Class = "NSTableColumn"; headerCell.title = "Error Message"; ObjectID = "11"; */
|
|
||||||
"11.headerCell.title" = "Error Message";
|
|
||||||
|
|
||||||
/* Class = "NSButtonCell"; title = "Close"; ObjectID = "19"; */
|
|
||||||
"19.title" = "Close";
|
|
||||||
|
|
||||||
/* Class = "NSButtonCell"; title = "Reveal Selected"; ObjectID = "21"; */
|
|
||||||
"21.title" = "Reveal Selected";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,99 +0,0 @@
|
|||||||
|
|
||||||
/* 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 = "NSTableColumn"; headerCell.title = "Name"; ObjectID = "11"; */
|
|
||||||
"11.headerCell.title" = "Name";
|
|
||||||
|
|
||||||
/* 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.labels[0] = "Details"; ObjectID = "44"; */
|
|
||||||
"44.labels[0]" = "Details";
|
|
||||||
|
|
||||||
/* Class = "NSSegmentedCell"; 44.labels[1] = "Dupes Only"; ObjectID = "44"; */
|
|
||||||
"44.labels[1]" = "Dupes Only";
|
|
||||||
|
|
||||||
/* Class = "NSSegmentedCell"; 44.labels[2] = "Delta"; ObjectID = "44"; */
|
|
||||||
"44.labels[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";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +0,0 @@
|
|||||||
"Collecting files to scan" = "Collecting files to scan";
|
|
||||||
"%s (%d discarded)" = "%s (%d discarded)";
|
|
||||||
"Scanning for duplicates" = "Scanning for duplicates";
|
|
||||||
"Loading" = "Loading";
|
|
||||||
"Moving" = "Moving";
|
|
||||||
"Copying" = "Copying";
|
|
||||||
"Sending to Trash" = "Sending to Trash";
|
|
||||||
"0 matches found" = "0 matches found";
|
|
||||||
"%d matches found" = "%d matches found";
|
|
||||||
"Read size of %d/%d files" = "Read size of %d/%d files";
|
|
||||||
"Grouped %d/%d matches" = "Grouped %d/%d matches";
|
|
||||||
"%d / %d (%s / %s) duplicates marked." = "%d / %d (%s / %s) duplicates marked.";
|
|
||||||
" filter: %s" = " filter: %s";
|
|
||||||
"Read size of %d/%d files" = "Read size of %d/%d files";
|
|
||||||
"Read metadata of %d/%d files" = "Read metadata of %d/%d files";
|
|
||||||
"Removing false matches" = "Removing false matches";
|
|
||||||
"Processed %d/%d matches against the ignore list" = "Processed %d/%d matches against the ignore list";
|
|
||||||
"Doing group prioritization" = "Doing group prioritization";
|
|
||||||
|
|
||||||
"Analyzed %d/%d pictures" = "Analyzed %d/%d pictures";
|
|
||||||
"Preparing for matching" = "Preparing for matching";
|
|
||||||
"Matched %d/%d pictures" = "Matched %d/%d pictures";
|
|
||||||
"Verified %d/%d matches" = "Verified %d/%d matches";
|
|
||||||
|
|
||||||
"Removing dead tracks from your iTunes Library" = "Removing dead tracks from your iTunes Library";
|
|
||||||
"Scanning the iTunes Library" = "Scanning the iTunes Library";
|
|
||||||
|
|
||||||
"Probing iPhoto. Don't touch it during the operation!" = "Probing iPhoto. Don't touch it during the operation!";
|
|
||||||
"Sending dupes to the Trash" = "Sending dupes to the Trash";
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
/* Class = "NSPanel"; title = "Details of Selected File"; ObjectID = "5"; */
|
|
||||||
"5.title" = "Détails du fichier sélectionné";
|
|
||||||
|
|
||||||
/* Class = "NSTableColumn"; headerCell.title = "Selected"; ObjectID = "9"; */
|
|
||||||
"9.headerCell.title" = "Sélectionné";
|
|
||||||
|
|
||||||
/* Class = "NSTableColumn"; headerCell.title = "Reference"; ObjectID = "10"; */
|
|
||||||
"10.headerCell.title" = "Référence";
|
|
||||||
|
|
||||||
/* Class = "NSTableColumn"; headerCell.title = "Attribute"; ObjectID = "11"; */
|
|
||||||
"11.headerCell.title" = "Attribut";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
|||||||
/* Class = "NSTableColumn"; headerCell.title = "State"; ObjectID = "13"; */
|
|
||||||
"13.headerCell.title" = "Type";
|
|
||||||
|
|
||||||
/* Class = "NSTableColumn"; headerCell.title = "Name"; ObjectID = "15"; */
|
|
||||||
"15.headerCell.title" = "Nom";
|
|
||||||
|
|
||||||
/* 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" = "Référence";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Excluded"; ObjectID = "57"; */
|
|
||||||
"57.title" = "Exclu";
|
|
||||||
|
|
||||||
/* Class = "NSTextFieldCell"; title = "Select folders to scan and press \"Scan\"."; ObjectID = "71"; */
|
|
||||||
"71.title" = "Sélectionnez les dossiers à scanner et cliquez sur Scan.";
|
|
||||||
|
|
||||||
/* Class = "NSButtonCell"; title = "Load Results"; ObjectID = "73"; */
|
|
||||||
"73.title" = "Charger";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,63 +0,0 @@
|
|||||||
"Add New Folder..." = "Ajouter dossier...";
|
|
||||||
"Load from file..." = "Charger un fichier...";
|
|
||||||
"Reset to Default" = "Colonnes par défault";
|
|
||||||
|
|
||||||
"Add iTunes Directory" = "Ajouter librairie iTunes";
|
|
||||||
"Remove Dead Tracks in iTunes" = "Retirer les tracks mortes dans iTunes";
|
|
||||||
|
|
||||||
"Add iPhoto Library" = "Ajouter librairie iPhoto";
|
|
||||||
"Clear Picture Cache" = "Vider la cache d'images";
|
|
||||||
|
|
||||||
/* Columns */
|
|
||||||
"Folder" = "Dossier";
|
|
||||||
"Size (KB)" = "Taille (KB)";
|
|
||||||
"Size (MB)" = "Taille (MB)";
|
|
||||||
"Kind" = "Type";
|
|
||||||
"Modification" = "Modification";
|
|
||||||
"Match %" = "Match %";
|
|
||||||
"Words Used" = "Mots";
|
|
||||||
"Dupe Count" = "# Doublons";
|
|
||||||
|
|
||||||
"Time" = "Temps";
|
|
||||||
"Bitrate" = "Bitrate";
|
|
||||||
"Sample Rate" = "Sample Rate";
|
|
||||||
"Title" = "Titre";
|
|
||||||
"Artist" = "Artiste";
|
|
||||||
"Album" = "Album";
|
|
||||||
"Genre" = "Genre";
|
|
||||||
"Year" = "Année";
|
|
||||||
"Track Number" = "Track";
|
|
||||||
"Comment" = "Commentaire";
|
|
||||||
|
|
||||||
"Dimensions" = "Dimensions";
|
|
||||||
|
|
||||||
/* Messages */
|
|
||||||
"SelectResultToLoadMsg" = "Sélectionnez un fichier résultats à charger";
|
|
||||||
"SelectCopyDestinationMsg" = "Sélectionnez un dossier vers lequel copier les fichiers";
|
|
||||||
"SelectMoveDestinationMsg" = "Sélectionnez un dossier vers lequel déplacer les fichiers";
|
|
||||||
"SelectResultToSaveMsg" = "Sélectionnez un fichier résultats dans lequel sauvegarder";
|
|
||||||
"SelectFolderToAddMsg" = "Sélectionnez un dossier à ajouter à la liste";
|
|
||||||
"ReallyWantToQuitMsg" = "Vos résultats ne sont pas sauvegardés. Voulez-vous vraiment quitter?";
|
|
||||||
"ReallyWantToContinueMsg" = "Vos résultats ne sont pas sauvegardés. Voulez-vous vraiment continuer?";
|
|
||||||
"FolderAlreadyInListMsg" = "'%@' est déjà dans la liste.";
|
|
||||||
"FolderDoesNotExistMsg" = "'%@' n'existe pas.";
|
|
||||||
"FileRemovalConfirmMsg" = "%d fichiers seront retirés des résultats. Continuer?";
|
|
||||||
"FilenameAlreadyExistsMsg" = "Le nom '%@' existe déjà.";
|
|
||||||
"NoScannableFileMsg" = "Les dossiers sélectionnés ne continnent pas de fichiers valides.";
|
|
||||||
"UnknownErrorMsg" = "Erreur inconnue.";
|
|
||||||
"SendToTrashConfirmMsg" = "%d fichiers seront envoyés à la corbeille. Continuer?";
|
|
||||||
"HardlinkConfirmMsg" = "%d fichiers seront envoyés à la corbeille (puis 'hardlinkés'). Continuer?";
|
|
||||||
"ClearIgnoreListConfirmMsg" = "Voulez-vous vider la liste de fichiers ignorés des %d items qu'elle contient?";
|
|
||||||
"IgnoreConfirmMsg" = "%d fichiers seront ignorés des prochains scans. Continuer?";
|
|
||||||
"NoCustomCommandMsg" = "Vous n'avez pas de commande personnalisée. Ajoutez-la dans vos préférences.";
|
|
||||||
"CopySuccessMsg" = "Tous les fichiers marqués ont été copiés correctement.";
|
|
||||||
"MoveSuccessMsg" = "Tous les fichiers déplacés ont été copiés correctement.";
|
|
||||||
"SendToTrashSuccessMsg" = "Tous les fichiers marqués ont été correctement envoyés à la corbeille.";
|
|
||||||
"NoDuplicateFoundMsg" = "Aucun doublon trouvé.";
|
|
||||||
"TaskHangingMsg" = "Une action précédente est encore en cours. Attendez quelques secondes avant d'en repartir une nouvelle.";
|
|
||||||
|
|
||||||
"RemoveDeadTracksConfirmMsg" = "Votre librairie iTunes contient %d tracks mortes qui seront retirées. Continuer?";
|
|
||||||
"NoDeadTrackMsg" = "Votre librairie iTunes ne contient aucune track morte.";
|
|
||||||
|
|
||||||
"IPhotoAppNotFoundMsg" = "iPhoto n'a pas pu être trouvée dans vos applications.";
|
|
||||||
"ClearPictureCacheConfirmMsg" = "Voulez-vous vraiment vider la cache de vos analyses précédentes?";
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "5"; */
|
|
||||||
"5.title" = "Tout ramener au premier plan";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Window"; ObjectID = "19"; */
|
|
||||||
"19.title" = "Fenêtre";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "23"; */
|
|
||||||
"23.title" = "Placer dans le Dock";
|
|
||||||
|
|
||||||
/* Class = "NSMenu"; title = "Window"; ObjectID = "24"; */
|
|
||||||
"24.title" = "Fenêtre";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "About dupeGuru"; ObjectID = "58"; */
|
|
||||||
"58.title" = "À propos de dupeGuru";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Help"; ObjectID = "103"; */
|
|
||||||
"103.title" = "Aide";
|
|
||||||
|
|
||||||
/* Class = "NSMenu"; title = "Help"; ObjectID = "106"; */
|
|
||||||
"106.title" = "Aide";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "dupeGuru Help"; ObjectID = "111"; */
|
|
||||||
"111.title" = "Aide dupeGuru";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Hide dupeGuru"; ObjectID = "134"; */
|
|
||||||
"134.title" = "Masquer dupeGuru";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Quit dupeGuru"; ObjectID = "136"; */
|
|
||||||
"136.title" = "Quitter dupeGuru";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "145"; */
|
|
||||||
"145.title" = "Masquer les autres";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Show All"; ObjectID = "150"; */
|
|
||||||
"150.title" = "Tout afficher";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "197"; */
|
|
||||||
"197.title" = "Réduire/agrandir";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Details Panel"; ObjectID = "398"; */
|
|
||||||
"398.title" = "Fenêtre de détails";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Preferences..."; ObjectID = "541"; */
|
|
||||||
"541.title" = "Préférences...";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Folder Selection Window"; ObjectID = "579"; */
|
|
||||||
"579.title" = "Fenêtre de sélection de dossiers";
|
|
||||||
|
|
||||||
/* 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" = "Envoyer marqués à la corbeille";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Move Marked to..."; ObjectID = "600"; */
|
|
||||||
"600.title" = "Déplacer marqués vers...";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Copy Marked to..."; ObjectID = "601"; */
|
|
||||||
"601.title" = "Copier marqués vers...";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Make Selected Reference"; ObjectID = "602"; */
|
|
||||||
"602.title" = "Transformer sélectionnés en références";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Remove Marked from Results"; ObjectID = "603"; */
|
|
||||||
"603.title" = "Retirer marqués des résultats";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Remove Selected from Results"; ObjectID = "605"; */
|
|
||||||
"605.title" = "Retirer sélectionnés des résultats";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Columns"; ObjectID = "618"; */
|
|
||||||
"618.title" = "Colonnes";
|
|
||||||
|
|
||||||
/* Class = "NSMenu"; title = "Columns"; ObjectID = "619"; */
|
|
||||||
"619.title" = "Colonnes";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Open Selected with Default Application"; ObjectID = "708"; */
|
|
||||||
"708.title" = "Ouvrir sélectionné avec l'application par défaut";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Reveal Selected in Finder"; ObjectID = "710"; */
|
|
||||||
"710.title" = "Révéler sélectionné dans Finder";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Add Selected to Ignore List"; ObjectID = "922"; */
|
|
||||||
"922.title" = "Ajouter sélectionnés à la liste de fichiers ignorés";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Close Window"; ObjectID = "924"; */
|
|
||||||
"924.title" = "Fermer";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Start Duplicate Scan"; ObjectID = "926"; */
|
|
||||||
"926.title" = "Commencer à scanner";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Clear Ignore List"; ObjectID = "927"; */
|
|
||||||
"927.title" = "Vider la liste de fichiers ignorés";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Rename Selected"; ObjectID = "933"; */
|
|
||||||
"933.title" = "Renommer sélectionné";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Export Results to XHTML"; ObjectID = "947"; */
|
|
||||||
"947.title" = "Exporter les résultats vers HTML";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Check for update..."; ObjectID = "950"; */
|
|
||||||
"950.title" = "Mise à jour...";
|
|
||||||
|
|
||||||
/* 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" = "Ne pas montrer les références";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Show Delta Values"; ObjectID = "962"; */
|
|
||||||
"962.title" = "Montrer les valeurs en tant que delta";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Edit"; ObjectID = "965"; */
|
|
||||||
"965.title" = "Édition";
|
|
||||||
|
|
||||||
/* Class = "NSMenu"; title = "Edit"; ObjectID = "966"; */
|
|
||||||
"966.title" = "Édition";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Cut"; ObjectID = "985"; */
|
|
||||||
"985.title" = "Couper";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Copy"; ObjectID = "986"; */
|
|
||||||
"986.title" = "Copier";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Paste"; ObjectID = "991"; */
|
|
||||||
"991.title" = "Coller";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Mark All"; ObjectID = "1011"; */
|
|
||||||
"1011.title" = "Tout marquer";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Mark None"; ObjectID = "1012"; */
|
|
||||||
"1012.title" = "Tout démarquer";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Invert Marking"; ObjectID = "1013"; */
|
|
||||||
"1013.title" = "Inverser le marquage";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Mark Selected"; ObjectID = "1014"; */
|
|
||||||
"1014.title" = "Marquer sélectionnés";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "dupeGuru Website"; ObjectID = "1023"; */
|
|
||||||
"1023.title" = "Site web de dupeGuru";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Invoke Custom Command"; ObjectID = "1177"; */
|
|
||||||
"1177.title" = "Invoquer commande personnalisée";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "File"; ObjectID = "1203"; */
|
|
||||||
"1203.title" = "Fichier";
|
|
||||||
|
|
||||||
/* Class = "NSMenu"; title = "File"; ObjectID = "1204"; */
|
|
||||||
"1204.title" = "Fichier";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Load Results..."; ObjectID = "1205"; */
|
|
||||||
"1205.title" = "Charger résultats...";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Save Results..."; ObjectID = "1206"; */
|
|
||||||
"1206.title" = "Sauvegarder résultats...";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Delete Marked and Replace with Hardlinks"; ObjectID = "1227"; */
|
|
||||||
"1227.title" = "Remplacer marqués par des hardlinks";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Load Recent Results"; ObjectID = "1239"; */
|
|
||||||
"1239.title" = "Charger résultats récents";
|
|
||||||
|
|
||||||
/* Class = "NSMenu"; title = "Load Recent Results"; ObjectID = "1240"; */
|
|
||||||
"1240.title" = "Charger résultats récents";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Results Window"; ObjectID = "1272"; */
|
|
||||||
"1272.title" = "Fenêtre de résultats";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,19 +0,0 @@
|
|||||||
|
|
||||||
/* Class = "NSWindow"; title = "Problems!"; ObjectID = "1"; */
|
|
||||||
"1.title" = "Problèmes!";
|
|
||||||
|
|
||||||
/* 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.";
|
|
||||||
"4.title" = "Il y a eu des problèmes lors du traitement de certain (ou tous) fichiers. La cause de ces problèmes est décrite ci-dessous. Ces fichiers n'ont pas été enlevés des résultats.";
|
|
||||||
|
|
||||||
/* Class = "NSTableColumn"; headerCell.title = "File Path"; ObjectID = "10"; */
|
|
||||||
"10.headerCell.title" = "Chemin du fichier";
|
|
||||||
|
|
||||||
/* Class = "NSTableColumn"; headerCell.title = "Error Message"; ObjectID = "11"; */
|
|
||||||
"11.headerCell.title" = "Message d'erreur";
|
|
||||||
|
|
||||||
/* Class = "NSButtonCell"; title = "Close"; ObjectID = "19"; */
|
|
||||||
"19.title" = "Fermer";
|
|
||||||
|
|
||||||
/* Class = "NSButtonCell"; title = "Reveal Selected"; ObjectID = "21"; */
|
|
||||||
"21.title" = "Révéler Fichier";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,96 +0,0 @@
|
|||||||
|
|
||||||
/* Class = "NSWindow"; title = "dupeGuru Results"; ObjectID = "1"; */
|
|
||||||
"1.title" = "dupeGuru (Résultats)";
|
|
||||||
|
|
||||||
/* Class = "NSTextFieldCell"; title = "Marked: 0 files, 0 B. Total: 0 files, 0 B."; ObjectID = "6"; */
|
|
||||||
"6.title" = "Marqués: 0 fichiers, 0 B. Total: 0 fichiers, 0 B.";
|
|
||||||
|
|
||||||
/* Class = "NSTableColumn"; headerCell.title = "Name"; ObjectID = "11"; */
|
|
||||||
"11.headerCell.title" = "Nom";
|
|
||||||
|
|
||||||
/* 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" = "Filtre";
|
|
||||||
|
|
||||||
/* Class = "NSToolbarItem"; paletteLabel = "Filter"; ObjectID = "16"; */
|
|
||||||
"16.paletteLabel" = "Filtre";
|
|
||||||
|
|
||||||
/* 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" = "Dossiers";
|
|
||||||
|
|
||||||
/* Class = "NSToolbarItem"; paletteLabel = "Directories"; ObjectID = "19"; */
|
|
||||||
"19.paletteLabel" = "Dossiers";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Delete Marked and Replace with Hardlinks"; ObjectID = "27"; */
|
|
||||||
"27.title" = "Remplacer marqués par des hardlinks";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Send Marked to Trash"; ObjectID = "29"; */
|
|
||||||
"29.title" = "Envoyer marqués à la corbeille";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Move Marked to..."; ObjectID = "30"; */
|
|
||||||
"30.title" = "Déplacer marqués vers...";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Copy Marked to..."; ObjectID = "31"; */
|
|
||||||
"31.title" = "Copier marqués vers...";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Remove Marked from Results"; ObjectID = "32"; */
|
|
||||||
"32.title" = "Retirer marqués des résultats";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Remove Selected from Results"; ObjectID = "34"; */
|
|
||||||
"34.title" = "Retirer sélectionnés des résultats";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Add Selected to Ignore List"; ObjectID = "35"; */
|
|
||||||
"35.title" = "Ajouter sélectionnés à la liste de fichiers ignorés";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Make Selected Reference"; ObjectID = "36"; */
|
|
||||||
"36.title" = "Transformer sélectionnés en références";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Open Selected with Default Application"; ObjectID = "38"; */
|
|
||||||
"38.title" = "Ouvrir sélectionné avec l'application par défaut";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Reveal Selected in Finder"; ObjectID = "39"; */
|
|
||||||
"39.title" = "Révéler sélectionné dans Finder";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Rename Selected"; ObjectID = "40"; */
|
|
||||||
"40.title" = "Renommer sélectionné";
|
|
||||||
|
|
||||||
/* Class = "NSSearchFieldCell"; placeholderString = "Filter"; ObjectID = "42"; */
|
|
||||||
"42.placeholderString" = "Filtre";
|
|
||||||
|
|
||||||
/* Class = "NSSegmentedCell"; 44.labels[0] = "Details"; ObjectID = "44"; */
|
|
||||||
"44.labels[0]" = "Détails";
|
|
||||||
|
|
||||||
/* Class = "NSSegmentedCell"; 44.labels[1] = "Dupes Only"; ObjectID = "44"; */
|
|
||||||
"44.labels[1]" = "Sans réf.";
|
|
||||||
|
|
||||||
/* Class = "NSSegmentedCell"; 44.labels[2] = "Delta"; ObjectID = "44"; */
|
|
||||||
"44.labels[2]" = "Delta";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Add Selected to Ignore List"; ObjectID = "68"; */
|
|
||||||
"68.title" = "Ajouter sélectionnés à la liste de fichiers ignorés";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Rename Selected"; ObjectID = "70"; */
|
|
||||||
"70.title" = "Renommer sélectionné";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Remove Selected from Results"; ObjectID = "71"; */
|
|
||||||
"71.title" = "Retirer sélectionnés des résultats";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Make Selected Reference"; ObjectID = "72"; */
|
|
||||||
"72.title" = "Transformer sélectionnés en références";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Reveal Selected in Finder"; ObjectID = "73"; */
|
|
||||||
"73.title" = "Révéler sélectionné dans Finder";
|
|
||||||
|
|
||||||
/* Class = "NSMenuItem"; title = "Open Selected with Default Application"; ObjectID = "74"; */
|
|
||||||
"74.title" = "Ouvrir sélectionné avec l'application par défaut";
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,28 +0,0 @@
|
|||||||
"Collecting files to scan" = "Collecte des fichiers à scanner";
|
|
||||||
"%s (%d discarded)" = "%s (%d hors-groupe)";
|
|
||||||
"Scanning for duplicates" = "Scan de doublons en cours";
|
|
||||||
"Loading" = "Chargement en cours";
|
|
||||||
"Moving" = "Déplacement en cours";
|
|
||||||
"Copying" = "Copie en cours";
|
|
||||||
"Sending to Trash" = "Envoi vers corbeille";
|
|
||||||
"0 matches found" = "0 paires trouvées";
|
|
||||||
"%d matches found" = "%d paires trouvées";
|
|
||||||
"Read size of %d/%d files" = "Lu la taille de %d/%d fichiers";
|
|
||||||
"Grouped %d/%d matches" = "%d/%d paires groupées";
|
|
||||||
"%d / %d (%s / %s) duplicates marked." = "%d / %d (%s / %s) doublons marqués.";
|
|
||||||
" filter: %s" = " filtre: %s";
|
|
||||||
"Read metadata of %d/%d files" = "Lu les métadonnées de %d/%d fichiers";
|
|
||||||
"Removing false matches" = "Retrait des paires invalides";
|
|
||||||
"Processed %d/%d matches against the ignore list" = "Vérification de %d/%d paires dans la ignore list";
|
|
||||||
"Doing group prioritization" = "Prioritization des groupes";
|
|
||||||
|
|
||||||
"Analyzed %d/%d pictures" = "Analyzé %d/%d images";
|
|
||||||
"Preparing for matching" = "Préparation pour la comparaison";
|
|
||||||
"Matched %d/%d pictures" = "Comparé %d/%d images";
|
|
||||||
"Verified %d/%d matches" = "Vérifié %d/%d paires";
|
|
||||||
|
|
||||||
"Removing dead tracks from your iTunes Library" = "Retrait des tracks mortes de votre librairie iTunes";
|
|
||||||
"Scanning the iTunes Library" = "Scan de la librairie iTunes en cours";
|
|
||||||
|
|
||||||
"Probing iPhoto. Don't touch it during the operation!" = "Communication avec iPhoto en cours. N'y touchez pas!";
|
|
||||||
"Sending dupes to the Trash" = "Envoi de doublons à la corbeille en cours";
|
|
||||||
49
cocoa/base/main.m
Normal file
49
cocoa/base/main.m
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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>
|
||||||
|
#import "AppDelegate.h"
|
||||||
|
#import "MainMenu_UI.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
[NSApplication sharedApplication];
|
||||||
|
AppDelegate *appDelegate = [[AppDelegate alloc] init];
|
||||||
|
[NSApp setDelegate:appDelegate];
|
||||||
|
[NSApp setMainMenu:createMainMenu_UI(appDelegate)];
|
||||||
|
[appDelegate finalizeInit];
|
||||||
|
[pool release];
|
||||||
|
[NSApp run];
|
||||||
|
Py_Finalize();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
49
cocoa/base/ui/deletion_options.py
Normal file
49
cocoa/base/ui/deletion_options.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
ownerclass = 'DeletionOptions'
|
||||||
|
ownerimport = 'DeletionOptions.h'
|
||||||
|
|
||||||
|
result = Window(450, 240, "Deletion Options")
|
||||||
|
messageLabel = Label(result, "")
|
||||||
|
linkCheckbox = Checkbox(result, "Link deleted files")
|
||||||
|
linkLabel = Label(result, "After having deleted a duplicate, place a link targeting the "
|
||||||
|
"reference file to replace the deleted file.")
|
||||||
|
linkTypeChoice = RadioButtons(result, ["Symlink", "Hardlink"], columns=2)
|
||||||
|
directCheckbox = Checkbox(result, "Directly delete files")
|
||||||
|
directLabel = Label(result, "Instead of sending files to trash, delete them directly. This option "
|
||||||
|
"is usually used as a workaround when the normal deletion method doesn't work.")
|
||||||
|
proceedButton = Button(result, "Proceed")
|
||||||
|
cancelButton = Button(result, "Cancel")
|
||||||
|
|
||||||
|
owner.linkButton = linkCheckbox
|
||||||
|
owner.linkTypeRadio = linkTypeChoice
|
||||||
|
owner.directButton = directCheckbox
|
||||||
|
owner.messageTextField = messageLabel
|
||||||
|
|
||||||
|
result.canMinimize = False
|
||||||
|
result.canResize = False
|
||||||
|
linkLabel.controlSize = ControlSize.Small
|
||||||
|
directLabel.controlSize = ControlSize.Small
|
||||||
|
linkTypeChoice.controlSize = ControlSize.Small
|
||||||
|
proceedButton.keyEquivalent = '\\r'
|
||||||
|
cancelButton.keyEquivalent = '\\e'
|
||||||
|
linkCheckbox.action = directCheckbox.action = linkTypeChoice.action = Action(owner, 'updateOptions')
|
||||||
|
proceedButton.action = Action(owner, 'proceed')
|
||||||
|
cancelButton.action = Action(owner, 'cancel')
|
||||||
|
|
||||||
|
linkLabel.height *= 2 # 2 lines
|
||||||
|
directLabel.height *= 3 # 3 lines
|
||||||
|
proceedButton.width = 92
|
||||||
|
cancelButton.width = 92
|
||||||
|
|
||||||
|
mainLayout = VLayout([messageLabel, linkCheckbox, linkLabel, linkTypeChoice, directCheckbox,
|
||||||
|
directLabel])
|
||||||
|
mainLayout.packToCorner(Pack.UpperLeft)
|
||||||
|
mainLayout.fill(Pack.Right)
|
||||||
|
buttonLayout = HLayout([cancelButton, proceedButton])
|
||||||
|
buttonLayout.packToCorner(Pack.LowerRight)
|
||||||
|
|
||||||
|
# indent the labels under checkboxes a little bit to the right
|
||||||
|
for indentedView in (linkLabel, directLabel, linkTypeChoice):
|
||||||
|
indentedView.x += 20
|
||||||
|
indentedView.width -= 20
|
||||||
|
# We actually don't want the link choice radio buttons to take all the width, it looks weird.
|
||||||
|
linkTypeChoice.width = 170
|
||||||
32
cocoa/base/ui/details_panel.py
Normal file
32
cocoa/base/ui/details_panel.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
ownerclass = 'DetailsPanel'
|
||||||
|
ownerimport = 'DetailsPanel.h'
|
||||||
|
|
||||||
|
result = Panel(451, 146, "Details of Selected File")
|
||||||
|
table = TableView(result)
|
||||||
|
|
||||||
|
owner.detailsTable = table
|
||||||
|
|
||||||
|
result.style = PanelStyle.Utility
|
||||||
|
result.xProportion = 0.2
|
||||||
|
result.yProportion = 0.4
|
||||||
|
result.canMinimize = False
|
||||||
|
result.autosaveName = 'DetailsPanel'
|
||||||
|
result.minSize = Size(result.width, result.height)
|
||||||
|
|
||||||
|
table.dataSource = owner
|
||||||
|
table.allowsColumnReordering = False
|
||||||
|
table.allowsColumnSelection = False
|
||||||
|
table.allowsMultipleSelection = False
|
||||||
|
table.font = Font(FontFamily.System, FontSize.SmallSystem)
|
||||||
|
table.rowHeight = 14
|
||||||
|
table.editable = False
|
||||||
|
col = table.addColumn('0', "Attribute", 70)
|
||||||
|
col.autoResizable = True
|
||||||
|
col = table.addColumn('1', "Selected", 198)
|
||||||
|
col.autoResizable = True
|
||||||
|
col = table.addColumn('2', "Reference", 172)
|
||||||
|
col.autoResizable = True
|
||||||
|
|
||||||
|
table.packToCorner(Pack.UpperLeft, margin=0)
|
||||||
|
table.fill(Pack.LowerRight, margin=0)
|
||||||
|
table.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
||||||
64
cocoa/base/ui/directory_panel.py
Normal file
64
cocoa/base/ui/directory_panel.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
ownerclass = 'DirectoryPanel'
|
||||||
|
ownerimport = 'DirectoryPanel.h'
|
||||||
|
|
||||||
|
result = Window(425, 300, "dupeGuru")
|
||||||
|
promptLabel = Label(result, "Select folders to scan and press \"Scan\".")
|
||||||
|
directoryOutline = OutlineView(result)
|
||||||
|
directoryOutline.OBJC_CLASS = 'HSOutlineView'
|
||||||
|
addButton = Button(result, "")
|
||||||
|
removeButton = Button(result, "")
|
||||||
|
loadResultsButton = Button(result, "Load Results")
|
||||||
|
scanButton = Button(result, "Scan")
|
||||||
|
addPopup = Popup(None)
|
||||||
|
loadRecentPopup = Popup(None)
|
||||||
|
|
||||||
|
owner.outlineView = directoryOutline
|
||||||
|
owner.removeButton = removeButton
|
||||||
|
owner.loadResultsButton = loadResultsButton
|
||||||
|
owner.addButtonPopUp = addPopup
|
||||||
|
owner.loadRecentButtonPopUp = loadRecentPopup
|
||||||
|
|
||||||
|
result.autosaveName = 'DirectoryPanel'
|
||||||
|
result.canMinimize = False
|
||||||
|
result.minSize = Size(370, 270)
|
||||||
|
addButton.bezelStyle = removeButton.bezelStyle = const.NSTexturedRoundedBezelStyle
|
||||||
|
addButton.image = 'NSAddTemplate'
|
||||||
|
removeButton.image = 'NSRemoveTemplate'
|
||||||
|
for button in (addButton, removeButton):
|
||||||
|
button.style = const.NSTexturedRoundedBezelStyle
|
||||||
|
button.imagePosition = const.NSImageOnly
|
||||||
|
scanButton.keyEquivalent = '\\r'
|
||||||
|
addButton.action = Action(owner, 'popupAddDirectoryMenu:')
|
||||||
|
removeButton.action = Action(owner, 'removeSelectedDirectory')
|
||||||
|
loadResultsButton.action = Action(owner, 'popupLoadRecentMenu:')
|
||||||
|
scanButton.action = Action(None, 'startScanning')
|
||||||
|
|
||||||
|
directoryOutline.font = Font(FontFamily.System, FontSize.SmallSystem)
|
||||||
|
col = directoryOutline.addColumn('name', "Name", 100)
|
||||||
|
col.editable = False
|
||||||
|
col.autoResizable = True
|
||||||
|
col = directoryOutline.addColumn('state', "State", 85)
|
||||||
|
col.editable = True
|
||||||
|
col.autoResizable = False
|
||||||
|
col.dataCell = Popup(None, ["Normal", "Reference", "Excluded"])
|
||||||
|
col.dataCell.controlSize = const.NSSmallControlSize
|
||||||
|
directoryOutline.allowsColumnReordering = False
|
||||||
|
directoryOutline.allowsColumnSelection = False
|
||||||
|
directoryOutline.allowsMultipleSelection = True
|
||||||
|
|
||||||
|
for button in (addButton, removeButton):
|
||||||
|
button.width = 28
|
||||||
|
for button in (loadResultsButton, scanButton):
|
||||||
|
button.width = 118
|
||||||
|
|
||||||
|
buttonLayout = HLayout([addButton, removeButton, None, loadResultsButton, scanButton])
|
||||||
|
promptLabel.packToCorner(Pack.UpperLeft)
|
||||||
|
promptLabel.fill(Pack.Right)
|
||||||
|
directoryOutline.packRelativeTo(promptLabel, Pack.Below)
|
||||||
|
buttonLayout.packRelativeTo(directoryOutline, Pack.Below, margin=8)
|
||||||
|
directoryOutline.fill(Pack.LowerRight)
|
||||||
|
buttonLayout.fill(Pack.Right)
|
||||||
|
|
||||||
|
promptLabel.setAnchor(Pack.UpperLeft, growX=True)
|
||||||
|
directoryOutline.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
||||||
|
buttonLayout.setAnchor(Pack.Below)
|
||||||
30
cocoa/base/ui/ignore_list_dialog.py
Normal file
30
cocoa/base/ui/ignore_list_dialog.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
ownerclass = 'IgnoreListDialog'
|
||||||
|
ownerimport = 'IgnoreListDialog.h'
|
||||||
|
|
||||||
|
result = Window(550, 350, "Ignore List")
|
||||||
|
table = TableView(result)
|
||||||
|
removeSelectedButton = Button(result, "Remove Selected")
|
||||||
|
clearButton = Button(result, "Clear")
|
||||||
|
closeButton = Button(result, "Close")
|
||||||
|
|
||||||
|
owner.ignoreListTableView = table
|
||||||
|
|
||||||
|
result.canMinimize = False
|
||||||
|
removeSelectedButton.action = Action(owner.model, 'removeSelected')
|
||||||
|
clearButton.action = Action(owner.model, 'clear')
|
||||||
|
closeButton.action = Action(result, 'performClose:')
|
||||||
|
closeButton.keyEquivalent = '\\r'
|
||||||
|
table.allowsColumnReordering = False
|
||||||
|
table.allowsColumnSelection = False
|
||||||
|
table.allowsMultipleSelection = True
|
||||||
|
|
||||||
|
removeSelectedButton.width = 142
|
||||||
|
clearButton.width = 142
|
||||||
|
closeButton.width = 84
|
||||||
|
buttonLayout = HLayout([removeSelectedButton, clearButton, None, closeButton])
|
||||||
|
buttonLayout.packToCorner(Pack.LowerLeft)
|
||||||
|
buttonLayout.fill(Pack.Right)
|
||||||
|
buttonLayout.setAnchor(Pack.Below)
|
||||||
|
table.packRelativeTo(buttonLayout, Pack.Above)
|
||||||
|
table.fill(Pack.UpperRight)
|
||||||
|
table.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
||||||
82
cocoa/base/ui/main_menu.py
Normal file
82
cocoa/base/ui/main_menu.py
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
ownerclass = 'AppDelegateBase'
|
||||||
|
ownerimport = 'AppDelegateBase.h'
|
||||||
|
edition = args.get('edition', 'se')
|
||||||
|
|
||||||
|
result = Menu("")
|
||||||
|
appMenu = result.addMenu("dupeGuru")
|
||||||
|
fileMenu = result.addMenu("File")
|
||||||
|
editMenu = result.addMenu("Edit")
|
||||||
|
actionMenu = result.addMenu("Actions")
|
||||||
|
owner.columnsMenu = result.addMenu("Columns")
|
||||||
|
modeMenu = result.addMenu("Mode")
|
||||||
|
windowMenu = result.addMenu("Window")
|
||||||
|
helpMenu = result.addMenu("Help")
|
||||||
|
|
||||||
|
appMenu.addItem("About dupeGuru", Action(owner, 'showAboutBox'))
|
||||||
|
appMenu.addItem("Check for update...", Action(owner.updater, 'checkForUpdates:'))
|
||||||
|
appMenu.addSeparator()
|
||||||
|
appMenu.addItem("Preferences...", Action(owner, 'showPreferencesPanel'), 'cmd+,')
|
||||||
|
appMenu.addSeparator()
|
||||||
|
NSApp.servicesMenu = appMenu.addMenu("Services")
|
||||||
|
appMenu.addSeparator()
|
||||||
|
appMenu.addItem("Hide dupeGuru", Action(NSApp, 'hide:'), 'cmd+h')
|
||||||
|
appMenu.addItem("Hide Others", Action(NSApp, 'hideOtherApplications:'), 'cmd+alt+h')
|
||||||
|
appMenu.addItem("Show All", Action(NSApp, 'unhideAllApplications:'))
|
||||||
|
appMenu.addSeparator()
|
||||||
|
appMenu.addItem("Quit dupeGuru", Action(NSApp, 'terminate:'), 'cmd+q')
|
||||||
|
|
||||||
|
fileMenu.addItem("Load Results...", Action(None, 'loadResults'), 'cmd+o')
|
||||||
|
owner.recentResultsMenu = fileMenu.addMenu("Load Recent Results")
|
||||||
|
fileMenu.addItem("Save Results...", Action(None, 'saveResults'), 'cmd+s')
|
||||||
|
fileMenu.addItem("Export Results to XHTML", Action(owner.model, 'exportToXHTML'), 'cmd+shift+e')
|
||||||
|
fileMenu.addItem("Export Results to CSV", Action(owner.model, 'exportToCSV'))
|
||||||
|
if edition == 'pe':
|
||||||
|
fileMenu.addItem("Clear Picture Cache", Action(owner, 'clearPictureCache'), 'cmd+shift+p')
|
||||||
|
elif edition == 'me':
|
||||||
|
fileMenu.addItem("Remove Dead Tracks in iTunes", Action(owner, 'removeDeadTracks'))
|
||||||
|
|
||||||
|
editMenu.addItem("Mark All", Action(None, 'markAll'), 'cmd+a')
|
||||||
|
editMenu.addItem("Mark None", Action(None, 'markNone'), 'cmd+shift+a')
|
||||||
|
editMenu.addItem("Invert Marking", Action(None, 'markInvert'), 'cmd+alt+a')
|
||||||
|
editMenu.addItem("Mark Selected", Action(None, 'markSelected'), 'ctrl+cmd+a')
|
||||||
|
editMenu.addSeparator()
|
||||||
|
editMenu.addItem("Cut", Action(None, 'cut:'), 'cmd+x')
|
||||||
|
editMenu.addItem("Copy", Action(None, 'copy:'), 'cmd+c')
|
||||||
|
editMenu.addItem("Paste", Action(None, 'paste:'), 'cmd+v')
|
||||||
|
editMenu.addSeparator()
|
||||||
|
editMenu.addItem("Filter Results...", Action(None, 'focusOnFilterField'), 'cmd+alt+f')
|
||||||
|
|
||||||
|
actionMenu.addItem("Start Duplicate Scan", Action(owner, 'startScanning'), 'cmd+d')
|
||||||
|
actionMenu.addSeparator()
|
||||||
|
actionMenu.addItem("Send Marked to Trash...", Action(None, 'trashMarked'), 'cmd+t')
|
||||||
|
actionMenu.addItem("Move Marked to...", Action(None, 'moveMarked'), 'cmd+m')
|
||||||
|
actionMenu.addItem("Copy Marked to...", Action(None, 'copyMarked'), 'cmd+alt+m')
|
||||||
|
actionMenu.addItem("Remove Marked from Results", Action(None, 'removeMarked'), 'cmd+r')
|
||||||
|
actionMenu.addItem("Re-Prioritize Results...", Action(None, 'reprioritizeResults'))
|
||||||
|
actionMenu.addSeparator()
|
||||||
|
actionMenu.addItem("Remove Selected from Results", Action(None, 'removeSelected'), 'cmd+backspace')
|
||||||
|
actionMenu.addItem("Add Selected to Ignore List", Action(None, 'ignoreSelected'), 'cmd+g')
|
||||||
|
actionMenu.addItem("Make Selected into Reference", Action(None, 'switchSelected'), 'cmd+arrowup')
|
||||||
|
actionMenu.addSeparator()
|
||||||
|
actionMenu.addItem("Open Selected with Default Application", Action(None, 'openSelected'), 'cmd+return')
|
||||||
|
actionMenu.addItem("Reveal Selected in Finder", Action(None, 'revealSelected'), 'cmd+alt+return')
|
||||||
|
actionMenu.addItem("Invoke Custom Command", Action(None, 'invokeCustomCommand'), 'cmd+shift+c')
|
||||||
|
actionMenu.addItem("Rename Selected", Action(None, 'renameSelected'), 'enter')
|
||||||
|
|
||||||
|
modeMenu.addItem("Show Dupes Only", Action(None, 'togglePowerMarker'), 'cmd+1')
|
||||||
|
modeMenu.addItem("Show Delta Values", Action(None, 'toggleDelta'), 'cmd+2')
|
||||||
|
|
||||||
|
windowMenu.addItem("Results Window", Action(owner, 'showResultWindow'))
|
||||||
|
windowMenu.addItem("Folder Selection Window", Action(owner, 'showDirectoryWindow'))
|
||||||
|
windowMenu.addItem("Ignore List", Action(owner, 'showIgnoreList'))
|
||||||
|
windowMenu.addItem("Details Panel", Action(None, 'toggleDetailsPanel'), 'cmd+i')
|
||||||
|
windowMenu.addItem("Quick Look", Action(None, 'toggleQuicklookPanel'), 'cmd+l')
|
||||||
|
windowMenu.addSeparator()
|
||||||
|
windowMenu.addItem("Minimize", Action(None, 'performMinimize:'))
|
||||||
|
windowMenu.addItem("Zoom", Action(None, 'performZoom:'))
|
||||||
|
windowMenu.addItem("Close Window", Action(None, 'performClose:'), 'cmd+w')
|
||||||
|
windowMenu.addSeparator()
|
||||||
|
windowMenu.addItem("Bring All to Front", Action(None, 'arrangeInFront:'))
|
||||||
|
|
||||||
|
helpMenu.addItem("dupeGuru Help", Action(owner, 'openHelp'), 'cmd+?')
|
||||||
|
helpMenu.addItem("dupeGuru Website", Action(owner, 'openWebsite'))
|
||||||
189
cocoa/base/ui/preferences_panel.py
Normal file
189
cocoa/base/ui/preferences_panel.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
edition = args.get('edition', 'se')
|
||||||
|
dialogTitles = {
|
||||||
|
'se': "dupeGuru Preferences",
|
||||||
|
'me': "dupeGuru ME Preferences",
|
||||||
|
'pe': "dupeGuru PE Preferences",
|
||||||
|
}
|
||||||
|
dialogHeights = {
|
||||||
|
'se': 345,
|
||||||
|
'me': 365,
|
||||||
|
'pe': 275,
|
||||||
|
}
|
||||||
|
scanTypeNames = {
|
||||||
|
'se': ["Filename", "Content", "Folders"],
|
||||||
|
'me': ["Filename", "Filename - Fields", "Filename - Fields (No Order)", "Tags", "Content", "Audio Content"],
|
||||||
|
'pe': ["Contents", "EXIF Timestamp"],
|
||||||
|
}
|
||||||
|
|
||||||
|
result = Window(410, dialogHeights[edition], dialogTitles[edition])
|
||||||
|
tabView = TabView(result)
|
||||||
|
basicTab = tabView.addTab("Basic")
|
||||||
|
advancedTab = tabView.addTab("Advanced")
|
||||||
|
scanTypePopup = Popup(basicTab.view, scanTypeNames[edition])
|
||||||
|
scanTypeLabel = Label(basicTab.view, "Scan Type:")
|
||||||
|
thresholdSlider = Slider(basicTab.view, 1, 100, 80)
|
||||||
|
thresholdLabel = Label(basicTab.view, "Filter hardness:")
|
||||||
|
moreResultsLabel = Label(basicTab.view, "More results")
|
||||||
|
fewerResultsLabel = Label(basicTab.view, "Fewer results")
|
||||||
|
thresholdValueLabel = Label(basicTab.view, "")
|
||||||
|
fontSizeCombo = Combobox(basicTab.view, ["11", "12", "13", "14", "18", "24"])
|
||||||
|
fontSizeLabel = Label(basicTab.view, "Font Size:")
|
||||||
|
if edition in ('se', 'me'):
|
||||||
|
wordWeightingBox = Checkbox(basicTab.view, "Word weighting")
|
||||||
|
matchSimilarWordsBox = Checkbox(basicTab.view, "Match similar words")
|
||||||
|
elif edition == 'pe':
|
||||||
|
matchDifferentDimensionsBox = Checkbox(basicTab.view, "Match pictures of different dimensions")
|
||||||
|
mixKindBox = Checkbox(basicTab.view, "Can mix file kind")
|
||||||
|
removeEmptyFoldersBox = Checkbox(basicTab.view, "Remove empty folders on delete or move")
|
||||||
|
checkForUpdatesBox = Checkbox(basicTab.view, "Automatically check for updates")
|
||||||
|
if edition == 'se':
|
||||||
|
ignoreSmallFilesBox = Checkbox(basicTab.view, "Ignore files smaller than:")
|
||||||
|
smallFilesThresholdText = TextField(basicTab.view, "")
|
||||||
|
smallFilesThresholdSuffixLabel = Label(basicTab.view, "KB")
|
||||||
|
elif edition == 'me':
|
||||||
|
tagsToScanLabel = Label(basicTab.view, "Tags to scan:")
|
||||||
|
trackBox = Checkbox(basicTab.view, "Track")
|
||||||
|
artistBox = Checkbox(basicTab.view, "Artist")
|
||||||
|
albumBox = Checkbox(basicTab.view, "Album")
|
||||||
|
titleBox = Checkbox(basicTab.view, "Title")
|
||||||
|
genreBox = Checkbox(basicTab.view, "Genre")
|
||||||
|
yearBox = Checkbox(basicTab.view, "Year")
|
||||||
|
tagBoxes = [trackBox, artistBox, albumBox, titleBox, genreBox, yearBox]
|
||||||
|
|
||||||
|
regexpCheckbox = Checkbox(advancedTab.view, "Use regular expressions when filtering")
|
||||||
|
ignoreHardlinksBox = Checkbox(advancedTab.view, "Ignore duplicates hardlinking to the same file")
|
||||||
|
debugModeCheckbox = Checkbox(advancedTab.view, "Debug mode (restart required)")
|
||||||
|
customCommandLabel = Label(advancedTab.view, "Custom command (arguments: %d for dupe, %r for ref):")
|
||||||
|
customCommandText = TextField(advancedTab.view, "")
|
||||||
|
copyMoveLabel = Label(advancedTab.view, "Copy and Move:")
|
||||||
|
copyMovePopup = Popup(advancedTab.view, ["Right in destination", "Recreate relative path", "Recreate absolute path"])
|
||||||
|
|
||||||
|
resetToDefaultsButton = Button(result, "Reset To Defaults")
|
||||||
|
|
||||||
|
scanTypePopup.bind('selectedIndex', defaults, 'values.scanType')
|
||||||
|
thresholdSlider.bind('value', defaults, 'values.minMatchPercentage')
|
||||||
|
thresholdValueLabel.bind('value', defaults, 'values.minMatchPercentage')
|
||||||
|
fontSizeCombo.bind('value', defaults, 'values.TableFontSize')
|
||||||
|
mixKindBox.bind('value', defaults, 'values.mixFileKind')
|
||||||
|
removeEmptyFoldersBox.bind('value', defaults, 'values.removeEmptyFolders')
|
||||||
|
checkForUpdatesBox.bind('value', defaults, 'values.SUEnableAutomaticChecks')
|
||||||
|
regexpCheckbox.bind('value', defaults, 'values.useRegexpFilter')
|
||||||
|
ignoreHardlinksBox.bind('value', defaults, 'values.ignoreHardlinkMatches')
|
||||||
|
debugModeCheckbox.bind('value', defaults, 'values.DebugMode')
|
||||||
|
customCommandText.bind('value', defaults, 'values.CustomCommand')
|
||||||
|
copyMovePopup.bind('selectedIndex', defaults, 'values.recreatePathType')
|
||||||
|
if edition in ('se', 'me'):
|
||||||
|
wordWeightingBox.bind('value', defaults, 'values.wordWeighting')
|
||||||
|
matchSimilarWordsBox.bind('value', defaults, 'values.matchSimilarWords')
|
||||||
|
disableWhenContentScan = [thresholdSlider, wordWeightingBox, matchSimilarWordsBox]
|
||||||
|
for control in disableWhenContentScan:
|
||||||
|
control.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsNotContent')
|
||||||
|
if edition == 'se':
|
||||||
|
ignoreSmallFilesBox.bind('value', defaults, 'values.ignoreSmallFiles')
|
||||||
|
smallFilesThresholdText.bind('value', defaults, 'values.smallFileThreshold')
|
||||||
|
elif edition == 'me':
|
||||||
|
for box in tagBoxes:
|
||||||
|
box.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsTag')
|
||||||
|
trackBox.bind('value', defaults, 'values.scanTagTrack')
|
||||||
|
artistBox.bind('value', defaults, 'values.scanTagArtist')
|
||||||
|
albumBox.bind('value', defaults, 'values.scanTagAlbum')
|
||||||
|
titleBox.bind('value', defaults, 'values.scanTagTitle')
|
||||||
|
genreBox.bind('value', defaults, 'values.scanTagGenre')
|
||||||
|
yearBox.bind('value', defaults, 'values.scanTagYear')
|
||||||
|
elif edition == 'pe':
|
||||||
|
matchDifferentDimensionsBox.bind('value', defaults, 'values.matchScaled')
|
||||||
|
thresholdSlider.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsFuzzy')
|
||||||
|
|
||||||
|
result.canResize = False
|
||||||
|
result.canMinimize = False
|
||||||
|
thresholdValueLabel.formatter = NumberFormatter(NumberStyle.Decimal)
|
||||||
|
thresholdValueLabel.formatter.maximumFractionDigits = 0
|
||||||
|
allLabels = [scanTypeLabel, thresholdValueLabel, moreResultsLabel, fewerResultsLabel,
|
||||||
|
thresholdLabel, fontSizeLabel, customCommandLabel, copyMoveLabel]
|
||||||
|
allCheckboxes = [mixKindBox, removeEmptyFoldersBox, checkForUpdatesBox, regexpCheckbox,
|
||||||
|
ignoreHardlinksBox, debugModeCheckbox]
|
||||||
|
if edition == 'se':
|
||||||
|
allLabels += [smallFilesThresholdSuffixLabel]
|
||||||
|
allCheckboxes += [ignoreSmallFilesBox, wordWeightingBox, matchSimilarWordsBox]
|
||||||
|
elif edition == 'me':
|
||||||
|
allLabels += [tagsToScanLabel]
|
||||||
|
allCheckboxes += tagBoxes + [wordWeightingBox, matchSimilarWordsBox]
|
||||||
|
elif edition == 'pe':
|
||||||
|
allCheckboxes += [matchDifferentDimensionsBox]
|
||||||
|
for label in allLabels:
|
||||||
|
label.controlSize = ControlSize.Small
|
||||||
|
fewerResultsLabel.alignment = TextAlignment.Right
|
||||||
|
for checkbox in allCheckboxes:
|
||||||
|
checkbox.font = scanTypeLabel.font
|
||||||
|
resetToDefaultsButton.action = Action(defaults, 'revertToInitialValues:')
|
||||||
|
|
||||||
|
scanTypeLabel.width = thresholdLabel.width = fontSizeLabel.width = 94
|
||||||
|
fontSizeCombo.width = 66
|
||||||
|
thresholdValueLabel.width = 25
|
||||||
|
resetToDefaultsButton.width = 136
|
||||||
|
if edition == 'se':
|
||||||
|
smallFilesThresholdText.width = 60
|
||||||
|
smallFilesThresholdSuffixLabel.width = 40
|
||||||
|
elif edition == 'me':
|
||||||
|
for box in tagBoxes:
|
||||||
|
box.width = 70
|
||||||
|
|
||||||
|
tabView.packToCorner(Pack.UpperLeft)
|
||||||
|
tabView.fill(Pack.Right)
|
||||||
|
resetToDefaultsButton.packRelativeTo(tabView, Pack.Below, align=Pack.Right)
|
||||||
|
tabView.fill(Pack.Below, margin=14)
|
||||||
|
tabView.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
||||||
|
scanTypePopup.packToCorner(Pack.UpperRight)
|
||||||
|
scanTypeLabel.packRelativeTo(scanTypePopup, Pack.Left)
|
||||||
|
scanTypePopup.fill(Pack.Left)
|
||||||
|
thresholdSlider.packRelativeTo(scanTypePopup, Pack.Below)
|
||||||
|
thresholdValueLabel.packRelativeTo(thresholdSlider, Pack.Right)
|
||||||
|
thresholdSlider.fill(Pack.Right)
|
||||||
|
# We want to give the labels as much space as possible, and we only "know" how much is available
|
||||||
|
# after the slider's fill operation.
|
||||||
|
moreResultsLabel.width = fewerResultsLabel.width = thresholdSlider.width // 2
|
||||||
|
moreResultsLabel.packRelativeTo(thresholdSlider, Pack.Below, align=Pack.Left, margin=6)
|
||||||
|
fewerResultsLabel.packRelativeTo(thresholdSlider, Pack.Below, align=Pack.Right, margin=6)
|
||||||
|
thresholdLabel.packRelativeTo(thresholdSlider, Pack.Left)
|
||||||
|
fontSizeCombo.packRelativeTo(moreResultsLabel, Pack.Below)
|
||||||
|
fontSizeLabel.packRelativeTo(fontSizeCombo, Pack.Left)
|
||||||
|
|
||||||
|
if edition == 'me':
|
||||||
|
tagsToScanLabel.packRelativeTo(fontSizeCombo, Pack.Below)
|
||||||
|
tagsToScanLabel.fill(Pack.Left)
|
||||||
|
tagsToScanLabel.fill(Pack.Right)
|
||||||
|
trackBox.packRelativeTo(tagsToScanLabel, Pack.Below)
|
||||||
|
trackBox.x += 10
|
||||||
|
artistBox.packRelativeTo(trackBox, Pack.Right)
|
||||||
|
albumBox.packRelativeTo(artistBox, Pack.Right)
|
||||||
|
titleBox.packRelativeTo(trackBox, Pack.Below)
|
||||||
|
genreBox.packRelativeTo(titleBox, Pack.Right)
|
||||||
|
yearBox.packRelativeTo(genreBox, Pack.Right)
|
||||||
|
viewToPackCheckboxesUnder = titleBox
|
||||||
|
else:
|
||||||
|
viewToPackCheckboxesUnder = fontSizeCombo
|
||||||
|
|
||||||
|
if edition == 'se':
|
||||||
|
checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox,
|
||||||
|
ignoreSmallFilesBox]
|
||||||
|
elif edition == 'me':
|
||||||
|
checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox,
|
||||||
|
checkForUpdatesBox]
|
||||||
|
elif edition == 'pe':
|
||||||
|
checkboxesToLayout = [matchDifferentDimensionsBox, mixKindBox, removeEmptyFoldersBox,
|
||||||
|
checkForUpdatesBox]
|
||||||
|
checkboxLayout = VLayout(checkboxesToLayout)
|
||||||
|
checkboxLayout.packRelativeTo(viewToPackCheckboxesUnder, Pack.Below)
|
||||||
|
checkboxLayout.fill(Pack.Left)
|
||||||
|
checkboxLayout.fill(Pack.Right)
|
||||||
|
|
||||||
|
if edition == 'se':
|
||||||
|
smallFilesThresholdText.packRelativeTo(ignoreSmallFilesBox, Pack.Below, margin=4)
|
||||||
|
checkForUpdatesBox.packRelativeTo(smallFilesThresholdText, Pack.Below, margin=4)
|
||||||
|
checkForUpdatesBox.fill(Pack.Right)
|
||||||
|
smallFilesThresholdText.x += 20
|
||||||
|
smallFilesThresholdSuffixLabel.packRelativeTo(smallFilesThresholdText, Pack.Right)
|
||||||
|
|
||||||
|
advancedLayout = VLayout(advancedTab.view.subviews[:])
|
||||||
|
advancedLayout.packToCorner(Pack.UpperLeft)
|
||||||
|
advancedLayout.fill(Pack.Right)
|
||||||
65
cocoa/base/ui/prioritize_dialog.py
Normal file
65
cocoa/base/ui/prioritize_dialog.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
ownerclass = 'PrioritizeDialog'
|
||||||
|
ownerimport = 'PrioritizeDialog.h'
|
||||||
|
|
||||||
|
result = Window(610, 400, "Re-Prioritize duplicates")
|
||||||
|
promptLabel = Label(result, "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.")
|
||||||
|
split = SplitView(result, 2, vertical=True)
|
||||||
|
categoryPopup = Popup(split.subviews[0])
|
||||||
|
criteriaTable = ListView(split.subviews[0])
|
||||||
|
prioritizationTable = ListView(split.subviews[1])
|
||||||
|
addButton = Button(split.subviews[1], NLSTR("-->"))
|
||||||
|
removeButton = Button(split.subviews[1], NLSTR("<--"))
|
||||||
|
okButton = Button(result, "Ok")
|
||||||
|
cancelButton = Button(result, "Cancel")
|
||||||
|
|
||||||
|
owner.categoryPopUpView = categoryPopup
|
||||||
|
owner.criteriaTableView = criteriaTable
|
||||||
|
owner.prioritizationTableView = prioritizationTable
|
||||||
|
|
||||||
|
result.canMinimize = False
|
||||||
|
result.canClose = False
|
||||||
|
result.minSize = Size(result.width, result.height)
|
||||||
|
addButton.action = Action(owner.model, 'addSelected')
|
||||||
|
removeButton.action = Action(owner.model, 'removeSelected')
|
||||||
|
okButton.action = Action(owner, 'ok')
|
||||||
|
cancelButton.action = Action(owner, 'cancel')
|
||||||
|
okButton.keyEquivalent = '\\r'
|
||||||
|
cancelButton.keyEquivalent = '\\e'
|
||||||
|
|
||||||
|
# For layouts to correctly work, subviews need to have the dimensions they'll approximately have
|
||||||
|
# at runtime.
|
||||||
|
split.subviews[0].width = 260
|
||||||
|
split.subviews[0].height = 260
|
||||||
|
split.subviews[1].width = 340
|
||||||
|
split.subviews[1].height = 260
|
||||||
|
promptLabel.height *= 3 # 3 lines
|
||||||
|
|
||||||
|
leftLayout = VLayout([categoryPopup, criteriaTable], filler=criteriaTable)
|
||||||
|
middleLayout = VLayout([addButton, removeButton], width=41)
|
||||||
|
buttonLayout = HLayout([None, cancelButton, okButton])
|
||||||
|
|
||||||
|
#pack split subview 0
|
||||||
|
leftLayout.fillAll()
|
||||||
|
|
||||||
|
#pack split subview 1
|
||||||
|
prioritizationTable.fillAll()
|
||||||
|
prioritizationTable.width -= 48
|
||||||
|
prioritizationTable.moveTo(Pack.Right)
|
||||||
|
middleLayout.moveNextTo(prioritizationTable, Pack.Left, align=Pack.Middle)
|
||||||
|
|
||||||
|
# Main layout
|
||||||
|
promptLabel.packToCorner(Pack.UpperLeft)
|
||||||
|
promptLabel.fill(Pack.Right)
|
||||||
|
split.moveNextTo(promptLabel, Pack.Below)
|
||||||
|
buttonLayout.moveNextTo(split, Pack.Below)
|
||||||
|
buttonLayout.fill(Pack.Right)
|
||||||
|
split.fill(Pack.LowerRight)
|
||||||
|
|
||||||
|
promptLabel.setAnchor(Pack.UpperLeft, growX=True)
|
||||||
|
prioritizationTable.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
||||||
|
categoryPopup.setAnchor(Pack.UpperLeft, growX=True)
|
||||||
|
criteriaTable.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
||||||
|
split.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
||||||
|
buttonLayout.setAnchor(Pack.Below)
|
||||||
35
cocoa/base/ui/problem_dialog.py
Normal file
35
cocoa/base/ui/problem_dialog.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
ownerclass = 'ProblemDialog'
|
||||||
|
ownerimport = 'ProblemDialog.h'
|
||||||
|
|
||||||
|
result = Window(480, 310, "Problems!")
|
||||||
|
messageLabel = Label(result, "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.")
|
||||||
|
problemTable = TableView(result)
|
||||||
|
revealButton = Button(result, "Reveal")
|
||||||
|
closeButton = Button(result, "Close")
|
||||||
|
|
||||||
|
owner.problemTableView = problemTable
|
||||||
|
|
||||||
|
result.canMinimize = False
|
||||||
|
result.minSize = Size(300, 300)
|
||||||
|
closeButton.keyEquivalent = '\\r'
|
||||||
|
revealButton.action = Action(owner.model, 'revealSelected')
|
||||||
|
closeButton.action = Action(result, 'performClose:')
|
||||||
|
|
||||||
|
messageLabel.height *= 3 # 3 lines
|
||||||
|
revealButton.width = 150
|
||||||
|
closeButton.width = 98
|
||||||
|
|
||||||
|
messageLabel.packToCorner(Pack.UpperLeft)
|
||||||
|
messageLabel.fill(Pack.Right)
|
||||||
|
problemTable.packRelativeTo(messageLabel, Pack.Below)
|
||||||
|
problemTable.fill(Pack.Right)
|
||||||
|
revealButton.packRelativeTo(problemTable, Pack.Below)
|
||||||
|
closeButton.packRelativeTo(problemTable, Pack.Below, align=Pack.Right)
|
||||||
|
problemTable.fill(Pack.Below)
|
||||||
|
|
||||||
|
messageLabel.setAnchor(Pack.UpperLeft, growX=True)
|
||||||
|
problemTable.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
||||||
|
revealButton.setAnchor(Pack.LowerLeft)
|
||||||
|
closeButton.setAnchor(Pack.LowerRight)
|
||||||
97
cocoa/base/ui/result_window.py
Normal file
97
cocoa/base/ui/result_window.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
ownerclass = 'ResultWindowBase'
|
||||||
|
ownerimport = 'ResultWindowBase.h'
|
||||||
|
|
||||||
|
result = Window(557, 400, "dupeGuru Results")
|
||||||
|
toolbar = result.createToolbar('ResultsToolbar')
|
||||||
|
table = TableView(result)
|
||||||
|
table.OBJC_CLASS = 'HSTableView'
|
||||||
|
statsLabel = Label(result, "")
|
||||||
|
contextMenu = Menu("")
|
||||||
|
|
||||||
|
#Setup toolbar items
|
||||||
|
toolbar.displayMode = const.NSToolbarDisplayModeIconOnly
|
||||||
|
directoriesToolItem = toolbar.addItem('Directories', "Directories", image='folder32')
|
||||||
|
actionToolItem = toolbar.addItem('Action', "Action")
|
||||||
|
filterToolItem = toolbar.addItem('Filter', "Filter")
|
||||||
|
optionsToolItem = toolbar.addItem('Options', "Options")
|
||||||
|
quicklookToolItem = toolbar.addItem('QuickLook', "Quick Look")
|
||||||
|
toolbar.defaultItems = [actionToolItem, optionsToolItem, quicklookToolItem, directoriesToolItem,
|
||||||
|
toolbar.flexibleSpace(), filterToolItem]
|
||||||
|
actionPopup = Popup(None)
|
||||||
|
actionPopup.pullsdown = True
|
||||||
|
actionPopup.bezelStyle = const.NSTexturedRoundedBezelStyle
|
||||||
|
actionPopup.arrowPosition = const.NSPopUpArrowAtBottom
|
||||||
|
item = actionPopup.menu.addItem("") # First item is invisible
|
||||||
|
item.hidden = True
|
||||||
|
item.image = 'NSActionTemplate'
|
||||||
|
actionPopup.width = 44
|
||||||
|
actionToolItem.view = actionPopup
|
||||||
|
filterField = SearchField(None, "Filter")
|
||||||
|
filterField.action = Action(owner, 'filter')
|
||||||
|
filterField.sendsWholeSearchString = True
|
||||||
|
filterToolItem.view = filterField
|
||||||
|
filterToolItem.minSize = Size(80, 22)
|
||||||
|
filterToolItem.maxSize = Size(300, 22)
|
||||||
|
quickLookButton = Button(None, "")
|
||||||
|
quickLookButton.bezelStyle = const.NSTexturedRoundedBezelStyle
|
||||||
|
quickLookButton.image = 'NSQuickLookTemplate'
|
||||||
|
quickLookButton.width = 44
|
||||||
|
quickLookButton.action = Action(owner, 'toggleQuicklookPanel')
|
||||||
|
quicklookToolItem.view = quickLookButton
|
||||||
|
optionsSegments = SegmentedControl(None)
|
||||||
|
optionsSegments.segmentStyle = const.NSSegmentStyleCapsule
|
||||||
|
optionsSegments.trackingMode = const.NSSegmentSwitchTrackingSelectAny
|
||||||
|
optionsSegments.font = Font(FontFamily.System, 11)
|
||||||
|
optionsSegments.addSegment("Details", 57)
|
||||||
|
optionsSegments.addSegment("Dupes Only", 82)
|
||||||
|
optionsSegments.addSegment("Delta", 48)
|
||||||
|
optionsSegments.action = Action(owner, 'changeOptions')
|
||||||
|
optionsToolItem.view = optionsSegments
|
||||||
|
|
||||||
|
# Popuplate menus
|
||||||
|
actionPopup.menu.addItem("Send Marked to Trash...", action=Action(owner, 'trashMarked'))
|
||||||
|
actionPopup.menu.addItem("Move Marked to...", action=Action(owner, 'moveMarked'))
|
||||||
|
actionPopup.menu.addItem("Copy Marked to...", action=Action(owner, 'copyMarked'))
|
||||||
|
actionPopup.menu.addItem("Remove Marked from Results", action=Action(owner, 'removeMarked'))
|
||||||
|
actionPopup.menu.addSeparator()
|
||||||
|
for menu in (actionPopup.menu, contextMenu):
|
||||||
|
menu.addItem("Remove Selected from Results", action=Action(owner, 'removeSelected'))
|
||||||
|
menu.addItem("Add Selected to Ignore List", action=Action(owner, 'ignoreSelected'))
|
||||||
|
menu.addItem("Make Selected into Reference", action=Action(owner, 'switchSelected'))
|
||||||
|
menu.addSeparator()
|
||||||
|
menu.addItem("Open Selected with Default Application", action=Action(owner, 'openSelected'))
|
||||||
|
menu.addItem("Reveal Selected in Finder", action=Action(owner, 'revealSelected'))
|
||||||
|
menu.addItem("Rename Selected", action=Action(owner, 'renameSelected'))
|
||||||
|
|
||||||
|
# Doing connections
|
||||||
|
owner.filterField = filterField
|
||||||
|
owner.matches = table
|
||||||
|
owner.optionsSwitch = optionsSegments
|
||||||
|
owner.optionsToolbarItem = optionsToolItem
|
||||||
|
owner.stats = statsLabel
|
||||||
|
table.bind('rowHeight', defaults, 'values.TableFontSize', valueTransformer='vtRowHeightOffset')
|
||||||
|
|
||||||
|
# Rest of the setup
|
||||||
|
result.minSize = Size(340, 340)
|
||||||
|
result.autosaveName = 'MainWindow'
|
||||||
|
statsLabel.alignment = TextAlignment.Center
|
||||||
|
table.alternatingRows = True
|
||||||
|
table.menu = contextMenu
|
||||||
|
table.allowsColumnReordering = True
|
||||||
|
table.allowsColumnResizing = True
|
||||||
|
table.allowsColumnSelection = False
|
||||||
|
table.allowsEmptySelection = False
|
||||||
|
table.allowsMultipleSelection = True
|
||||||
|
table.allowsTypeSelect = True
|
||||||
|
table.gridStyleMask = const.NSTableViewSolidHorizontalGridLineMask
|
||||||
|
table.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
||||||
|
statsLabel.setAnchor(Pack.LowerLeft, growX=True)
|
||||||
|
|
||||||
|
# Layout
|
||||||
|
# It's a little weird to pack with a margin of -1, but if I don't do that, I get too thick of a
|
||||||
|
# border on the upper side of the table.
|
||||||
|
table.packToCorner(Pack.UpperLeft, margin=-1)
|
||||||
|
table.fill(Pack.Right, margin=0)
|
||||||
|
statsLabel.packRelativeTo(table, Pack.Below, margin=6)
|
||||||
|
statsLabel.fill(Pack.Right, margin=0)
|
||||||
|
table.fill(Pack.Below, margin=5)
|
||||||
10
cocoa/inter/all.py
Normal file
10
cocoa/inter/all.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from cocoa.inter import PyTextField, PyProgressWindow
|
||||||
|
from .deletion_options import PyDeletionOptions
|
||||||
|
from .details_panel import PyDetailsPanel
|
||||||
|
from .directory_outline import PyDirectoryOutline
|
||||||
|
from .prioritize_dialog import PyPrioritizeDialog
|
||||||
|
from .prioritize_list import PyPrioritizeList
|
||||||
|
from .problem_dialog import PyProblemDialog
|
||||||
|
from .ignore_list_dialog import PyIgnoreListDialog
|
||||||
|
from .result_table import PyResultTable
|
||||||
|
from .stats_label import PyStatsLabel
|
||||||
168
cocoa/inter/app.py
Normal file
168
cocoa/inter/app.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from objp.util import pyref, dontwrap
|
||||||
|
from cocoa import install_exception_hook, install_cocoa_logger, patch_threaded_job_performer
|
||||||
|
from cocoa.inter import PyBaseApp, BaseAppView
|
||||||
|
|
||||||
|
class DupeGuruView(BaseAppView):
|
||||||
|
def askYesNoWithPrompt_(self, prompt: str) -> bool: pass
|
||||||
|
def showProblemDialog(self): pass
|
||||||
|
def selectDestFolderWithPrompt_(self, prompt: str) -> str: pass
|
||||||
|
def selectDestFileWithPrompt_extension_(self, prompt: str, extension: str) -> str: pass
|
||||||
|
|
||||||
|
class PyDupeGuruBase(PyBaseApp):
|
||||||
|
@dontwrap
|
||||||
|
def _init(self, modelclass):
|
||||||
|
logging.basicConfig(level=logging.WARNING, format='%(levelname)s %(message)s')
|
||||||
|
install_exception_hook('https://github.com/hsoft/dupeguru/issues')
|
||||||
|
install_cocoa_logger()
|
||||||
|
patch_threaded_job_performer()
|
||||||
|
self.model = modelclass(self)
|
||||||
|
|
||||||
|
#---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
|
||||||
|
|
||||||
|
def progressWindow(self) -> pyref:
|
||||||
|
return self.model.progress_window
|
||||||
|
|
||||||
|
def deletionOptions(self) -> pyref:
|
||||||
|
return self.model.deletion_options
|
||||||
|
|
||||||
|
#---Directories
|
||||||
|
def addDirectory_(self, directory: str):
|
||||||
|
self.model.add_directory(directory)
|
||||||
|
|
||||||
|
#---Results
|
||||||
|
def doScan(self):
|
||||||
|
self.model.start_scanning()
|
||||||
|
|
||||||
|
def exportToXHTML(self):
|
||||||
|
self.model.export_to_xhtml()
|
||||||
|
|
||||||
|
def exportToCSV(self):
|
||||||
|
self.model.export_to_csv()
|
||||||
|
|
||||||
|
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 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
|
||||||
|
|
||||||
|
#--- model --> view
|
||||||
|
@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)
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def select_dest_file(self, prompt, extension):
|
||||||
|
return self.callback.selectDestFileWithPrompt_extension_(prompt, extension)
|
||||||
|
|
||||||
287
cocoa/inter/app_me.py
Normal file
287
cocoa/inter/app_me.py
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2006/11/16
|
||||||
|
# Copyright 2014 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 plistlib
|
||||||
|
import time
|
||||||
|
import os.path as op
|
||||||
|
from appscript import app, its, k, CommandError, ApplicationNotFoundError
|
||||||
|
from . import tunes
|
||||||
|
|
||||||
|
from cocoa import as_fetch, proxy
|
||||||
|
from hscommon.trans import trget
|
||||||
|
from hscommon.path import Path
|
||||||
|
from hscommon.util import remove_invalid_xml
|
||||||
|
|
||||||
|
from core import directories
|
||||||
|
from core.app import JobType, JOBID2TITLE
|
||||||
|
from core.scanner import ScanType
|
||||||
|
from core_me.app import DupeGuru as DupeGuruBase
|
||||||
|
from core_me import fs
|
||||||
|
from .app import 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, terms=tunes)
|
||||||
|
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 plistpath.exists():
|
||||||
|
return []
|
||||||
|
s = plistpath.open('rt', encoding='utf-8').read()
|
||||||
|
# iTunes sometimes produces XML files with invalid characters in it.
|
||||||
|
s = remove_invalid_xml(s, replace_with='')
|
||||||
|
plist = plistlib.readPlistFromBytes(s.encode('utf-8'))
|
||||||
|
result = []
|
||||||
|
for song_data in plist['Tracks'].values():
|
||||||
|
try:
|
||||||
|
if song_data['Track Type'] != 'File':
|
||||||
|
continue
|
||||||
|
song = ITunesSong(song_data)
|
||||||
|
except KeyError: # No "Track Type", "Location" or "Track ID" key in track
|
||||||
|
continue
|
||||||
|
if song.path.exists():
|
||||||
|
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):
|
||||||
|
DupeGuruBase.__init__(self, view)
|
||||||
|
# Use fileclasses set in DupeGuruBase.__init__()
|
||||||
|
self.directories = Directories(fileclasses=self.directories.fileclasses)
|
||||||
|
self.dead_tracks = []
|
||||||
|
|
||||||
|
def _do_delete(self, j, *args):
|
||||||
|
def op(dupe):
|
||||||
|
j.add_progress()
|
||||||
|
return self._do_delete_dupe(dupe, *args)
|
||||||
|
|
||||||
|
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, terms=tunes)
|
||||||
|
a.activate(timeout=0)
|
||||||
|
except (CommandError, RuntimeError, ApplicationNotFoundError):
|
||||||
|
pass
|
||||||
|
self.results.perform_on_marked(op, True)
|
||||||
|
|
||||||
|
def _do_delete_dupe(self, dupe, *args):
|
||||||
|
if isinstance(dupe, ITunesSong):
|
||||||
|
dupe.remove_from_library()
|
||||||
|
DupeGuruBase._do_delete_dupe(self, dupe, *args)
|
||||||
|
|
||||||
|
def _create_file(self, path):
|
||||||
|
if (self.directories.itunes_libpath is not None) and (path in self.directories.itunes_libpath.parent()):
|
||||||
|
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):
|
||||||
|
# XXX Just before release, I'm realizing that this piece of code below is why I was passing
|
||||||
|
# job exception as an argument to _job_completed(). I have to comment it for now. It's not
|
||||||
|
# the end of the world, but I should find an elegant solution to this at some point.
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
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, terms=tunes)
|
||||||
|
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, terms=tunes)
|
||||||
|
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._start_job(JobType.RemoveDeadTracks, do)
|
||||||
|
|
||||||
|
def scan_dead_tracks(self):
|
||||||
|
def do(j):
|
||||||
|
a = app(ITUNES, terms=tunes)
|
||||||
|
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._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)
|
||||||
342
cocoa/inter/app_pe.py
Normal file
342
cocoa/inter/app_pe.py
Normal file
@@ -0,0 +1,342 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2006/11/13
|
||||||
|
# Copyright 2014 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 re
|
||||||
|
import io
|
||||||
|
|
||||||
|
from appscript import app, its, k, CommandError, ApplicationNotFoundError
|
||||||
|
|
||||||
|
from hscommon.util import remove_invalid_xml, first
|
||||||
|
from hscommon.path import Path, pathify
|
||||||
|
from hscommon.trans import trget
|
||||||
|
from cocoa import proxy
|
||||||
|
|
||||||
|
from core.scanner import ScanType
|
||||||
|
from core import directories
|
||||||
|
from core.app import JobType
|
||||||
|
from core_pe import _block_osx
|
||||||
|
from core_pe.photo import Photo as PhotoBase
|
||||||
|
from core_pe.app import DupeGuru as DupeGuruBase
|
||||||
|
from core_pe.iphoto_plist import IPhotoPlistParser
|
||||||
|
from .app import PyDupeGuruBase
|
||||||
|
|
||||||
|
tr = trget('ui')
|
||||||
|
|
||||||
|
IPHOTO_PATH = Path('iPhoto Library')
|
||||||
|
APERTURE_PATH = Path('Aperture 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
|
||||||
|
|
||||||
|
def _get_exif_timestamp(self):
|
||||||
|
exifdata = proxy.readExifData_(str(self.path))
|
||||||
|
if exifdata:
|
||||||
|
try:
|
||||||
|
return exifdata['{Exif}']['DateTimeOriginal']
|
||||||
|
except KeyError:
|
||||||
|
return ''
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
|
class IPhoto(Photo):
|
||||||
|
def __init__(self, path, db_id):
|
||||||
|
# In IPhoto, we don't care about the db_id, we find photos by path.
|
||||||
|
Photo.__init__(self, path)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_folder_path(self):
|
||||||
|
return IPHOTO_PATH
|
||||||
|
|
||||||
|
class AperturePhoto(Photo):
|
||||||
|
def __init__(self, path, db_id):
|
||||||
|
Photo.__init__(self, path)
|
||||||
|
self.db_id = db_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def display_folder_path(self):
|
||||||
|
return APERTURE_PATH
|
||||||
|
|
||||||
|
@pathify
|
||||||
|
def get_iphoto_or_aperture_pictures(plistpath: Path, photo_class):
|
||||||
|
# The structure of iPhoto and Aperture libraries for the base photo list are excactly the same.
|
||||||
|
if not plistpath.exists():
|
||||||
|
return []
|
||||||
|
s = plistpath.open('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)
|
||||||
|
parser = IPhotoPlistParser()
|
||||||
|
try:
|
||||||
|
plist = parser.parse(io.BytesIO(s.encode('utf-8')))
|
||||||
|
except Exception:
|
||||||
|
logging.warning("iPhoto plist parsing choked on data: %r", parser.lastdata)
|
||||||
|
raise
|
||||||
|
result = []
|
||||||
|
for key, photo_data in plist['Master Image List'].items():
|
||||||
|
if photo_data['MediaType'] != 'Image':
|
||||||
|
continue
|
||||||
|
photo_path = Path(photo_data['ImagePath'])
|
||||||
|
photo = photo_class(photo_path, key)
|
||||||
|
result.append(photo)
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_iphoto_pictures(plistpath):
|
||||||
|
return get_iphoto_or_aperture_pictures(plistpath, IPhoto)
|
||||||
|
|
||||||
|
def get_aperture_pictures(plistpath):
|
||||||
|
return get_iphoto_or_aperture_pictures(plistpath, AperturePhoto)
|
||||||
|
|
||||||
|
def get_iapps_database_path(prefname):
|
||||||
|
plisturls = proxy.prefValue_inDomain_(prefname, 'com.apple.iApps')
|
||||||
|
if not plisturls:
|
||||||
|
raise directories.InvalidPathError()
|
||||||
|
plistpath = proxy.url2path_(plisturls[0])
|
||||||
|
return Path(plistpath)
|
||||||
|
|
||||||
|
def get_iphoto_database_path():
|
||||||
|
return get_iapps_database_path('iPhotoRecentDatabases')
|
||||||
|
|
||||||
|
def get_aperture_database_path():
|
||||||
|
return get_iapps_database_path('ApertureLibraries')
|
||||||
|
|
||||||
|
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.parent(), directories.DirectoryState.Excluded)
|
||||||
|
except directories.InvalidPathError:
|
||||||
|
self.iphoto_libpath = None
|
||||||
|
try:
|
||||||
|
self.aperture_libpath = get_aperture_database_path()
|
||||||
|
self.set_state(self.aperture_libpath.parent(), directories.DirectoryState.Excluded)
|
||||||
|
except directories.InvalidPathError:
|
||||||
|
self.aperture_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
|
||||||
|
elif from_path == APERTURE_PATH:
|
||||||
|
if self.aperture_libpath is None:
|
||||||
|
return []
|
||||||
|
is_ref = self.get_state(from_path) == directories.DirectoryState.Reference
|
||||||
|
photos = get_aperture_pictures(self.aperture_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 in {IPHOTO_PATH, APERTURE_PATH}:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return directories.Directories.get_subfolders(path)
|
||||||
|
|
||||||
|
def add_path(self, path):
|
||||||
|
if path in {IPHOTO_PATH, APERTURE_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 in {IPHOTO_PATH, APERTURE_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):
|
||||||
|
DupeGuruBase.__init__(self, view)
|
||||||
|
self.directories = Directories()
|
||||||
|
|
||||||
|
def _do_delete(self, j, *args):
|
||||||
|
def op(dupe):
|
||||||
|
j.add_progress()
|
||||||
|
return self._do_delete_dupe(dupe, *args)
|
||||||
|
|
||||||
|
self.deleted_aperture_photos = False
|
||||||
|
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
|
||||||
|
if any(isinstance(dupe, AperturePhoto) for dupe in marked):
|
||||||
|
self.deleted_aperture_photos = True
|
||||||
|
j.add_progress(0, desc=tr("Talking to Aperture. Don't touch it!"))
|
||||||
|
try:
|
||||||
|
a = app('Aperture')
|
||||||
|
a.activate(timeout=0)
|
||||||
|
except (CommandError, RuntimeError, ApplicationNotFoundError):
|
||||||
|
pass
|
||||||
|
self.results.perform_on_marked(op, True)
|
||||||
|
|
||||||
|
def _do_delete_dupe(self, dupe, *args):
|
||||||
|
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))
|
||||||
|
if isinstance(dupe, AperturePhoto):
|
||||||
|
try:
|
||||||
|
a = app('Aperture')
|
||||||
|
# I'm flying blind here. In my own test library, all photos are in an album with the
|
||||||
|
# id "LibraryFolder", so I'm going to guess that it's the case at least most of the
|
||||||
|
# time. As a safeguard, if we don't find any library with that id, we'll use the
|
||||||
|
# first album.
|
||||||
|
# Now, about deleting: All attempts I've made at sending photos to trash failed,
|
||||||
|
# even with normal applescript. So, what we're going to do here is to create a
|
||||||
|
# "dupeGuru Trash" project and tell the user to manually send those photos to trash.
|
||||||
|
libraries = a.libraries()
|
||||||
|
library = first(l for l in libraries if l.id == 'LibraryFolder')
|
||||||
|
if library is None:
|
||||||
|
library = libraries[0]
|
||||||
|
trash_project = a.projects["dupeGuru Trash"]
|
||||||
|
if trash_project.exists():
|
||||||
|
trash_project = trash_project()
|
||||||
|
else:
|
||||||
|
trash_project = library.make(new=k.project, with_properties={k.name: "dupeGuru Trash"})
|
||||||
|
[photo] = library.image_versions[its.id == dupe.db_id]()
|
||||||
|
photo.move(to=trash_project)
|
||||||
|
except (IndexError, ValueError):
|
||||||
|
msg = "Could not find photo '{}' in Aperture Library".format(str(dupe.path))
|
||||||
|
raise EnvironmentError(msg)
|
||||||
|
except (CommandError, RuntimeError) as e:
|
||||||
|
raise EnvironmentError(str(e))
|
||||||
|
else:
|
||||||
|
DupeGuruBase._do_delete_dupe(self, dupe, *args)
|
||||||
|
|
||||||
|
def _create_file(self, path):
|
||||||
|
if (self.directories.iphoto_libpath is not None) and (path in self.directories.iphoto_libpath.parent()):
|
||||||
|
if not hasattr(self, 'path2iphoto'):
|
||||||
|
photos = get_iphoto_pictures(self.directories.iphoto_libpath)
|
||||||
|
self.path2iphoto = {p.path: p for p in photos}
|
||||||
|
return self.path2iphoto.get(path)
|
||||||
|
if (self.directories.aperture_libpath is not None) and (path in self.directories.aperture_libpath.parent()):
|
||||||
|
if not hasattr(self, 'path2aperture'):
|
||||||
|
photos = get_aperture_pictures(self.directories.aperture_libpath)
|
||||||
|
self.path2aperture = {p.path: p for p in photos}
|
||||||
|
return self.path2aperture.get(path)
|
||||||
|
return DupeGuruBase._create_file(self, path)
|
||||||
|
|
||||||
|
def _job_completed(self, jobid):
|
||||||
|
DupeGuruBase._job_completed(self, jobid)
|
||||||
|
if jobid == JobType.Load:
|
||||||
|
if hasattr(self, 'path2iphoto'):
|
||||||
|
del self.path2iphoto
|
||||||
|
if hasattr(self, 'path2aperture'):
|
||||||
|
del self.path2aperture
|
||||||
|
if jobid == JobType.Delete and self.deleted_aperture_photos:
|
||||||
|
msg = tr("Deleted Aperture photos were sent to a project called \"dupeGuru Trash\".")
|
||||||
|
self.view.show_message(msg)
|
||||||
|
|
||||||
|
def copy_or_move(self, dupe, copy, destination, dest_type):
|
||||||
|
if isinstance(dupe, (IPhoto, AperturePhoto)):
|
||||||
|
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
|
||||||
104
cocoa/inter/app_se.py
Normal file
104
cocoa/inter/app_se.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2009-05-24
|
||||||
|
# Copyright 2014 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.path import Path, pathify
|
||||||
|
from cocoa import proxy
|
||||||
|
|
||||||
|
from core.scanner import ScanType
|
||||||
|
from core.directories import Directories as DirectoriesBase, DirectoryState
|
||||||
|
from core_se.app import DupeGuru as DupeGuruBase
|
||||||
|
from core_se import fs
|
||||||
|
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
|
||||||
|
@pathify
|
||||||
|
def can_handle(cls, path: Path):
|
||||||
|
return not path.islink() and path.isdir() 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])
|
||||||
|
self.folderclass = fs.Folder
|
||||||
|
|
||||||
|
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 = op.join(appdata, 'dupeGuru')
|
||||||
|
# print(repr(appdata))
|
||||||
|
DupeGuruBase.__init__(self, view)
|
||||||
|
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
|
||||||
|
|
||||||
37
cocoa/inter/deletion_options.py
Normal file
37
cocoa/inter/deletion_options.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Created On: 2012-05-30
|
||||||
|
# Copyright 2014 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
|
||||||
|
|
||||||
|
from objp.util import dontwrap
|
||||||
|
from cocoa.inter import PyGUIObject, GUIObjectView
|
||||||
|
|
||||||
|
class DeletionOptionsView(GUIObjectView):
|
||||||
|
def updateMsg_(self, msg: str): pass
|
||||||
|
def show(self) -> bool: pass
|
||||||
|
def setHardlinkOptionEnabled_(self, enabled: bool): pass
|
||||||
|
|
||||||
|
class PyDeletionOptions(PyGUIObject):
|
||||||
|
def setLinkDeleted_(self, link_deleted: bool):
|
||||||
|
self.model.link_deleted = link_deleted
|
||||||
|
|
||||||
|
def setUseHardlinks_(self, use_hardlinks: bool):
|
||||||
|
self.model.use_hardlinks = use_hardlinks
|
||||||
|
|
||||||
|
def setDirect_(self, direct: bool):
|
||||||
|
self.model.direct = direct
|
||||||
|
|
||||||
|
#--- model --> view
|
||||||
|
@dontwrap
|
||||||
|
def update_msg(self, msg):
|
||||||
|
self.callback.updateMsg_(msg)
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def show(self):
|
||||||
|
return self.callback.show()
|
||||||
|
|
||||||
|
@dontwrap
|
||||||
|
def set_hardlink_option_enabled(self, enabled):
|
||||||
|
self.callback.setHardlinkOptionEnabled_(enabled)
|
||||||
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)]
|
||||||
21
cocoa/inter/directory_outline.py
Normal file
21
cocoa/inter/directory_outline.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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()
|
||||||
|
|
||||||
|
def selectAll(self):
|
||||||
|
self.model.select_all()
|
||||||
|
|
||||||
|
# python --> cocoa
|
||||||
|
@dontwrap
|
||||||
|
def refresh_states(self):
|
||||||
|
# Under cocoa, both refresh() and refresh_states() do the same thing.
|
||||||
|
self.callback.refresh()
|
||||||
21
cocoa/inter/ignore_list_dialog.py
Normal file
21
cocoa/inter/ignore_list_dialog.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from objp.util import pyref, dontwrap
|
||||||
|
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
|
||||||
|
@dontwrap
|
||||||
|
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()
|
||||||
50
cocoa/inter/result_table.py
Normal file
50
cocoa/inter/result_table.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
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 valueForRow_column_(self, row_index: int, column: str) -> object:
|
||||||
|
return self.model.get_row_value(row_index, column)
|
||||||
|
|
||||||
|
def isDeltaAtRow_column_(self, row_index: int, column: str) -> bool:
|
||||||
|
row = self.model[row_index]
|
||||||
|
return row.is_cell_delta(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
|
||||||
282
cocoa/inter/tunes.py
Normal file
282
cocoa/inter/tunes.py
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
# Taken from https://github.com/abarnert/itunesterms
|
||||||
|
|
||||||
|
version = 1.1
|
||||||
|
path = '/Applications/iTunes.app'
|
||||||
|
|
||||||
|
classes = \
|
||||||
|
[('print_settings', b'pset'),
|
||||||
|
('application', b'capp'),
|
||||||
|
('artwork', b'cArt'),
|
||||||
|
('audio_CD_playlist', b'cCDP'),
|
||||||
|
('audio_CD_track', b'cCDT'),
|
||||||
|
('browser_window', b'cBrW'),
|
||||||
|
('device_playlist', b'cDvP'),
|
||||||
|
('device_track', b'cDvT'),
|
||||||
|
('encoder', b'cEnc'),
|
||||||
|
('EQ_preset', b'cEQP'),
|
||||||
|
('EQ_window', b'cEQW'),
|
||||||
|
('file_track', b'cFlT'),
|
||||||
|
('folder_playlist', b'cFoP'),
|
||||||
|
('item', b'cobj'),
|
||||||
|
('library_playlist', b'cLiP'),
|
||||||
|
('playlist', b'cPly'),
|
||||||
|
('playlist_window', b'cPlW'),
|
||||||
|
('radio_tuner_playlist', b'cRTP'),
|
||||||
|
('shared_track', b'cShT'),
|
||||||
|
('source', b'cSrc'),
|
||||||
|
('track', b'cTrk'),
|
||||||
|
('URL_track', b'cURT'),
|
||||||
|
('user_playlist', b'cUsP'),
|
||||||
|
('visual', b'cVis'),
|
||||||
|
('window', b'cwin')]
|
||||||
|
|
||||||
|
enums = \
|
||||||
|
[('track_listing', b'kTrk'),
|
||||||
|
('album_listing', b'kAlb'),
|
||||||
|
('cd_insert', b'kCDi'),
|
||||||
|
('standard', b'lwst'),
|
||||||
|
('detailed', b'lwdt'),
|
||||||
|
('stopped', b'kPSS'),
|
||||||
|
('playing', b'kPSP'),
|
||||||
|
('paused', b'kPSp'),
|
||||||
|
('fast_forwarding', b'kPSF'),
|
||||||
|
('rewinding', b'kPSR'),
|
||||||
|
('off', b'kRpO'),
|
||||||
|
('one', b'kRp1'),
|
||||||
|
('all', b'kAll'),
|
||||||
|
('small', b'kVSS'),
|
||||||
|
('medium', b'kVSM'),
|
||||||
|
('large', b'kVSL'),
|
||||||
|
('library', b'kLib'),
|
||||||
|
('iPod', b'kPod'),
|
||||||
|
('audio_CD', b'kACD'),
|
||||||
|
('MP3_CD', b'kMCD'),
|
||||||
|
('device', b'kDev'),
|
||||||
|
('radio_tuner', b'kTun'),
|
||||||
|
('shared_library', b'kShd'),
|
||||||
|
('unknown', b'kUnk'),
|
||||||
|
('albums', b'kSrL'),
|
||||||
|
('artists', b'kSrR'),
|
||||||
|
('composers', b'kSrC'),
|
||||||
|
('displayed', b'kSrV'),
|
||||||
|
('songs', b'kSrS'),
|
||||||
|
('none', b'kNon'),
|
||||||
|
('Books', b'kSpA'),
|
||||||
|
('folder', b'kSpF'),
|
||||||
|
('Genius', b'kSpG'),
|
||||||
|
('iTunes_U', b'kSpU'),
|
||||||
|
('Library', b'kSpL'),
|
||||||
|
('Movies', b'kSpI'),
|
||||||
|
('Music', b'kSpZ'),
|
||||||
|
('Party_Shuffle', b'kSpS'),
|
||||||
|
('Podcasts', b'kSpP'),
|
||||||
|
('Purchased_Music', b'kSpM'),
|
||||||
|
('TV_Shows', b'kSpT'),
|
||||||
|
('movie', b'kVdM'),
|
||||||
|
('music_video', b'kVdV'),
|
||||||
|
('TV_show', b'kVdT'),
|
||||||
|
('user', b'kRtU'),
|
||||||
|
('computed', b'kRtC')]
|
||||||
|
|
||||||
|
properties = \
|
||||||
|
[('copies', b'lwcp'),
|
||||||
|
('collating', b'lwcl'),
|
||||||
|
('starting_page', b'lwfp'),
|
||||||
|
('ending_page', b'lwlp'),
|
||||||
|
('pages_across', b'lwla'),
|
||||||
|
('pages_down', b'lwld'),
|
||||||
|
('error_handling', b'lweh'),
|
||||||
|
('requested_print_time', b'lwqt'),
|
||||||
|
('printer_features', b'lwpf'),
|
||||||
|
('fax_number', b'faxn'),
|
||||||
|
('target_printer', b'trpr'),
|
||||||
|
('current_encoder', b'pEnc'),
|
||||||
|
('current_EQ_preset', b'pEQP'),
|
||||||
|
('current_playlist', b'pPla'),
|
||||||
|
('current_stream_title', b'pStT'),
|
||||||
|
('current_stream_URL', b'pStU'),
|
||||||
|
('current_track', b'pTrk'),
|
||||||
|
('current_visual', b'pVis'),
|
||||||
|
('EQ_enabled', b'pEQ '),
|
||||||
|
('fixed_indexing', b'pFix'),
|
||||||
|
('frontmost', b'pisf'),
|
||||||
|
('full_screen', b'pFSc'),
|
||||||
|
('name', b'pnam'),
|
||||||
|
('mute', b'pMut'),
|
||||||
|
('player_position', b'pPos'),
|
||||||
|
('player_state', b'pPlS'),
|
||||||
|
('selection', b'sele'),
|
||||||
|
('sound_volume', b'pVol'),
|
||||||
|
('version', b'vers'),
|
||||||
|
('visuals_enabled', b'pVsE'),
|
||||||
|
('visual_size', b'pVSz'),
|
||||||
|
('data', b'pPCT'),
|
||||||
|
('description', b'pDes'),
|
||||||
|
('downloaded', b'pDlA'),
|
||||||
|
('format', b'pFmt'),
|
||||||
|
('kind', b'pKnd'),
|
||||||
|
('raw_data', b'pRaw'),
|
||||||
|
('artist', b'pArt'),
|
||||||
|
('compilation', b'pAnt'),
|
||||||
|
('composer', b'pCmp'),
|
||||||
|
('disc_count', b'pDsC'),
|
||||||
|
('disc_number', b'pDsN'),
|
||||||
|
('genre', b'pGen'),
|
||||||
|
('year', b'pYr '),
|
||||||
|
('location', b'pLoc'),
|
||||||
|
('minimized', b'pMin'),
|
||||||
|
('view', b'pPly'),
|
||||||
|
('band_1', b'pEQ1'),
|
||||||
|
('band_2', b'pEQ2'),
|
||||||
|
('band_3', b'pEQ3'),
|
||||||
|
('band_4', b'pEQ4'),
|
||||||
|
('band_5', b'pEQ5'),
|
||||||
|
('band_6', b'pEQ6'),
|
||||||
|
('band_7', b'pEQ7'),
|
||||||
|
('band_8', b'pEQ8'),
|
||||||
|
('band_9', b'pEQ9'),
|
||||||
|
('band_10', b'pEQ0'),
|
||||||
|
('modifiable', b'pMod'),
|
||||||
|
('preamp', b'pEQA'),
|
||||||
|
('update_tracks', b'pUTC'),
|
||||||
|
('container', b'ctnr'),
|
||||||
|
('id', b'ID '),
|
||||||
|
('index', b'pidx'),
|
||||||
|
('persistent_ID', b'pPIS'),
|
||||||
|
('duration', b'pDur'),
|
||||||
|
('parent', b'pPlP'),
|
||||||
|
('shuffle', b'pShf'),
|
||||||
|
('size', b'pSiz'),
|
||||||
|
('song_repeat', b'pRpt'),
|
||||||
|
('special_kind', b'pSpK'),
|
||||||
|
('time', b'pTim'),
|
||||||
|
('visible', b'pvis'),
|
||||||
|
('capacity', b'capa'),
|
||||||
|
('free_space', b'frsp'),
|
||||||
|
('album', b'pAlb'),
|
||||||
|
('album_artist', b'pAlA'),
|
||||||
|
('album_rating', b'pAlR'),
|
||||||
|
('album_rating_kind', b'pARk'),
|
||||||
|
('bit_rate', b'pBRt'),
|
||||||
|
('bookmark', b'pBkt'),
|
||||||
|
('bookmarkable', b'pBkm'),
|
||||||
|
('bpm', b'pBPM'),
|
||||||
|
('category', b'pCat'),
|
||||||
|
('comment', b'pCmt'),
|
||||||
|
('database_ID', b'pDID'),
|
||||||
|
('date_added', b'pAdd'),
|
||||||
|
('enabled', b'enbl'),
|
||||||
|
('episode_ID', b'pEpD'),
|
||||||
|
('episode_number', b'pEpN'),
|
||||||
|
('EQ', b'pEQp'),
|
||||||
|
('finish', b'pStp'),
|
||||||
|
('gapless', b'pGpl'),
|
||||||
|
('grouping', b'pGrp'),
|
||||||
|
('long_description', b'pLds'),
|
||||||
|
('lyrics', b'pLyr'),
|
||||||
|
('modification_date', b'asmo'),
|
||||||
|
('played_count', b'pPlC'),
|
||||||
|
('played_date', b'pPlD'),
|
||||||
|
('podcast', b'pTPc'),
|
||||||
|
('rating', b'pRte'),
|
||||||
|
('rating_kind', b'pRtk'),
|
||||||
|
('release_date', b'pRlD'),
|
||||||
|
('sample_rate', b'pSRt'),
|
||||||
|
('season_number', b'pSeN'),
|
||||||
|
('shufflable', b'pSfa'),
|
||||||
|
('skipped_count', b'pSkC'),
|
||||||
|
('skipped_date', b'pSkD'),
|
||||||
|
('show', b'pShw'),
|
||||||
|
('sort_album', b'pSAl'),
|
||||||
|
('sort_artist', b'pSAr'),
|
||||||
|
('sort_album_artist', b'pSAA'),
|
||||||
|
('sort_name', b'pSNm'),
|
||||||
|
('sort_composer', b'pSCm'),
|
||||||
|
('sort_show', b'pSSN'),
|
||||||
|
('start', b'pStr'),
|
||||||
|
('track_count', b'pTrC'),
|
||||||
|
('track_number', b'pTrN'),
|
||||||
|
('unplayed', b'pUnp'),
|
||||||
|
('video_kind', b'pVdK'),
|
||||||
|
('volume_adjustment', b'pAdj'),
|
||||||
|
('address', b'pURL'),
|
||||||
|
('shared', b'pShr'),
|
||||||
|
('smart', b'pSmt'),
|
||||||
|
('bounds', b'pbnd'),
|
||||||
|
('closeable', b'hclb'),
|
||||||
|
('collapseable', b'pWSh'),
|
||||||
|
('collapsed', b'wshd'),
|
||||||
|
('position', b'ppos'),
|
||||||
|
('resizable', b'prsz'),
|
||||||
|
('zoomable', b'iszm'),
|
||||||
|
('zoomed', b'pzum')]
|
||||||
|
|
||||||
|
elements = \
|
||||||
|
[('artworks', b'cArt'),
|
||||||
|
('audio_CD_playlists', b'cCDP'),
|
||||||
|
('audio_CD_tracks', b'cCDT'),
|
||||||
|
('browser_windows', b'cBrW'),
|
||||||
|
('device_playlists', b'cDvP'),
|
||||||
|
('device_tracks', b'cDvT'),
|
||||||
|
('encoders', b'cEnc'),
|
||||||
|
('EQ_presets', b'cEQP'),
|
||||||
|
('EQ_windows', b'cEQW'),
|
||||||
|
('file_tracks', b'cFlT'),
|
||||||
|
('folder_playlists', b'cFoP'),
|
||||||
|
('items', b'cobj'),
|
||||||
|
('library_playlists', b'cLiP'),
|
||||||
|
('playlists', b'cPly'),
|
||||||
|
('playlist_windows', b'cPlW'),
|
||||||
|
('radio_tuner_playlists', b'cRTP'),
|
||||||
|
('shared_tracks', b'cShT'),
|
||||||
|
('sources', b'cSrc'),
|
||||||
|
('tracks', b'cTrk'),
|
||||||
|
('URL_tracks', b'cURT'),
|
||||||
|
('user_playlists', b'cUsP'),
|
||||||
|
('visuals', b'cVis'),
|
||||||
|
('windows', b'cwin'),
|
||||||
|
('application', b'capp'),
|
||||||
|
('print_settings', b'pset')]
|
||||||
|
|
||||||
|
commands = \
|
||||||
|
[('set', b'coresetd', [('to', b'data')]),
|
||||||
|
('exists', b'coredoex', []),
|
||||||
|
('move', b'coremove', [('to', b'insh')]),
|
||||||
|
('subscribe', b'hookpSub', []),
|
||||||
|
('playpause', b'hookPlPs', []),
|
||||||
|
('download', b'hookDwnl', []),
|
||||||
|
('close', b'coreclos', []),
|
||||||
|
('open', b'aevtodoc', []),
|
||||||
|
('open_location', b'GURLGURL', []),
|
||||||
|
('quit', b'aevtquit', []),
|
||||||
|
('pause', b'hookPaus', []),
|
||||||
|
('make',
|
||||||
|
'corecrel',
|
||||||
|
[('new', b'kocl'), ('at', b'insh'), ('with_properties', b'prdt')]),
|
||||||
|
('duplicate', b'coreclon', [('to', b'insh')]),
|
||||||
|
('print_',
|
||||||
|
'aevtpdoc',
|
||||||
|
[('print_dialog', b'pdlg'),
|
||||||
|
('with_properties', b'prdt'),
|
||||||
|
('kind', b'pKnd'),
|
||||||
|
('theme', b'pThm')]),
|
||||||
|
('add', b'hookAdd ', [('to', b'insh')]),
|
||||||
|
('rewind', b'hookRwnd', []),
|
||||||
|
('play', b'hookPlay', [('once', b'POne')]),
|
||||||
|
('run', b'aevtoapp', []),
|
||||||
|
('resume', b'hookResu', []),
|
||||||
|
('updatePodcast', b'hookUpd1', []),
|
||||||
|
('next_track', b'hookNext', []),
|
||||||
|
('stop', b'hookStop', []),
|
||||||
|
('search', b'hookSrch', [('for_', b'pTrm'), ('only', b'pAre')]),
|
||||||
|
('updateAllPodcasts', b'hookUpdp', []),
|
||||||
|
('update', b'hookUpdt', []),
|
||||||
|
('previous_track', b'hookPrev', []),
|
||||||
|
('fast_forward', b'hookFast', []),
|
||||||
|
('count', b'corecnte', [('each', b'kocl')]),
|
||||||
|
('reveal', b'hookRevl', []),
|
||||||
|
('convert', b'hookConv', []),
|
||||||
|
('eject', b'hookEjct', []),
|
||||||
|
('back_track', b'hookBack', []),
|
||||||
|
('refresh', b'hookRfrs', []),
|
||||||
|
('delete', b'coredelo', [])]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -7,10 +7,10 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "../base/AppDelegate.h"
|
#import "AppDelegateBase.h"
|
||||||
#import "ResultWindow.h"
|
#import "ResultWindow.h"
|
||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase {}
|
@interface AppDelegate : AppDelegateBase {}
|
||||||
- (PyDupeGuru *)py;
|
- (void)removeDeadTracks;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -17,31 +17,20 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
#import "Consts.h"
|
#import "Consts.h"
|
||||||
|
|
||||||
@implementation AppDelegate
|
@implementation AppDelegate
|
||||||
+ (void)initialize
|
+ (NSDictionary *)defaultPreferences
|
||||||
{
|
{
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:[super defaultPreferences]];
|
||||||
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:10];
|
|
||||||
[d setObject:i2n(3) forKey:@"scanType"];
|
[d setObject:i2n(3) forKey:@"scanType"];
|
||||||
[d setObject:i2n(80) forKey:@"minMatchPercentage"];
|
[d setObject:i2n(80) forKey:@"minMatchPercentage"];
|
||||||
[d setObject:i2n(1) forKey:@"recreatePathType"];
|
|
||||||
[d setObject:b2n(NO) forKey:@"wordWeighting"];
|
[d setObject:b2n(NO) forKey:@"wordWeighting"];
|
||||||
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
|
[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(NO) forKey:@"scanTagTrack"];
|
||||||
[d setObject:b2n(YES) forKey:@"scanTagArtist"];
|
[d setObject:b2n(YES) forKey:@"scanTagArtist"];
|
||||||
[d setObject:b2n(YES) forKey:@"scanTagAlbum"];
|
[d setObject:b2n(YES) forKey:@"scanTagAlbum"];
|
||||||
[d setObject:b2n(YES) forKey:@"scanTagTitle"];
|
[d setObject:b2n(YES) forKey:@"scanTagTitle"];
|
||||||
[d setObject:b2n(NO) forKey:@"scanTagGenre"];
|
[d setObject:b2n(NO) forKey:@"scanTagGenre"];
|
||||||
[d setObject:b2n(NO) forKey:@"scanTagYear"];
|
[d setObject:b2n(NO) forKey:@"scanTagYear"];
|
||||||
[d setObject:[NSArray array] forKey:@"recentDirectories"];
|
return d;
|
||||||
[d setObject:[NSArray array] forKey:@"columnsOrder"];
|
|
||||||
[d setObject:[NSDictionary dictionary] forKey:@"columnsWidth"];
|
|
||||||
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:d];
|
|
||||||
[ud registerDefaults:d];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)init
|
- (id)init
|
||||||
@@ -72,15 +61,8 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
return [[DirectoryPanelME alloc] initWithParentApp:self];
|
return [[DirectoryPanelME alloc] initWithParentApp:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (PyDupeGuru *)py { return (PyDupeGuru *)py; }
|
- (void)removeDeadTracks
|
||||||
|
|
||||||
//Delegate
|
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
||||||
{
|
{
|
||||||
// index 3 is just after "Export Results to XHTML"
|
[(ResultWindow *)[self resultWindow] removeDeadTracks];
|
||||||
NSMenuItem *mi = [actionsMenu insertItemWithTitle:TR(@"Remove Dead Tracks in iTunes")
|
|
||||||
action:@selector(removeDeadTracks:) keyEquivalent:@"" atIndex:3];
|
|
||||||
[mi setTarget:[self resultWindow]];
|
|
||||||
[super applicationDidFinishLaunching:aNotification];
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -7,8 +7,7 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "PyGUI.h"
|
#import "DetailsPanelBase.h"
|
||||||
|
|
||||||
@interface PyStatsLabel : PyGUI
|
@interface DetailsPanel : DetailsPanelBase
|
||||||
- (NSString *)display;
|
|
||||||
@end
|
@end
|
||||||
17
cocoa/me/DetailsPanel.m
Normal file
17
cocoa/me/DetailsPanel.m
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 "DetailsPanel_UI.h"
|
||||||
|
|
||||||
|
@implementation DetailsPanel
|
||||||
|
- (NSWindow *)createWindow
|
||||||
|
{
|
||||||
|
return createDetailsPanel_UI(self);
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2014 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
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
|
which should be included with this package. The terms are also available at
|
||||||
@@ -7,13 +7,11 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#import "DirectoryPanel.h"
|
#import "DirectoryPanel.h"
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation DirectoryPanelME
|
@implementation DirectoryPanelME
|
||||||
- (id)initWithParentApp:(id)aParentApp
|
- (id)initWithParentApp:(id)aParentApp
|
||||||
{
|
{
|
||||||
self = [super initWithParentApp:aParentApp];
|
self = [super initWithParentApp:aParentApp];
|
||||||
[[self window] setTitle:@"dupeGuru Music Edition"];
|
|
||||||
_alwaysShowPopUp = YES;
|
_alwaysShowPopUp = YES;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
@@ -22,13 +20,13 @@ http://www.hardcoded.net/licenses/bsd_license
|
|||||||
{
|
{
|
||||||
[super fillPopUpMenu];
|
[super fillPopUpMenu];
|
||||||
NSMenu *m = [addButtonPopUp menu];
|
NSMenu *m = [addButtonPopUp menu];
|
||||||
NSMenuItem *mi = [m insertItemWithTitle:TR(@"Add iTunes Directory") action:@selector(addiTunes:)
|
NSMenuItem *mi = [m insertItemWithTitle:NSLocalizedString(@"Add iTunes Library", @"") action:@selector(addiTunes:)
|
||||||
keyEquivalent:@"" atIndex:1];
|
keyEquivalent:@"" atIndex:1];
|
||||||
[mi setTarget:self];
|
[mi setTarget:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)addiTunes:(id)sender
|
- (IBAction)addiTunes:(id)sender
|
||||||
{
|
{
|
||||||
[self addDirectory:[@"~/Music/iTunes/iTunes Music" stringByExpandingTildeInPath]];
|
[self addDirectory:@"iTunes Library"];
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>English</string>
|
<string>English</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>${EXECUTABLE_NAME}</string>
|
<string>dupeGuru</string>
|
||||||
<key>CFBundleHelpBookFolder</key>
|
<key>CFBundleHelpBookFolder</key>
|
||||||
<string>dupeguru_me_help</string>
|
<string>dupeguru_me_help</string>
|
||||||
<key>CFBundleHelpBookName</key>
|
<key>CFBundleHelpBookName</key>
|
||||||
@@ -13,15 +13,17 @@
|
|||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>dupeguru</string>
|
<string>dupeguru</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.hardcoded_software.dupeguru_me</string>
|
<string>com.hardcoded-software.dupeguru-me</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>${PRODUCT_NAME}</string>
|
<string>dupeGuru ME</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>hsft</string>
|
<string>hsft</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>{version}</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>{version}</string>
|
<string>{version}</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
@@ -29,7 +31,7 @@
|
|||||||
<key>NSPrincipalClass</key>
|
<key>NSPrincipalClass</key>
|
||||||
<string>NSApplication</string>
|
<string>NSApplication</string>
|
||||||
<key>NSHumanReadableCopyright</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>© Hardcoded Software, 2010</string>
|
<string>© Hardcoded Software, 2013</string>
|
||||||
<key>SUFeedURL</key>
|
<key>SUFeedURL</key>
|
||||||
<string>http://www.hardcoded.net/updates/dupeguru_me.appcast</string>
|
<string>http://www.hardcoded.net/updates/dupeguru_me.appcast</string>
|
||||||
<key>SUPublicDSAKeyFile</key>
|
<key>SUPublicDSAKeyFile</key>
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 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/PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface PyDupeGuru : PyDupeGuruBase
|
|
||||||
//Scanning options
|
|
||||||
- (void)setScanType:(NSNumber *)scan_type;
|
|
||||||
- (void)setMinWordCount:(NSNumber *)word_count;
|
|
||||||
- (void)setMinWordLength:(NSNumber *)word_length;
|
|
||||||
- (void)setWordWeighting:(NSNumber *)words_are_weighted;
|
|
||||||
- (void)setMatchSimilarWords:(NSNumber *)match_similar_words;
|
|
||||||
- (void)enable:(NSNumber *)enable scanForTag:(NSString *)tag;
|
|
||||||
- (void)scanDeadTracks;
|
|
||||||
- (void)removeDeadTracks;
|
|
||||||
- (NSInteger)deadTrackCount;
|
|
||||||
@end
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user