mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-25 16:11:39 +00:00
Compare commits
1090 Commits
se2.9.1
...
trigger-ha
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bd0ec8875 | ||
|
|
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 | ||
|
|
4249c528e9 | ||
|
|
084068852e | ||
|
|
c524a85897 | ||
|
|
d39d46be5a | ||
|
|
b8980b4667 | ||
|
|
e0adec7b2b | ||
|
|
eb8b9d663f | ||
|
|
fa4b0cf9ec | ||
|
|
f72db8dd1d | ||
|
|
c5bf0f228a | ||
|
|
e150b26cab | ||
|
|
da41d07dae | ||
|
|
c885cb35d8 | ||
|
|
7c38217308 | ||
|
|
a88519b814 | ||
|
|
0aa91b170c | ||
|
|
e9bb1c01f7 | ||
|
|
883875e88e | ||
|
|
4cab6b0ad2 | ||
|
|
91a2664830 | ||
|
|
6abbeaf987 | ||
|
|
21efef42f7 | ||
|
|
9d0e8d94ca | ||
|
|
0f57ca698c | ||
|
|
69c572e875 | ||
|
|
4083b60ff3 | ||
|
|
41fbeb7ec9 | ||
|
|
1162357b9b | ||
|
|
a2a526866f | ||
|
|
d3338b699e | ||
|
|
6c60e76b55 | ||
|
|
8a0d31f612 | ||
|
|
6fc7e5ace1 | ||
|
|
8175762e74 | ||
|
|
f48e14af8a | ||
|
|
cd2a61d926 | ||
|
|
f45997afe4 | ||
|
|
b6f56721cb | ||
|
|
f9e7e82772 | ||
|
|
dbcd7b63d8 | ||
|
|
bf807684dd | ||
|
|
f02fcb5e4b | ||
|
|
2c127adf59 | ||
|
|
7f8a357019 | ||
|
|
99daf5b7b7 | ||
|
|
42cff20710 | ||
|
|
04d7880a0c | ||
|
|
e7d26e3f82 | ||
|
|
19308bf686 | ||
|
|
92970489c5 | ||
|
|
d51f5184d7 | ||
|
|
30eb26af7d | ||
|
|
3ea43f8213 | ||
|
|
9833067ba7 | ||
|
|
ad3114c56b | ||
|
|
9da9c269c1 | ||
|
|
0a22bb8469 | ||
|
|
c9fd1b1a17 | ||
|
|
19b40d45c0 | ||
|
|
90e2a1cda0 | ||
|
|
5e47b9f4a7 | ||
|
|
50b6948250 | ||
|
|
3ef118c9fa | ||
|
|
064707db43 | ||
|
|
8f71a1318d | ||
|
|
1b8ab35fdd | ||
|
|
4a1fe2f8ab | ||
|
|
e6e4e14781 | ||
|
|
d139157234 | ||
|
|
94104f4e03 | ||
|
|
8bea978715 | ||
|
|
eefe464fba | ||
|
|
33c0ba808c | ||
|
|
e0cc8ecda2 | ||
|
|
2d423b2358 | ||
|
|
b5b27b141c | ||
|
|
800a879927 | ||
|
|
f6806e42db | ||
|
|
3aae21b810 | ||
|
|
75239d6a64 | ||
|
|
09082955a3 | ||
|
|
6a6f2d51aa | ||
|
|
7b0d3ea8ac | ||
|
|
1c88b6bb26 | ||
|
|
e5e8e5d908 | ||
|
|
92fadd26b7 | ||
|
|
45d783ac43 | ||
|
|
ea9e76e7ae | ||
|
|
28426c0e91 | ||
|
|
3a9f51b600 | ||
|
|
f1b4db368e | ||
|
|
95efac187b | ||
|
|
6770d22438 | ||
|
|
4ce97613c4 | ||
|
|
030eb8eb6e | ||
|
|
c9da8e26e6 | ||
|
|
7ddf9772df | ||
|
|
0382ad1534 | ||
|
|
1b6e1369a0 | ||
|
|
835050c337 | ||
|
|
ca6a42e6eb | ||
|
|
a2e4d893ac | ||
|
|
657520b0b3 | ||
|
|
ea4b87895c | ||
|
|
19db500a19 | ||
|
|
1366cfd478 | ||
|
|
56a6df1f68 | ||
|
|
a1b35a8abf | ||
|
|
8a8a181186 | ||
|
|
463a551f7d | ||
|
|
fc613fb325 | ||
|
|
4517bea664 | ||
|
|
81dcfbe6ae | ||
|
|
fa8e64d04a | ||
|
|
562123b219 | ||
|
|
b217309618 | ||
|
|
357a02c74b | ||
|
|
508eeffa6e | ||
|
|
31555aa473 | ||
|
|
d2f968def7 | ||
|
|
d574bc611b | ||
|
|
a50a3b0123 | ||
|
|
5b6891dd45 | ||
|
|
4886982d43 | ||
|
|
7360f57beb | ||
|
|
491279b7a8 | ||
|
|
05b79f81af | ||
|
|
96ef2f2dd3 | ||
|
|
2542af17b6 | ||
|
|
c86bc649ff | ||
|
|
4b8e48ed88 | ||
|
|
a1addfd416 | ||
|
|
a1a57d8933 | ||
|
|
864970b860 | ||
|
|
a056be0842 | ||
|
|
c672e75739 | ||
|
|
7b5dd3f964 | ||
|
|
a6072f608b | ||
|
|
06462c65a5 | ||
|
|
359f9c0680 | ||
|
|
01db7c4948 | ||
|
|
f67f14a78d | ||
|
|
0a64d653e1 | ||
|
|
456a835285 | ||
|
|
0d8ed92a68 | ||
|
|
9bd093a03c | ||
|
|
361d4698a9 | ||
|
|
b342b15011 | ||
|
|
95638a3a80 | ||
|
|
2204fe3355 | ||
|
|
abcd774c9d | ||
|
|
ee209f8f88 | ||
|
|
b1f2e1c191 | ||
|
|
33f372f6c6 | ||
|
|
8e5c2a8875 | ||
|
|
36f3638ae4 | ||
|
|
d10210011f | ||
|
|
e867840d81 | ||
|
|
fb7e3189a8 | ||
|
|
5733c0143b | ||
|
|
ac4881f231 | ||
|
|
939efd7dab | ||
|
|
a93d96d742 | ||
|
|
f21804c769 | ||
|
|
4bc05a8d46 | ||
|
|
eebe2b0e80 | ||
|
|
250a496a78 | ||
|
|
29163ed053 | ||
|
|
cc05661f9e | ||
|
|
89409c22d1 | ||
|
|
e2f240ebc9 | ||
|
|
8d56f4c33b | ||
|
|
36eccb7122 | ||
|
|
c8827769b4 | ||
|
|
12e6c400b9 | ||
|
|
4c273a7910 | ||
|
|
58da335b17 | ||
|
|
5b2d506462 | ||
|
|
531430d44a | ||
|
|
7450eec7eb | ||
|
|
3a5802435f | ||
|
|
1b6b058097 | ||
|
|
a5797a2350 | ||
|
|
e81a5147c5 | ||
|
|
565c990687 | ||
|
|
0ccdfe0e26 | ||
|
|
f8a558e3a7 | ||
|
|
c5fa195cc6 | ||
|
|
3a821edd45 | ||
|
|
854d194f88 | ||
|
|
fb79daad6a | ||
|
|
b2ae0e8759 | ||
|
|
09f73988b3 | ||
|
|
9e6f289319 | ||
|
|
d2a55ffd31 | ||
|
|
793c2aa423 | ||
|
|
5daa332b6c | ||
|
|
d5511a857c | ||
|
|
7fecd21331 | ||
|
|
88b79e512f | ||
|
|
853bf63777 | ||
|
|
ff16fea54a | ||
|
|
a03e2a69d4 | ||
|
|
56a39df635 | ||
|
|
ac1593ff75 | ||
|
|
4d66b4667c | ||
|
|
fdde538b66 | ||
|
|
de1147219c | ||
|
|
371426a08e | ||
|
|
75eb005ba0 | ||
|
|
601b67145c | ||
|
|
c65afbc057 | ||
|
|
378589a473 | ||
|
|
fa264972a4 | ||
|
|
6b10e01c03 | ||
|
|
5a6d74ab37 | ||
|
|
73f1bb6968 | ||
|
|
d1a7f51859 | ||
|
|
2ae16396a6 | ||
|
|
ef090a5dc5 | ||
|
|
5c0799e82b | ||
|
|
fa2ee01d3f | ||
|
|
d6ba80bd3f | ||
|
|
ee96d5f88c | ||
|
|
e96a917bef | ||
|
|
769b816998 | ||
|
|
ff891c210c | ||
|
|
3ed5e1bf95 | ||
|
|
5bc8581389 | ||
|
|
7346b422d5 | ||
|
|
5c80ac1c74 | ||
|
|
699023992c | ||
|
|
454ce604ad | ||
|
|
1e0f6bfecb | ||
|
|
7f10aa3de2 | ||
|
|
f8764ab85e | ||
|
|
aa8544308e | ||
|
|
31fc70e0f8 | ||
|
|
a16af4560b | ||
|
|
0782ba0dab | ||
|
|
83725667a4 | ||
|
|
f4b3163b04 | ||
|
|
6cd745f429 | ||
|
|
6131f7f6bf | ||
|
|
dd4faa030f | ||
|
|
ab8691f5ac | ||
|
|
77ab073cdb | ||
|
|
87e0011525 | ||
|
|
7af3bb7226 | ||
|
|
5573352ce6 | ||
|
|
e6486e08ab | ||
|
|
48badaa927 | ||
|
|
2f13bf677e | ||
|
|
e63abc1b4b | ||
|
|
88334acdef | ||
|
|
0491aa9f6e | ||
|
|
5be76d7c0f | ||
|
|
3b510389fc | ||
|
|
32d88e9249 | ||
|
|
7b1a1ff4bb | ||
|
|
19beb919d0 | ||
|
|
ba09e8bf4d | ||
|
|
26dd2d0e8e | ||
|
|
69b15d58a2 | ||
|
|
ba68789fb9 | ||
|
|
47a6ceffbc | ||
|
|
b17ca66f73 | ||
|
|
93bc609026 | ||
|
|
3ea51c2e15 | ||
|
|
1d9897ea60 | ||
|
|
b6cb00bc79 | ||
|
|
6dd53c6bfd | ||
|
|
07df5126b3 | ||
|
|
47b38c7d45 | ||
|
|
0e97bec7b2 | ||
|
|
b182585d46 | ||
|
|
e8f92535d3 | ||
|
|
d62c3663e9 | ||
|
|
6b0bfda9fb | ||
|
|
7477330961 | ||
|
|
1f71157063 | ||
|
|
905988c592 | ||
|
|
310951bfa8 | ||
|
|
64c1087856 | ||
|
|
cab6d924aa | ||
|
|
c3a972d39b | ||
|
|
33d44d4d24 | ||
|
|
fd89cf2482 | ||
|
|
112ffb981f | ||
|
|
514426b980 | ||
|
|
a4bf1c8be6 | ||
|
|
9b82e1478f | ||
|
|
d5f145d57e | ||
|
|
bab891ee74 | ||
|
|
a65fd7d0d0 | ||
|
|
46836cc805 | ||
|
|
42559f13d8 | ||
|
|
87351b5920 | ||
|
|
e68dcf189c | ||
|
|
5d62b8389c | ||
|
|
c50aebe76d | ||
|
|
a610f3fde7 | ||
|
|
626391a1d9 | ||
|
|
1bedfe75ea | ||
|
|
86ecc8d4d5 | ||
|
|
9eca84efe1 | ||
|
|
8a6fb6dcba | ||
|
|
e3706fa923 | ||
|
|
8193bc5f60 | ||
|
|
504ecaee5e | ||
|
|
7c9e836572 | ||
|
|
5db0f09b43 | ||
|
|
195bc4ef21 | ||
|
|
6b190bc184 | ||
|
|
39f1cac2c8 | ||
|
|
d193eed519 | ||
|
|
2d80b0e12f | ||
|
|
b50d99be9c | ||
|
|
af41876a5e | ||
|
|
76d351d8be | ||
|
|
b5dd9651c3 | ||
|
|
3e34502014 | ||
|
|
5e57f9cbd6 | ||
|
|
8edb869fdc | ||
|
|
37238c7f57 | ||
|
|
9edee82fa1 | ||
|
|
f7aaea79af | ||
|
|
3c75d2f8b7 | ||
|
|
64c67e19d2 | ||
|
|
d4db8faad8 | ||
|
|
7957b73b4a | ||
|
|
69838c44af | ||
|
|
8e2953aef6 | ||
|
|
8dda616502 | ||
|
|
484512e35b | ||
|
|
c8cd05c07d | ||
|
|
7ffefe6259 | ||
|
|
cd9b7f2f11 | ||
|
|
b372974437 | ||
|
|
7464e0f799 | ||
|
|
25e12f1775 | ||
|
|
6416469f78 | ||
|
|
922ce5ae36 | ||
|
|
9ca8a199c0 | ||
|
|
a570406ac8 | ||
|
|
719edb6b6e | ||
|
|
d075218621 | ||
|
|
7509943938 | ||
|
|
774da9d2f8 | ||
|
|
978fd383e8 | ||
|
|
8551fc23fe | ||
|
|
fb711edeeb | ||
|
|
352a21acaa | ||
|
|
0b9d936317 | ||
|
|
dc500243e9 | ||
|
|
21203b8341 | ||
|
|
5b03447640 | ||
|
|
d34158db2c | ||
|
|
65a17390c7 | ||
|
|
0e96f0917c | ||
|
|
3d62a7e64a | ||
|
|
962805936e | ||
|
|
967aeecf5b | ||
|
|
348b039fa3 | ||
|
|
6e9b1f4fa3 | ||
|
|
f1d447d1aa | ||
|
|
a7c6f47dbe | ||
|
|
0446e89bfe | ||
|
|
e41457913f | ||
|
|
cea1ec7641 | ||
|
|
cc362deb87 | ||
|
|
7ec64e8a3d | ||
|
|
ff2461df9d | ||
|
|
192cd2733c | ||
|
|
ecef95469d | ||
|
|
55d30d5e4b | ||
|
|
2d5502cc2f | ||
|
|
5cda4a1eb4 | ||
|
|
812b914b70 | ||
|
|
9b870ad863 | ||
|
|
0f250ac92d | ||
|
|
552e6b7836 | ||
|
|
28f70b281b | ||
|
|
32d9b573c0 | ||
|
|
fc76a843d5 | ||
|
|
06607aabb2 |
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
|
||||||
21
.hgignore
21
.hgignore
@@ -1,21 +0,0 @@
|
|||||||
syntax: glob
|
|
||||||
|
|
||||||
.DS_Store
|
|
||||||
*.pyc
|
|
||||||
*.so
|
|
||||||
*.mode1v3
|
|
||||||
*.pbxuser
|
|
||||||
*.tm_build_errors
|
|
||||||
conf.yaml
|
|
||||||
build
|
|
||||||
core_pe/modules/block/block.c
|
|
||||||
core_pe/modules/cache/cache.c
|
|
||||||
cocoa/*/Info.plist
|
|
||||||
cocoa/*/build
|
|
||||||
cocoa/*/dg_cocoa.plugin
|
|
||||||
qt/base/*_rc.py
|
|
||||||
qt/base/*_ui.py
|
|
||||||
qt/se/*_ui.py
|
|
||||||
help_se/dupeguru_help
|
|
||||||
help_me/dupeguru_me_help
|
|
||||||
help_pe/dupeguru_pe_help
|
|
||||||
7
.hgtags
7
.hgtags
@@ -1,7 +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
|
|
||||||
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
|
||||||
|
|
||||||
3
LICENSE
3
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright 2010 Hardcoded Software Inc. (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software Inc. (http://www.hardcoded.net)
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
@@ -6,6 +6,5 @@ Redistribution and use in source and binary forms, with or without modification,
|
|||||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
* Neither the name of Hardcoded Software Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
* Neither the name of Hardcoded Software Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||||
* If the source code has been published less than two years ago, any redistribution, in whole or in part, must retain full licensing functionality, without any attempt to change, obscure or in other ways circumvent its intent.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
68
README
68
README
@@ -1,68 +0,0 @@
|
|||||||
Contents
|
|
||||||
=====
|
|
||||||
|
|
||||||
This package contains the source for dupeGuru. To learns 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. Before redistributing changes in this part of the code, read the "qt/WARNING" file.
|
|
||||||
- images: Images used by the different UI codebases.
|
|
||||||
|
|
||||||
There are also other sub-folder that comes from external repositories (automatically checked out
|
|
||||||
with svn:externals):
|
|
||||||
|
|
||||||
- hsutil: A collection of helpers used across HS applications.
|
|
||||||
- hsdocgen: An ad-hoc document generation used across HS project (used for help files)
|
|
||||||
- hsmedia: A library to read audio file metadata, used in dupeGuru ME.
|
|
||||||
- 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 2.6 (http://www.python.org)
|
|
||||||
- Mako, to generate help files. (http://www.makotemplates.org/)
|
|
||||||
- PyYaml, for help files and the build system. (http://pyyaml.org/)
|
|
||||||
- Nose, to run unit tests. (http://somethingaboutorange.com/mrl/projects/nose/)
|
|
||||||
- Cython to compile a few optimized bottlenecks. (http://www.cython.org/)
|
|
||||||
- Python Imaging Library for dupeGuru PE. (http://www.pythonware.com/products/pil/)
|
|
||||||
|
|
||||||
OS X prerequisites
|
|
||||||
-----
|
|
||||||
|
|
||||||
- XCode 3.1 (http://developer.apple.com/TOOLS/xcode/)
|
|
||||||
- Sparkle (http://sparkle.andymatuschak.org/)
|
|
||||||
- PyObjC. Although Tiger support has been dropped with dupeGuru 1.7, I still use PyObjC 1.4 because funky stuff happens with newer releases. However, it's mostly related to packaging with py2app. (http://pyobjc.sourceforge.net/)
|
|
||||||
- py2app (http://svn.pythonmac.org/py2app/py2app/trunk/doc/index.html)
|
|
||||||
|
|
||||||
Windows prerequisites
|
|
||||||
---
|
|
||||||
|
|
||||||
- Visual Studio 2008 (Express is enough) is needed to build the Cython extensions. (http://www.microsoft.com/Express/)
|
|
||||||
- PyQt 4.6 (http://www.riverbankcomputing.co.uk/news)
|
|
||||||
- PyInstaller, if you want to build a exe. You don't need it if you just want to run dupeGuru. (http://www.pyinstaller.org/)
|
|
||||||
- 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 `mg_cocoa.plugin` in alias mode).
|
|
||||||
|
|
||||||
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.
|
||||||
|
* debian: Skeleton files required to create a .deb package
|
||||||
|
* 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 (13.10+), 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"
|
||||||
404
build.py
404
build.py
@@ -1,89 +1,359 @@
|
|||||||
# -*- 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 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
#
|
#
|
||||||
# This software is licensed under the "HS" 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/hs_license
|
# http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import os.path as op
|
import os.path as op
|
||||||
|
from optparse import OptionParser
|
||||||
import shutil
|
import shutil
|
||||||
|
import json
|
||||||
|
import importlib
|
||||||
|
import compileall
|
||||||
|
|
||||||
from setuptools import setup
|
from setuptools import setup, Extension
|
||||||
import yaml
|
|
||||||
|
|
||||||
from hsdocgen import generate_help, filters
|
from hscommon import sphinxgen
|
||||||
from hsutil.build import add_to_pythonpath, print_and_do, build_all_qt_ui, copy_packages
|
from hscommon.build import (add_to_pythonpath, print_and_do, copy_packages, filereplace,
|
||||||
|
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():
|
||||||
|
usage = "usage: %prog [options]"
|
||||||
|
parser = OptionParser(usage=usage)
|
||||||
|
parser.add_option('--clean', action='store_true', dest='clean',
|
||||||
|
help="Clean build folder before building")
|
||||||
|
parser.add_option('--doc', action='store_true', dest='doc',
|
||||||
|
help="Build only the help file")
|
||||||
|
parser.add_option('--loc', action='store_true', dest='loc',
|
||||||
|
help="Build only localization")
|
||||||
|
parser.add_option('--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()
|
||||||
|
return options
|
||||||
|
|
||||||
|
def cocoa_compile_command(edition):
|
||||||
|
return '{0} waf configure --edition {1} && {0} waf'.format(sys.executable, edition)
|
||||||
|
|
||||||
|
def cocoa_app(edition):
|
||||||
|
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 = {
|
||||||
|
'se': ['core_se'],
|
||||||
|
'me': ['core_me'] + appscript_pkgs + ['hsaudiotag'],
|
||||||
|
'pe': ['core_pe'] + appscript_pkgs,
|
||||||
|
}[edition]
|
||||||
|
tocopy = ['core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'jobprogress', 'objp',
|
||||||
|
'send2trash'] + specific_packages
|
||||||
|
copy_packages(tocopy, pydep_folder, create_links=dev)
|
||||||
|
sys.path.insert(0, 'build')
|
||||||
|
extra_deps = None
|
||||||
|
if edition == 'pe':
|
||||||
|
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have
|
||||||
|
# to manually specify it.
|
||||||
|
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('..')
|
||||||
|
app.copy_executable('cocoa/build/dupeGuru')
|
||||||
|
build_help(edition)
|
||||||
|
print("Copying resources and frameworks")
|
||||||
|
image_path = ed('cocoa/{}/dupeguru.icns')
|
||||||
|
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
|
||||||
|
app.copy_resources(*resources, use_symlinks=dev)
|
||||||
|
app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework')
|
||||||
|
print("Creating the run.py file")
|
||||||
|
tmpl = open('cocoa/run_template.py', 'rt').read()
|
||||||
|
run_contents = tmpl.replace('{{app_path}}', app.dest)
|
||||||
|
open('run.py', 'wt').write(run_contents)
|
||||||
|
|
||||||
|
def build_qt(edition, dev, conf):
|
||||||
|
print("Building localizations")
|
||||||
|
build_localizations('qt', edition)
|
||||||
|
print("Building Qt stuff")
|
||||||
|
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")
|
||||||
|
filereplace(op.join('qt', 'run_template.py'), 'run.py', edition=edition)
|
||||||
|
|
||||||
|
def build_help(edition):
|
||||||
|
print("Generating Help")
|
||||||
|
current_path = op.abspath('.')
|
||||||
|
help_basepath = op.join(current_path, 'help', 'en')
|
||||||
|
help_destpath = op.join(current_path, 'build', 'help'.format(edition))
|
||||||
|
changelog_path = op.join(current_path, 'help', 'changelog_{}'.format(edition))
|
||||||
|
tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
|
||||||
|
appname = {'se': 'dupeGuru', 'me': 'dupeGuru Music Edition', 'pe': 'dupeGuru Picture Edition'}[edition]
|
||||||
|
homepage = 'http://www.hardcoded.net/dupeguru{}/'.format('_' + edition if edition != 'se' else '')
|
||||||
|
confrepl = {'edition': edition, 'appname': appname, 'homepage': homepage, 'language': 'en'}
|
||||||
|
changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl')
|
||||||
|
conftmpl = op.join(current_path, 'help', 'conf.tmpl')
|
||||||
|
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl)
|
||||||
|
|
||||||
|
def build_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):
|
||||||
|
print("Building PE Modules")
|
||||||
|
exts = [
|
||||||
|
Extension("_block", [op.join('core_pe', 'modules', 'block.c'), op.join('core_pe', 'modules', 'common.c')]),
|
||||||
|
Extension("_cache", [op.join('core_pe', 'modules', 'cache.c'), op.join('core_pe', 'modules', 'common.c')]),
|
||||||
|
]
|
||||||
|
if ui == 'qt':
|
||||||
|
exts.append(Extension("_block_qt", [op.join('qt', 'pe', 'modules', 'block.c')]))
|
||||||
|
elif ui == 'cocoa':
|
||||||
|
exts.append(Extension(
|
||||||
|
"_block_osx", [op.join('core_pe', 'modules', 'block_osx.m'), op.join('core_pe', 'modules', 'common.c')],
|
||||||
|
extra_link_args=[
|
||||||
|
"-framework", "CoreFoundation",
|
||||||
|
"-framework", "Foundation",
|
||||||
|
"-framework", "ApplicationServices",]
|
||||||
|
))
|
||||||
|
setup(
|
||||||
|
script_args = ['build_ext', '--inplace'],
|
||||||
|
ext_modules = exts,
|
||||||
|
)
|
||||||
|
move_all('_block_qt*', op.join('qt', 'pe'))
|
||||||
|
move_all('_block*', 'core_pe')
|
||||||
|
move_all('_cache*', 'core_pe')
|
||||||
|
|
||||||
|
def build_normal(edition, ui, dev, conf):
|
||||||
|
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui))
|
||||||
|
add_to_pythonpath('.')
|
||||||
|
print("Building dupeGuru")
|
||||||
|
if edition == 'pe':
|
||||||
|
build_pe_modules(ui)
|
||||||
|
if ui == 'cocoa':
|
||||||
|
build_cocoa(edition, dev)
|
||||||
|
elif ui == 'qt':
|
||||||
|
build_qt(edition, dev, conf)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
conf = yaml.load(open('conf.yaml'))
|
options = parse_args()
|
||||||
|
conf = json.load(open('conf.json'))
|
||||||
edition = conf['edition']
|
edition = conf['edition']
|
||||||
ui = conf['ui']
|
ui = conf['ui']
|
||||||
dev = conf['dev']
|
dev = conf['dev']
|
||||||
print "Building dupeGuru {0} with UI {1}".format(edition.upper(), ui)
|
|
||||||
if dev:
|
if dev:
|
||||||
print "Building in Dev mode"
|
print("Building in Dev mode")
|
||||||
add_to_pythonpath('.')
|
if options.clean:
|
||||||
print "Generating Help"
|
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
|
||||||
windows = sys.platform == 'win32'
|
if op.exists(path):
|
||||||
tix = filters.tixgen("https://hardcoded.lighthouseapp.com/projects/31699-dupeguru/tickets/{0}")
|
shutil.rmtree(path)
|
||||||
help_dir = 'help_{0}'.format(edition)
|
if not op.exists('build'):
|
||||||
dest_dir = 'dupeguru_{0}_help'.format(edition) if edition != 'se' else 'dupeguru_help'
|
|
||||||
help_basepath = op.abspath(help_dir)
|
|
||||||
help_destpath = op.abspath(op.join(help_dir, dest_dir))
|
|
||||||
generate_help.main(help_basepath, help_destpath, force_render=not dev, tix=tix, windows=windows)
|
|
||||||
|
|
||||||
print "Building dupeGuru"
|
|
||||||
if edition == 'pe':
|
|
||||||
os.chdir('core_pe')
|
|
||||||
os.system('python gen.py')
|
|
||||||
os.chdir('..')
|
|
||||||
if ui == 'cocoa':
|
|
||||||
if not dev:
|
|
||||||
print "Building help index"
|
|
||||||
os.system('open -a /Developer/Applications/Utilities/Help\\ Indexer.app {0}'.format(help_destpath))
|
|
||||||
|
|
||||||
print "Building dg_cocoa.plugin"
|
|
||||||
if op.exists('build'):
|
|
||||||
shutil.rmtree('build')
|
|
||||||
os.mkdir('build')
|
os.mkdir('build')
|
||||||
if not dev:
|
if options.doc:
|
||||||
specific_packages = {
|
build_help(edition)
|
||||||
'se': ['core_se'],
|
elif options.loc:
|
||||||
'me': ['core_me', 'hsmedia'],
|
build_localizations(ui, edition)
|
||||||
'pe': ['core_pe'],
|
elif options.updatepot:
|
||||||
}[edition]
|
build_updatepot()
|
||||||
copy_packages(['core', 'hsutil'] + specific_packages, 'build')
|
elif options.mergepot:
|
||||||
cocoa_project_path = 'cocoa/{0}'.format(edition)
|
build_mergepot()
|
||||||
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
|
elif options.normpo:
|
||||||
os.chdir('build')
|
build_normpo()
|
||||||
script_args = ['py2app', '-A'] if dev else ['py2app']
|
elif options.cocoa_ext:
|
||||||
setup(
|
build_cocoa_proxy_module()
|
||||||
script_args = script_args,
|
build_cocoa_bridging_interfaces(edition)
|
||||||
plugin = ['dg_cocoa.py'],
|
elif options.cocoa_compile:
|
||||||
setup_requires = ['py2app'],
|
os.chdir('cocoa')
|
||||||
)
|
print_and_do(cocoa_compile_command(edition))
|
||||||
os.chdir('..')
|
os.chdir('..')
|
||||||
pluginpath = op.join(cocoa_project_path, 'dg_cocoa.plugin')
|
cocoa_app(edition).copy_executable('cocoa/build/dupeGuru')
|
||||||
if op.exists(pluginpath):
|
elif options.xibless:
|
||||||
shutil.rmtree(pluginpath)
|
build_cocoalib_xibless()
|
||||||
shutil.move('build/dist/dg_cocoa.plugin', pluginpath)
|
build_xibless(edition)
|
||||||
if dev:
|
else:
|
||||||
# In alias mode, the tweakings we do to the pythonpath aren't counted in. We have to
|
build_normal(edition, ui, dev, conf)
|
||||||
# manually put a .pth in the plugin
|
|
||||||
pthpath = op.join(pluginpath, 'Contents/Resources/dev.pth')
|
|
||||||
open(pthpath, 'w').write(op.abspath('.'))
|
|
||||||
os.chdir(cocoa_project_path)
|
|
||||||
print "Building the XCode project"
|
|
||||||
os.system('xcodebuild')
|
|
||||||
os.chdir('..')
|
|
||||||
elif ui == 'qt':
|
|
||||||
os.chdir(op.join('qt', edition))
|
|
||||||
os.system('python gen.py')
|
|
||||||
os.chdir(op.join('..', '..'))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "RecentDirectories.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "DetailsPanel.h"
|
|
||||||
|
|
||||||
@interface AppDelegateBase : NSObject
|
|
||||||
{
|
|
||||||
IBOutlet PyDupeGuruBase *py;
|
|
||||||
IBOutlet RecentDirectories *recentDirectories;
|
|
||||||
IBOutlet NSMenuItem *unlockMenuItem;
|
|
||||||
IBOutlet ResultWindowBase *result;
|
|
||||||
|
|
||||||
DetailsPanelBase *_detailsPanel;
|
|
||||||
}
|
|
||||||
- (IBAction)unlockApp:(id)sender;
|
|
||||||
|
|
||||||
- (PyDupeGuruBase *)py;
|
|
||||||
- (RecentDirectories *)recentDirectories;
|
|
||||||
- (DetailsPanelBase *)detailsPanel; // Virtual
|
|
||||||
@end
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "ProgressController.h"
|
|
||||||
#import "RegistrationInterface.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation AppDelegateBase
|
|
||||||
- (IBAction)unlockApp:(id)sender
|
|
||||||
{
|
|
||||||
if ([[self py] isRegistered])
|
|
||||||
return;
|
|
||||||
RegistrationInterface *ri = [[RegistrationInterface alloc] initWithApp:[self py]];
|
|
||||||
if ([ri enterCode] == NSOKButton)
|
|
||||||
{
|
|
||||||
NSString *menuTitle = [NSString stringWithFormat:@"Thanks for buying %@!",[py appName]];
|
|
||||||
[unlockMenuItem setTitle:menuTitle];
|
|
||||||
}
|
|
||||||
[ri release];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (PyDupeGuruBase *)py { return py; }
|
|
||||||
- (RecentDirectories *)recentDirectories { return recentDirectories; }
|
|
||||||
- (DetailsPanelBase *)detailsPanel { return nil; } // Virtual
|
|
||||||
|
|
||||||
/* 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])
|
|
||||||
[result restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
|
||||||
else
|
|
||||||
[result resetColumnsToDefault:nil];
|
|
||||||
//Reg stuff
|
|
||||||
if ([RegistrationInterface showNagWithApp:[self py]])
|
|
||||||
[unlockMenuItem setTitle:[NSString stringWithFormat:@"Thanks for buying %@!",[py appName]]];
|
|
||||||
//Restore results
|
|
||||||
[py loadIgnoreList];
|
|
||||||
[py loadResults];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
76
cocoa/base/AppDelegateBase.h
Normal file
76
cocoa/base/AppDelegateBase.h
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 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 2013 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,22 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
#define DuplicateSelectionChangedNotification @"DuplicateSelectionChangedNotification"
|
|
||||||
/* ResultsChangedNotification happens on major changes, which requires a complete reload of the data*/
|
|
||||||
#define ResultsChangedNotification @"ResultsChangedNotification"
|
|
||||||
/* ResultsChangedNotification happens on minor changes, which requires buffer flush*/
|
|
||||||
#define ResultsUpdatedNotification @"ResultsUpdatedNotification"
|
|
||||||
#define ResultsMarkingChangedNotification @"ResultsMarkingChangedNotification"
|
|
||||||
#define RegistrationRequired @"RegistrationRequired"
|
|
||||||
#define JobStarted @"JobStarted"
|
#define 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"
|
||||||
@@ -24,4 +16,4 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#define jobMove @"job_move"
|
#define jobMove @"job_move"
|
||||||
#define jobDelete @"job_delete"
|
#define jobDelete @"job_delete"
|
||||||
|
|
||||||
#define DEMO_MAX_ACTION_COUNT 10
|
#define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType"
|
||||||
|
|||||||
33
cocoa/base/DeletionOptions.h
Normal file
33
cocoa/base/DeletionOptions.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 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 2013 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,25 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "PyApp.h"
|
|
||||||
#import "Table.h"
|
|
||||||
|
|
||||||
|
|
||||||
@interface DetailsPanelBase : NSWindowController
|
|
||||||
{
|
|
||||||
IBOutlet TableView *detailsTable;
|
|
||||||
}
|
|
||||||
- (id)initWithPy:(PyApp *)aPy;
|
|
||||||
|
|
||||||
- (void)refresh;
|
|
||||||
- (void)toggleVisibility;
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)duplicateSelectionChanged:(NSNotification *)aNotification;
|
|
||||||
@end
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation DetailsPanelBase
|
|
||||||
- (id)initWithPy:(PyApp *)aPy
|
|
||||||
{
|
|
||||||
self = [super initWithWindowNibName:@"DetailsPanel"];
|
|
||||||
[self window]; //So the detailsTable is initialized.
|
|
||||||
[detailsTable setPy:aPy];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(duplicateSelectionChanged:) name:DuplicateSelectionChangedNotification object:nil];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)refresh
|
|
||||||
{
|
|
||||||
[detailsTable reloadData];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)toggleVisibility
|
|
||||||
{
|
|
||||||
if ([[self window] isVisible])
|
|
||||||
[[self window] close];
|
|
||||||
else
|
|
||||||
{
|
|
||||||
[self refresh]; // selection might have changed since last time
|
|
||||||
[[self window] orderFront:nil];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)duplicateSelectionChanged:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
if ([[self window] isVisible])
|
|
||||||
[self refresh];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
31
cocoa/base/DetailsPanelBase.h
Normal file
31
cocoa/base/DetailsPanelBase.h
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 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
|
||||||
80
cocoa/base/DetailsPanelBase.m
Normal file
80
cocoa/base/DetailsPanelBase.m
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 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 "DetailsPanelBase.h"
|
||||||
|
#import "HSPyUtil.h"
|
||||||
|
|
||||||
|
@implementation DetailsPanelBase
|
||||||
|
|
||||||
|
@synthesize detailsTable;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||||
|
{
|
||||||
|
self = [super initWithWindow:nil];
|
||||||
|
[self setWindow:[self createWindow]];
|
||||||
|
model = [[PyDetailsPanel alloc] initWithModel:aPyRef];
|
||||||
|
[model bindCallback:createCallback(@"DetailsPanelView", self)];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[model release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyDetailsPanel *)model
|
||||||
|
{
|
||||||
|
return (PyDetailsPanel *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSWindow *)createWindow
|
||||||
|
{
|
||||||
|
return nil; // Virtual
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)refreshDetails
|
||||||
|
{
|
||||||
|
[detailsTable reloadData];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)isVisible
|
||||||
|
{
|
||||||
|
return [[self window] isVisible];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)toggleVisibility
|
||||||
|
{
|
||||||
|
if ([self isVisible]) {
|
||||||
|
[[self window] close];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[self refreshDetails]; // selection might have changed since last time
|
||||||
|
[[self window] orderFront:nil];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NSTableView Delegate */
|
||||||
|
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
|
||||||
|
{
|
||||||
|
return [[self model] numberOfRows];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
return [[self model] valueForColumn:[column identifier] row:row];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)refresh
|
||||||
|
{
|
||||||
|
if ([[self window] isVisible]) {
|
||||||
|
[self refreshDetails];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
21
cocoa/base/DirectoryOutline.h
Normal file
21
cocoa/base/DirectoryOutline.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <Python.h>
|
||||||
|
#import "HSOutline.h"
|
||||||
|
#import "PyDirectoryOutline.h"
|
||||||
|
|
||||||
|
#define DGAddedFoldersNotification @"DGAddedFoldersNotification"
|
||||||
|
|
||||||
|
@interface DirectoryOutline : HSOutline {}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView;
|
||||||
|
- (PyDirectoryOutline *)model;
|
||||||
|
|
||||||
|
- (void)selectAll;
|
||||||
|
@end;
|
||||||
87
cocoa/base/DirectoryOutline.m
Normal file
87
cocoa/base/DirectoryOutline.m
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "DirectoryOutline.h"
|
||||||
|
|
||||||
|
@implementation DirectoryOutline
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView
|
||||||
|
{
|
||||||
|
self = [super initWithPyRef:aPyRef wrapperClass:[PyDirectoryOutline class]
|
||||||
|
callbackClassName:@"DirectoryOutlineView" view:aOutlineView];
|
||||||
|
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyDirectoryOutline *)model
|
||||||
|
{
|
||||||
|
return (PyDirectoryOutline *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
- (void)selectAll
|
||||||
|
{
|
||||||
|
[[self model] selectAll];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
|
||||||
|
{
|
||||||
|
NSPasteboard *pboard;
|
||||||
|
NSDragOperation sourceDragMask;
|
||||||
|
sourceDragMask = [info draggingSourceOperationMask];
|
||||||
|
pboard = [info draggingPasteboard];
|
||||||
|
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
|
||||||
|
if (sourceDragMask & NSDragOperationLink)
|
||||||
|
return NSDragOperationLink;
|
||||||
|
}
|
||||||
|
return NSDragOperationNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
|
||||||
|
{
|
||||||
|
NSPasteboard *pboard;
|
||||||
|
NSDragOperation sourceDragMask;
|
||||||
|
sourceDragMask = [info draggingSourceOperationMask];
|
||||||
|
pboard = [info draggingPasteboard];
|
||||||
|
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
|
||||||
|
NSArray *foldernames = [pboard propertyListForType:NSFilenamesPboardType];
|
||||||
|
if (!(sourceDragMask & NSDragOperationLink))
|
||||||
|
return NO;
|
||||||
|
for (NSString *foldername in foldernames) {
|
||||||
|
[[self model] addDirectory:foldername];
|
||||||
|
}
|
||||||
|
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:foldernames forKey:@"foldernames"];
|
||||||
|
[[NSNotificationCenter defaultCenter] postNotificationName:DGAddedFoldersNotification
|
||||||
|
object:self userInfo:userInfo];
|
||||||
|
}
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)outlineView:(NSOutlineView *)aOutlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
||||||
|
{
|
||||||
|
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
||||||
|
NSTextFieldCell *textCell = cell;
|
||||||
|
NSIndexPath *path = item;
|
||||||
|
BOOL selected = [path isEqualTo:[[self view] selectedPath]];
|
||||||
|
if (selected) {
|
||||||
|
[textCell setTextColor:[NSColor blackColor]];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NSInteger state = [self intProperty:@"state" valueAtPath:path];
|
||||||
|
if (state == 1) {
|
||||||
|
[textCell setTextColor:[NSColor blueColor]];
|
||||||
|
}
|
||||||
|
else if (state == 2) {
|
||||||
|
[textCell setTextColor:[NSColor redColor]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[textCell setTextColor:[NSColor blackColor]];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -1,42 +1,51 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "RecentDirectories.h"
|
#import "HSOutlineView.h"
|
||||||
#import "Outline.h"
|
#import "HSRecentFiles.h"
|
||||||
|
#import "DirectoryOutline.h"
|
||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
@interface DirectoryOutline : OutlineView
|
@class AppDelegateBase;
|
||||||
|
|
||||||
|
@interface DirectoryPanel : NSWindowController <NSOpenSavePanelDelegate>
|
||||||
{
|
{
|
||||||
|
AppDelegateBase *_app;
|
||||||
|
PyDupeGuru *model;
|
||||||
|
HSRecentFiles *_recentDirectories;
|
||||||
|
DirectoryOutline *outline;
|
||||||
|
BOOL _alwaysShowPopUp;
|
||||||
|
NSPopUpButton *addButtonPopUp;
|
||||||
|
NSPopUpButton *loadRecentButtonPopUp;
|
||||||
|
HSOutlineView *outlineView;
|
||||||
|
NSButton *removeButton;
|
||||||
|
NSButton *loadResultsButton;
|
||||||
}
|
}
|
||||||
@end
|
|
||||||
|
|
||||||
@protocol DirectoryOutlineDelegate
|
@property (readwrite, retain) NSPopUpButton *addButtonPopUp;
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView addDirectory:(NSString *)directory;
|
@property (readwrite, retain) NSPopUpButton *loadRecentButtonPopUp;
|
||||||
@end
|
@property (readwrite, retain) HSOutlineView *outlineView;
|
||||||
|
@property (readwrite, retain) NSButton *removeButton;
|
||||||
|
@property (readwrite, retain) NSButton *loadResultsButton;
|
||||||
|
|
||||||
@interface DirectoryPanelBase : NSWindowController
|
- (id)initWithParentApp:(AppDelegateBase *)aParentApp;
|
||||||
{
|
|
||||||
IBOutlet NSPopUpButton *addButtonPopUp;
|
|
||||||
IBOutlet DirectoryOutline *directories;
|
|
||||||
IBOutlet NSButton *removeButton;
|
|
||||||
|
|
||||||
PyDupeGuruBase *_py;
|
- (void)fillPopUpMenu; // Virtual
|
||||||
RecentDirectories *_recentDirectories;
|
- (void)adjustUIToLocalization;
|
||||||
}
|
|
||||||
- (id)initWithParentApp:(id)aParentApp;
|
|
||||||
|
|
||||||
- (IBAction)askForDirectory:(id)sender;
|
- (void)askForDirectory;
|
||||||
- (IBAction)changeDirectoryState:(id)sender;
|
- (void)popupAddDirectoryMenu:(id)sender;
|
||||||
- (IBAction)popupAddDirectoryMenu:(id)sender;
|
- (void)popupLoadRecentMenu:(id)sender;
|
||||||
- (IBAction)removeSelectedDirectory:(id)sender;
|
- (void)removeSelectedDirectory;
|
||||||
- (IBAction)toggleVisible:(id)sender;
|
|
||||||
|
|
||||||
- (void)addDirectory:(NSString *)directory;
|
- (void)addDirectory:(NSString *)directory;
|
||||||
- (void)refreshRemoveButtonText;
|
- (void)refreshRemoveButtonText;
|
||||||
|
- (void)markAll;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,219 +1,172 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
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"
|
||||||
|
|
||||||
@implementation DirectoryOutline
|
@implementation DirectoryPanel
|
||||||
- (void)doInit
|
|
||||||
{
|
|
||||||
[super doInit];
|
|
||||||
[self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
|
@synthesize addButtonPopUp;
|
||||||
{
|
@synthesize loadRecentButtonPopUp;
|
||||||
NSPasteboard *pboard;
|
@synthesize outlineView;
|
||||||
NSDragOperation sourceDragMask;
|
@synthesize removeButton;
|
||||||
sourceDragMask = [info draggingSourceOperationMask];
|
@synthesize loadResultsButton;
|
||||||
pboard = [info draggingPasteboard];
|
|
||||||
if ([[pboard types] containsObject:NSFilenamesPboardType])
|
|
||||||
{
|
|
||||||
if (sourceDragMask & NSDragOperationLink)
|
|
||||||
return NSDragOperationLink;
|
|
||||||
}
|
|
||||||
return NSDragOperationNone;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
|
- (id)initWithParentApp:(AppDelegateBase *)aParentApp
|
||||||
{
|
{
|
||||||
NSPasteboard *pboard;
|
self = [super initWithWindow:nil];
|
||||||
NSDragOperation sourceDragMask;
|
[self setWindow:createDirectoryPanel_UI(self)];
|
||||||
sourceDragMask = [info draggingSourceOperationMask];
|
_app = aParentApp;
|
||||||
pboard = [info draggingPasteboard];
|
model = [_app model];
|
||||||
if ( [[pboard types] containsObject:NSFilenamesPboardType] )
|
[[self window] setTitle:[model appName]];
|
||||||
{
|
_alwaysShowPopUp = NO;
|
||||||
NSArray *filenames = [pboard propertyListForType:NSFilenamesPboardType];
|
[self fillPopUpMenu];
|
||||||
if (!(sourceDragMask & NSDragOperationLink))
|
_recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]];
|
||||||
return NO;
|
[_recentDirectories setDelegate:self];
|
||||||
if (([self delegate] == nil) || (![[self delegate] respondsToSelector:@selector(outlineView:addDirectory:)]))
|
outline = [[DirectoryOutline alloc] initWithPyRef:[model directoryTree] outlineView:outlineView];
|
||||||
return NO;
|
|
||||||
for (NSString *filename in filenames)
|
|
||||||
[[self delegate] outlineView:self addDirectory:filename];
|
|
||||||
}
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation DirectoryPanelBase
|
|
||||||
- (id)initWithParentApp:(id)aParentApp
|
|
||||||
{
|
|
||||||
self = [super initWithWindowNibName:@"DirectoryPanel"];
|
|
||||||
[self window];
|
|
||||||
AppDelegateBase *app = aParentApp;
|
|
||||||
_py = [app py];
|
|
||||||
_recentDirectories = [app recentDirectories];
|
|
||||||
[directories setPy:_py];
|
|
||||||
NSPopUpButtonCell *cell = [[directories tableColumnWithIdentifier:@"1"] dataCell];
|
|
||||||
[cell addItemWithTitle:@"Normal"];
|
|
||||||
[cell addItemWithTitle:@"Reference"];
|
|
||||||
[cell addItemWithTitle:@"Excluded"];
|
|
||||||
for (int i=0;i<[[cell itemArray] count];i++)
|
|
||||||
{
|
|
||||||
NSMenuItem *mi = [[cell itemArray] objectAtIndex:i];
|
|
||||||
[mi setTarget:self];
|
|
||||||
[mi setAction:@selector(changeDirectoryState:)];
|
|
||||||
[mi setTag:i];
|
|
||||||
}
|
|
||||||
[self refreshRemoveButtonText];
|
[self refreshRemoveButtonText];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:) name:NSOutlineViewSelectionDidChangeNotification object:directories];
|
[self adjustUIToLocalization];
|
||||||
|
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:)
|
||||||
|
name:NSOutlineViewSelectionDidChangeNotification object:outlineView];
|
||||||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(outlineAddedFolders:)
|
||||||
|
name:DGAddedFoldersNotification object:outline];
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[outline release];
|
||||||
|
[_recentDirectories release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
|
||||||
|
- (void)fillPopUpMenu
|
||||||
|
{
|
||||||
|
NSMenu *m = [addButtonPopUp menu];
|
||||||
|
NSMenuItem *mi = [m addItemWithTitle:NSLocalizedString(@"Add New Folder...", @"") action:@selector(askForDirectory) keyEquivalent:@""];
|
||||||
|
[mi setTarget:self];
|
||||||
|
[m addItem:[NSMenuItem separatorItem]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)adjustUIToLocalization
|
||||||
|
{
|
||||||
|
NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
|
||||||
|
NSInteger loadResultsWidthDelta = 0;
|
||||||
|
if ([lang isEqual:@"ru"]) {
|
||||||
|
loadResultsWidthDelta = 50;
|
||||||
|
}
|
||||||
|
else if ([lang isEqual:@"uk"]) {
|
||||||
|
loadResultsWidthDelta = 70;
|
||||||
|
}
|
||||||
|
else if ([lang isEqual:@"hy"]) {
|
||||||
|
loadResultsWidthDelta = 30;
|
||||||
|
}
|
||||||
|
if (loadResultsWidthDelta) {
|
||||||
|
NSRect r = [loadResultsButton frame];
|
||||||
|
r.size.width += loadResultsWidthDelta;
|
||||||
|
r.origin.x -= loadResultsWidthDelta;
|
||||||
|
[loadResultsButton setFrame:r];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* 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:NO];
|
[op setAllowsMultipleSelection:YES];
|
||||||
[op setTitle:@"Select a directory to add to the scanning list"];
|
[op setTitle:NSLocalizedString(@"Select a folder to add to the scanning list", @"")];
|
||||||
[op setDelegate:self];
|
[op setDelegate:self];
|
||||||
if ([op runModalForTypes:nil] == NSOKButton)
|
if ([op runModal] == NSOKButton) {
|
||||||
{
|
for (NSURL *directoryURL in [op URLs]) {
|
||||||
NSString *directory = [[op filenames] objectAtIndex:0];
|
[self addDirectory:[directoryURL path]];
|
||||||
[self addDirectory:directory];
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)changeDirectoryState:(id)sender
|
- (void)popupAddDirectoryMenu:(id)sender
|
||||||
{
|
{
|
||||||
OVNode *node = [directories itemAtRow:[directories clickedRow]];
|
if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) {
|
||||||
[_py setDirectory:p2a([node indexPath]) state:i2n([sender tag])];
|
[self askForDirectory];
|
||||||
[node resetAllBuffers];
|
|
||||||
[directories reloadItem:node reloadChildren:YES];
|
|
||||||
[directories display];
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
- (IBAction)popupAddDirectoryMenu:(id)sender
|
|
||||||
{
|
|
||||||
if ([[_recentDirectories directories] count] == 0)
|
|
||||||
{
|
|
||||||
[self askForDirectory:sender];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NSMenu *m = [addButtonPopUp menu];
|
|
||||||
while ([m numberOfItems] > 0)
|
|
||||||
[m removeItemAtIndex:0];
|
|
||||||
NSMenuItem *mi = [m addItemWithTitle:@"Add New Directory..." action:@selector(askForDirectory:) keyEquivalent:@""];
|
|
||||||
[mi setTarget:self];
|
|
||||||
[m addItem:[NSMenuItem separatorItem]];
|
|
||||||
[_recentDirectories fillMenu:m];
|
|
||||||
[addButtonPopUp selectItem:nil];
|
[addButtonPopUp selectItem:nil];
|
||||||
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
- (IBAction)removeSelectedDirectory:(id)sender
|
- (void)popupLoadRecentMenu:(id)sender
|
||||||
|
{
|
||||||
|
if ([[[_app recentResults] filepaths] count] > 0) {
|
||||||
|
NSMenu *m = [loadRecentButtonPopUp menu];
|
||||||
|
while ([m numberOfItems] > 0) {
|
||||||
|
[m removeItemAtIndex:0];
|
||||||
|
}
|
||||||
|
NSMenuItem *mi = [m addItemWithTitle:NSLocalizedString(@"Load from file...", @"") action:@selector(loadResults) keyEquivalent:@""];
|
||||||
|
[mi setTarget:_app];
|
||||||
|
[m addItem:[NSMenuItem separatorItem]];
|
||||||
|
[[_app recentResults] fillMenu:m];
|
||||||
|
[loadRecentButtonPopUp selectItem:nil];
|
||||||
|
[[loadRecentButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[_app loadResults];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)removeSelectedDirectory
|
||||||
{
|
{
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
if ([directories selectedRow] < 0)
|
[[outline model] removeSelectedDirectory];
|
||||||
return;
|
|
||||||
OVNode *node = [directories itemAtRow:[directories selectedRow]];
|
|
||||||
if ([node level] == 1)
|
|
||||||
{
|
|
||||||
[_py removeDirectory:i2n([node index])];
|
|
||||||
[directories reloadData];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int state = n2i([[node buffer] objectAtIndex:1]);
|
|
||||||
int newState = state == 2 ? 0 : 2; // If excluded, put it back
|
|
||||||
[_py setDirectory:p2a([node indexPath]) state:i2n(newState)];
|
|
||||||
[node resetAllBuffers];
|
|
||||||
[directories display];
|
|
||||||
}
|
|
||||||
[self refreshRemoveButtonText];
|
[self refreshRemoveButtonText];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)toggleVisible:(id)sender
|
|
||||||
{
|
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Public */
|
/* Public */
|
||||||
|
|
||||||
- (void)addDirectory:(NSString *)directory
|
- (void)addDirectory:(NSString *)directory
|
||||||
{
|
{
|
||||||
int r = [[_py addDirectory:directory] intValue];
|
[model addDirectory:directory];
|
||||||
if (r)
|
[_recentDirectories addFile:directory];
|
||||||
{
|
|
||||||
NSString *m;
|
|
||||||
switch (r)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
{
|
|
||||||
m = @"This directory already is in the list.";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
{
|
|
||||||
m = @"This directory does not exist.";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[Dialogs showMessage:m];
|
|
||||||
}
|
|
||||||
[directories reloadData];
|
|
||||||
[_recentDirectories addDirectory:directory];
|
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
[[self window] makeKeyAndOrderFront:nil];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)refreshRemoveButtonText
|
- (void)refreshRemoveButtonText
|
||||||
{
|
{
|
||||||
if ([directories selectedRow] < 0)
|
if ([outlineView selectedRow] < 0) {
|
||||||
{
|
|
||||||
[removeButton setEnabled:NO];
|
[removeButton setEnabled:NO];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
[removeButton setEnabled:YES];
|
[removeButton setEnabled:YES];
|
||||||
OVNode *node = [directories itemAtRow:[directories selectedRow]];
|
NSIndexPath *path = [outline selectedIndexPath];
|
||||||
int state = n2i([[node buffer] objectAtIndex:1]);
|
if (path != nil) {
|
||||||
NSString *buttonText = state == 2 ? @"Put Back" : @"Remove";
|
NSInteger state = [outline intProperty:@"state" valueAtPath:path];
|
||||||
[removeButton setTitle:buttonText];
|
BOOL shouldDisplayArrow = ([path length] > 1) && (state == 2);
|
||||||
|
NSString *imgName = shouldDisplayArrow ? @"NSGoLeftTemplate" : @"NSRemoveTemplate";
|
||||||
|
[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 */
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView addDirectory:(NSString *)directory
|
|
||||||
{
|
|
||||||
[self addDirectory:directory];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
|
||||||
{
|
|
||||||
OVNode *node = item;
|
|
||||||
int state = n2i([[node buffer] objectAtIndex:1]);
|
|
||||||
if ([cell isKindOfClass:[NSTextFieldCell class]])
|
|
||||||
{
|
|
||||||
NSTextFieldCell *textCell = cell;
|
|
||||||
if (state == 1)
|
|
||||||
[textCell setTextColor:[NSColor blueColor]];
|
|
||||||
else if (state == 2)
|
|
||||||
[textCell setTextColor:[NSColor redColor]];
|
|
||||||
else
|
|
||||||
[textCell setTextColor:[NSColor blackColor]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
|
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
|
||||||
{
|
{
|
||||||
BOOL isdir;
|
BOOL isdir;
|
||||||
@@ -221,6 +174,19 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
return isdir;
|
return isdir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)recentFileClicked:(NSString *)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
|
||||||
@@ -228,4 +194,12 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[self refreshRemoveButtonText];
|
[self refreshRemoveButtonText];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)outlineAddedFolders:(NSNotification *)aNotification
|
||||||
|
{
|
||||||
|
NSArray *foldernames = [[aNotification userInfo] objectForKey:@"foldernames"];
|
||||||
|
for (NSString *foldername in foldernames) {
|
||||||
|
[_recentDirectories addFile:foldername];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
25
cocoa/base/IgnoreListDialog.h
Normal file
25
cocoa/base/IgnoreListDialog.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 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 2013 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 2013 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 2013 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 2013 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 2013 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
|
||||||
26
cocoa/base/ProblemDialog.h
Normal file
26
cocoa/base/ProblemDialog.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "PyProblemDialog.h"
|
||||||
|
#import "HSTable.h"
|
||||||
|
|
||||||
|
@interface ProblemDialog : NSWindowController
|
||||||
|
{
|
||||||
|
PyProblemDialog *model;
|
||||||
|
HSTable *problemTable;
|
||||||
|
NSTableView *problemTableView;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property (readwrite, retain) PyProblemDialog *model;
|
||||||
|
@property (readwrite, retain) NSTableView *problemTableView;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||||
|
|
||||||
|
- (void)initializeColumns;
|
||||||
|
@end
|
||||||
44
cocoa/base/ProblemDialog.m
Normal file
44
cocoa/base/ProblemDialog.m
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ProblemDialog.h"
|
||||||
|
#import "ProblemDialog_UI.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
|
||||||
|
@implementation ProblemDialog
|
||||||
|
|
||||||
|
@synthesize model;
|
||||||
|
@synthesize problemTableView;
|
||||||
|
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||||
|
{
|
||||||
|
self = [super initWithWindow:nil];
|
||||||
|
self.model = [[PyProblemDialog alloc] initWithModel:aPyRef];
|
||||||
|
[self setWindow:createProblemDialog_UI(self)];
|
||||||
|
problemTable = [[HSTable alloc] initWithPyRef:[self.model problemTable] tableView:problemTableView];
|
||||||
|
[self initializeColumns];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)dealloc
|
||||||
|
{
|
||||||
|
[problemTable release];
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)initializeColumns
|
||||||
|
{
|
||||||
|
HSColumnDef defs[] = {
|
||||||
|
{@"path", 202, 40, 0, NO, nil},
|
||||||
|
{@"msg", 228, 40, 0, NO, nil},
|
||||||
|
nil
|
||||||
|
};
|
||||||
|
[[problemTable columns] initializeColumns:defs];
|
||||||
|
[[problemTable columns] setColumnsAsReadOnly];
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "PyApp.h"
|
|
||||||
|
|
||||||
@interface PyDupeGuruBase : PyApp
|
|
||||||
//Actions
|
|
||||||
- (NSNumber *)addDirectory:(NSString *)name;
|
|
||||||
- (void)removeDirectory:(NSNumber *)index;
|
|
||||||
- (void)setDirectory:(NSArray *)indexPath state:(NSNumber *)state;
|
|
||||||
- (void)loadResults;
|
|
||||||
- (void)saveResults;
|
|
||||||
- (void)loadIgnoreList;
|
|
||||||
- (void)saveIgnoreList;
|
|
||||||
- (void)clearIgnoreList;
|
|
||||||
- (void)purgeIgnoreList;
|
|
||||||
- (NSString *)exportToXHTMLwithColumns:(NSArray *)aColIds;
|
|
||||||
|
|
||||||
- (NSNumber *)doScan;
|
|
||||||
|
|
||||||
- (NSArray *)selectedPowerMarkerNodePaths;
|
|
||||||
- (void)selectPowerMarkerNodePaths:(NSArray *)aIndexPaths;
|
|
||||||
- (NSArray *)selectedResultNodePaths;
|
|
||||||
- (void)selectResultNodePaths:(NSArray *)aIndexPaths;
|
|
||||||
|
|
||||||
- (void)toggleSelectedMark;
|
|
||||||
- (void)markAll;
|
|
||||||
- (void)markInvert;
|
|
||||||
- (void)markNone;
|
|
||||||
|
|
||||||
- (void)addSelectedToIgnoreList;
|
|
||||||
- (void)refreshDetailsWithSelected;
|
|
||||||
- (void)removeSelected;
|
|
||||||
- (void)openSelected;
|
|
||||||
- (NSNumber *)renameSelected:(NSString *)aNewName;
|
|
||||||
- (void)revealSelected;
|
|
||||||
- (void)makeSelectedReference;
|
|
||||||
- (void)applyFilter:(NSString *)filter;
|
|
||||||
|
|
||||||
- (void)sortGroupsBy:(NSNumber *)aIdentifier ascending:(NSNumber *)aAscending;
|
|
||||||
- (void)sortDupesBy:(NSNumber *)aIdentifier ascending:(NSNumber *)aAscending;
|
|
||||||
|
|
||||||
- (void)copyOrMove:(NSNumber *)aCopy markedTo:(NSString *)destination recreatePath:(NSNumber *)aRecreateType;
|
|
||||||
- (void)deleteMarked;
|
|
||||||
- (void)removeMarked;
|
|
||||||
|
|
||||||
//Data
|
|
||||||
- (NSNumber *)getIgnoreListCount;
|
|
||||||
- (NSNumber *)getMarkCount;
|
|
||||||
- (NSString *)getStatLine;
|
|
||||||
- (NSNumber *)getOperationalErrorCount;
|
|
||||||
|
|
||||||
//Scanning options
|
|
||||||
- (void)setMinMatchPercentage:(NSNumber *)percentage;
|
|
||||||
- (void)setMixFileKind:(NSNumber *)mix_file_kind;
|
|
||||||
- (void)setDisplayDeltaValues:(NSNumber *)display_delta_values;
|
|
||||||
- (void)setEscapeFilterRegexp:(NSNumber *)escape_filter_regexp;
|
|
||||||
- (void)setRemoveEmptyFolders:(NSNumber *)remove_empty_folders;
|
|
||||||
- (void)setSizeThreshold:(int)size_threshold;
|
|
||||||
@end
|
|
||||||
23
cocoa/base/ResultTable.h
Normal file
23
cocoa/base/ResultTable.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import <Quartz/Quartz.h>
|
||||||
|
#import "HSTable.h"
|
||||||
|
#import "PyResultTable.h"
|
||||||
|
|
||||||
|
@interface ResultTable : HSTable <QLPreviewPanelDataSource, QLPreviewPanelDelegate>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView;
|
||||||
|
- (PyResultTable *)model;
|
||||||
|
- (BOOL)powerMarkerMode;
|
||||||
|
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode;
|
||||||
|
- (BOOL)deltaValuesMode;
|
||||||
|
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode;
|
||||||
|
@end;
|
||||||
180
cocoa/base/ResultTable.m
Normal file
180
cocoa/base/ResultTable.m
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "ResultTable.h"
|
||||||
|
#import "Dialogs.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
#import "HSQuicklook.h"
|
||||||
|
|
||||||
|
@interface HSTable (private)
|
||||||
|
- (void)setPySelection;
|
||||||
|
- (void)setViewSelection;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation ResultTable
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView
|
||||||
|
{
|
||||||
|
self = [super initWithPyRef:aPyRef wrapperClass:[PyResultTable class] callbackClassName:@"ResultTableView" view:aTableView];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyResultTable *)model
|
||||||
|
{
|
||||||
|
return (PyResultTable *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Private */
|
||||||
|
- (void)updateQuicklookIfNeeded
|
||||||
|
{
|
||||||
|
if ([[QLPreviewPanel sharedPreviewPanel] dataSource] == self) {
|
||||||
|
[[QLPreviewPanel sharedPreviewPanel] reloadData];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPySelection
|
||||||
|
{
|
||||||
|
[super setPySelection];
|
||||||
|
[self updateQuicklookIfNeeded];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setViewSelection
|
||||||
|
{
|
||||||
|
[super setViewSelection];
|
||||||
|
[self updateQuicklookIfNeeded];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Public */
|
||||||
|
- (BOOL)powerMarkerMode
|
||||||
|
{
|
||||||
|
return [[self model] powerMarkerMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode
|
||||||
|
{
|
||||||
|
[[self model] setPowerMarkerMode:aPowerMarkerMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)deltaValuesMode
|
||||||
|
{
|
||||||
|
return [[self model] deltaValuesMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode
|
||||||
|
{
|
||||||
|
[[self model] setDeltaValuesMode:aDeltaValuesMode];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Datasource */
|
||||||
|
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
NSString *identifier = [column identifier];
|
||||||
|
if ([identifier isEqual:@"marked"]) {
|
||||||
|
return [[self model] valueForColumn:@"marked" row:row];
|
||||||
|
}
|
||||||
|
return [[self model] valueForRow:row column:identifier];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
NSString *identifier = [column identifier];
|
||||||
|
if ([identifier isEqual:@"marked"]) {
|
||||||
|
[[self model] setValue:object forColumn:identifier row:row];
|
||||||
|
}
|
||||||
|
else if ([identifier isEqual:@"name"]) {
|
||||||
|
NSString *oldName = [[self model] valueForRow:row column:identifier];
|
||||||
|
NSString *newName = object;
|
||||||
|
if (![newName isEqual:oldName]) {
|
||||||
|
BOOL renamed = [[self model] renameSelected:newName];
|
||||||
|
if (!renamed) {
|
||||||
|
[Dialogs showMessage:[NSString stringWithFormat:NSLocalizedString(@"The name '%@' already exists.", @""), newName]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
[[self view] setNeedsDisplay:YES];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delegate */
|
||||||
|
- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)tableColumn
|
||||||
|
{
|
||||||
|
if ([[[self view] sortDescriptors] count] < 1)
|
||||||
|
return;
|
||||||
|
NSSortDescriptor *sd = [[[self view] sortDescriptors] objectAtIndex:0];
|
||||||
|
[[self model] sortBy:[sd key] ascending:[sd ascending]];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)column row:(NSInteger)row
|
||||||
|
{
|
||||||
|
BOOL isSelected = [[self view] isRowSelected:row];
|
||||||
|
BOOL isMarkable = n2b([[self model] valueForColumn:@"markable" row:row]);
|
||||||
|
if ([[column identifier] isEqual:@"marked"]) {
|
||||||
|
[cell setEnabled:isMarkable];
|
||||||
|
// Low-tech solution, for indentation, but it works...
|
||||||
|
NSCellImagePosition pos = isMarkable ? NSImageRight : NSImageLeft;
|
||||||
|
[cell setImagePosition:pos];
|
||||||
|
}
|
||||||
|
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
||||||
|
NSColor *color = [NSColor textColor];
|
||||||
|
if (isSelected) {
|
||||||
|
color = [NSColor selectedTextColor];
|
||||||
|
}
|
||||||
|
else if (isMarkable) {
|
||||||
|
if ([[self model] isDeltaAtRow:row column:[column identifier]]) {
|
||||||
|
color = [NSColor orangeColor];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
color = [NSColor blueColor];
|
||||||
|
}
|
||||||
|
[(NSTextFieldCell *)cell setTextColor:color];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tableViewHadDeletePressed:(NSTableView *)tableView
|
||||||
|
{
|
||||||
|
[[self model] removeSelected];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)tableViewHadSpacePressed:(NSTableView *)tableView
|
||||||
|
{
|
||||||
|
[[self model] markSelected];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Quicklook */
|
||||||
|
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel
|
||||||
|
{
|
||||||
|
return [[[self model] selectedRows] count];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index
|
||||||
|
{
|
||||||
|
NSArray *selectedRows = [[self model] selectedRows];
|
||||||
|
NSInteger absIndex = n2i([selectedRows objectAtIndex:index]);
|
||||||
|
NSString *path = [[self model] pathAtIndex:absIndex];
|
||||||
|
return [[HSQLPreviewItem alloc] initWithUrl:[NSURL fileURLWithPath:path] title:path];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event
|
||||||
|
{
|
||||||
|
// redirect all key down events to the table view
|
||||||
|
if ([event type] == NSKeyDown) {
|
||||||
|
[[self view] keyDown:event];
|
||||||
|
return YES;
|
||||||
|
}
|
||||||
|
return NO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)invalidateMarkings
|
||||||
|
{
|
||||||
|
[[self view] setNeedsDisplay:YES];
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "Outline.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface MatchesView : OutlineView
|
|
||||||
- (void)keyDown:(NSEvent *)theEvent;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@interface ResultWindowBase : NSWindowController
|
|
||||||
{
|
|
||||||
@protected
|
|
||||||
IBOutlet PyDupeGuruBase *py;
|
|
||||||
IBOutlet id app;
|
|
||||||
IBOutlet NSSegmentedControl *deltaSwitch;
|
|
||||||
IBOutlet MatchesView *matches;
|
|
||||||
IBOutlet NSSegmentedControl *pmSwitch;
|
|
||||||
IBOutlet NSTextField *stats;
|
|
||||||
IBOutlet NSMenu *columnsMenu;
|
|
||||||
|
|
||||||
BOOL _powerMode;
|
|
||||||
BOOL _displayDelta;
|
|
||||||
NSMutableArray *_resultColumns;
|
|
||||||
NSWindowController *preferencesPanel;
|
|
||||||
}
|
|
||||||
/* Helpers */
|
|
||||||
- (void)fillColumnsMenu;
|
|
||||||
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn;
|
|
||||||
- (NSArray *)getColumnsOrder;
|
|
||||||
- (NSDictionary *)getColumnsWidth;
|
|
||||||
- (NSArray *)getSelected:(BOOL)aDupesOnly;
|
|
||||||
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly;
|
|
||||||
- (void)initResultColumns;
|
|
||||||
- (void)updatePySelection;
|
|
||||||
- (void)performPySelection:(NSArray *)aIndexPaths;
|
|
||||||
- (void)refreshStats;
|
|
||||||
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth;
|
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
- (IBAction)changeDelta:(id)sender;
|
|
||||||
- (IBAction)changePowerMarker:(id)sender;
|
|
||||||
- (IBAction)copyMarked:(id)sender;
|
|
||||||
- (IBAction)deleteMarked:(id)sender;
|
|
||||||
- (IBAction)expandAll:(id)sender;
|
|
||||||
- (IBAction)exportToXHTML:(id)sender;
|
|
||||||
- (IBAction)moveMarked:(id)sender;
|
|
||||||
- (IBAction)resetColumnsToDefault:(id)sender;
|
|
||||||
- (IBAction)showPreferencesPanel:(id)sender;
|
|
||||||
- (IBAction)switchSelected:(id)sender;
|
|
||||||
- (IBAction)toggleColumn:(id)sender;
|
|
||||||
- (IBAction)toggleDetailsPanel:(id)sender;
|
|
||||||
- (IBAction)togglePowerMarker:(id)sender;
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)jobCompleted:(NSNotification *)aNotification;
|
|
||||||
@end
|
|
||||||
@@ -1,466 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "Dialogs.h"
|
|
||||||
#import "ProgressController.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "RegistrationInterface.h"
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation MatchesView
|
|
||||||
- (void)keyDown:(NSEvent *)theEvent
|
|
||||||
{
|
|
||||||
unichar key = [[theEvent charactersIgnoringModifiers] characterAtIndex:0];
|
|
||||||
// get flags and strip the lower 16 (device dependant) bits
|
|
||||||
unsigned int flags = ( [theEvent modifierFlags] & 0x00FF );
|
|
||||||
if (((key == NSDeleteFunctionKey) || (key == NSDeleteCharacter)) && (flags == 0))
|
|
||||||
[self sendAction:@selector(removeSelected:) to:[self delegate]];
|
|
||||||
else
|
|
||||||
if ((key == 0x20) && (flags == 0)) // Space
|
|
||||||
[self sendAction:@selector(markSelected:) to:[self delegate]];
|
|
||||||
else
|
|
||||||
[super keyDown:theEvent];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
|
|
||||||
{
|
|
||||||
if (![[tableColumn identifier] isEqual:@"0"])
|
|
||||||
return; //We only want to cover renames.
|
|
||||||
OVNode *node = item;
|
|
||||||
NSString *oldName = [[node buffer] objectAtIndex:0];
|
|
||||||
NSString *newName = object;
|
|
||||||
if (![newName isEqual:oldName])
|
|
||||||
{
|
|
||||||
BOOL renamed = n2b([(PyDupeGuruBase *)py renameSelected:newName]);
|
|
||||||
if (renamed)
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
else
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"The name '%@' already exists.",newName]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation ResultWindowBase
|
|
||||||
- (void)awakeFromNib
|
|
||||||
{
|
|
||||||
[self window];
|
|
||||||
preferencesPanel = [[NSWindowController alloc] initWithWindowNibName:@"Preferences"];
|
|
||||||
[self initResultColumns];
|
|
||||||
[self fillColumnsMenu];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(registrationRequired:) name:RegistrationRequired object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobInProgress:) name:JobInProgress object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsChanged:) name:ResultsChangedNotification object:nil];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsUpdated:) name:ResultsUpdatedNotification object:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[preferencesPanel release];
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 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:@"Reset to Default" action:@selector(resetColumnsToDefault:) keyEquivalent:@""];
|
|
||||||
[mi setTarget:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSTableColumn *)getColumnForIdentifier:(int)aIdentifier title:(NSString *)aTitle width:(int)aWidth refCol:(NSTableColumn *)aColumn
|
|
||||||
{
|
|
||||||
NSNumber *n = [NSNumber numberWithInt: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
|
|
||||||
{
|
|
||||||
NSTableColumn *col;
|
|
||||||
NSString *colId;
|
|
||||||
NSMutableArray *result = [NSMutableArray array];
|
|
||||||
NSEnumerator *e = [[matches tableColumns] objectEnumerator];
|
|
||||||
while (col = [e nextObject])
|
|
||||||
{
|
|
||||||
colId = [col identifier];
|
|
||||||
[result addObject:colId];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDictionary *)getColumnsWidth
|
|
||||||
{
|
|
||||||
NSMutableDictionary *result = [NSMutableDictionary dictionary];
|
|
||||||
NSTableColumn *col;
|
|
||||||
NSString *colId;
|
|
||||||
NSNumber *width;
|
|
||||||
NSEnumerator *e = [[matches tableColumns] objectEnumerator];
|
|
||||||
while (col = [e nextObject])
|
|
||||||
{
|
|
||||||
colId = [col identifier];
|
|
||||||
width = [NSNumber numberWithFloat:[col width]];
|
|
||||||
[result setObject:width forKey:colId];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray *)getSelected:(BOOL)aDupesOnly
|
|
||||||
{
|
|
||||||
if (_powerMode)
|
|
||||||
aDupesOnly = NO;
|
|
||||||
NSIndexSet *indexes = [matches selectedRowIndexes];
|
|
||||||
NSMutableArray *nodeList = [NSMutableArray array];
|
|
||||||
OVNode *node;
|
|
||||||
int i = [indexes firstIndex];
|
|
||||||
while (i != NSNotFound)
|
|
||||||
{
|
|
||||||
node = [matches itemAtRow:i];
|
|
||||||
if (!aDupesOnly || ([node level] > 1))
|
|
||||||
[nodeList addObject:node];
|
|
||||||
i = [indexes indexGreaterThanIndex:i];
|
|
||||||
}
|
|
||||||
return nodeList;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSArray *)getSelectedPaths:(BOOL)aDupesOnly
|
|
||||||
{
|
|
||||||
NSMutableArray *r = [NSMutableArray array];
|
|
||||||
NSArray *selected = [self getSelected:aDupesOnly];
|
|
||||||
NSEnumerator *e = [selected objectEnumerator];
|
|
||||||
OVNode *node;
|
|
||||||
while (node = [e nextObject])
|
|
||||||
[r addObject:p2a([node indexPath])];
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)initResultColumns
|
|
||||||
{
|
|
||||||
// Virtual
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)restoreColumnsPosition:(NSArray *)aColumnsOrder widths:(NSDictionary *)aColumnsWidth
|
|
||||||
{
|
|
||||||
NSTableColumn *col;
|
|
||||||
NSString *colId;
|
|
||||||
NSNumber *width;
|
|
||||||
NSMenuItem *mi;
|
|
||||||
//Remove all columns
|
|
||||||
NSEnumerator *e = [[columnsMenu itemArray] objectEnumerator];
|
|
||||||
while (mi = [e nextObject])
|
|
||||||
{
|
|
||||||
if ([mi state] == NSOnState)
|
|
||||||
[self toggleColumn:mi];
|
|
||||||
}
|
|
||||||
//Add columns and set widths
|
|
||||||
e = [aColumnsOrder objectEnumerator];
|
|
||||||
while (colId = [e nextObject])
|
|
||||||
{
|
|
||||||
if (![colId isEqual:@"mark"])
|
|
||||||
{
|
|
||||||
col = [_resultColumns objectAtIndex:[colId intValue]];
|
|
||||||
width = [aColumnsWidth objectForKey:[col identifier]];
|
|
||||||
mi = [columnsMenu itemWithTag:[colId intValue]];
|
|
||||||
if (width)
|
|
||||||
[col setWidth:[width floatValue]];
|
|
||||||
[self toggleColumn:mi];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updatePySelection
|
|
||||||
{
|
|
||||||
NSArray *selection;
|
|
||||||
if (_powerMode)
|
|
||||||
selection = [py selectedPowerMarkerNodePaths];
|
|
||||||
else
|
|
||||||
selection = [py selectedResultNodePaths];
|
|
||||||
[matches selectNodePaths:selection];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)performPySelection:(NSArray *)aIndexPaths
|
|
||||||
{
|
|
||||||
if (_powerMode)
|
|
||||||
[py selectPowerMarkerNodePaths:aIndexPaths];
|
|
||||||
else
|
|
||||||
[py selectResultNodePaths:aIndexPaths];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)refreshStats
|
|
||||||
{
|
|
||||||
[stats setStringValue:[py getStatLine]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
- (IBAction)changeDelta:(id)sender
|
|
||||||
{
|
|
||||||
_displayDelta = [deltaSwitch selectedSegment] == 1;
|
|
||||||
[py setDisplayDeltaValues:b2n(_displayDelta)];
|
|
||||||
[matches reloadData];
|
|
||||||
[self expandAll:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)changePowerMarker:(id)sender
|
|
||||||
{
|
|
||||||
_powerMode = [pmSwitch selectedSegment] == 1;
|
|
||||||
if (_powerMode)
|
|
||||||
[matches setTag:2];
|
|
||||||
else
|
|
||||||
[matches setTag:0];
|
|
||||||
[self expandAll:nil];
|
|
||||||
[self outlineView:matches didClickTableColumn:nil];
|
|
||||||
[self updatePySelection];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)copyMarked:(id)sender
|
|
||||||
{
|
|
||||||
int mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count)
|
|
||||||
return;
|
|
||||||
NSOpenPanel *op = [NSOpenPanel openPanel];
|
|
||||||
[op setCanChooseFiles:NO];
|
|
||||||
[op setCanChooseDirectories:YES];
|
|
||||||
[op setCanCreateDirectories:YES];
|
|
||||||
[op setAllowsMultipleSelection:NO];
|
|
||||||
[op setTitle:@"Select a directory to copy marked files to"];
|
|
||||||
if ([op runModalForTypes:nil] == NSOKButton)
|
|
||||||
{
|
|
||||||
NSString *directory = [[op filenames] objectAtIndex:0];
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py copyOrMove:b2n(YES) markedTo:directory recreatePath:[ud objectForKey:@"recreatePathType"]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)deleteMarked:(id)sender
|
|
||||||
{
|
|
||||||
int mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count)
|
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to send %d files to Trash. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py setRemoveEmptyFolders:[ud objectForKey:@"removeEmptyFolders"]];
|
|
||||||
[py deleteMarked];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)expandAll:(id)sender
|
|
||||||
{
|
|
||||||
for (int i=0;i < [matches numberOfRows];i++)
|
|
||||||
[matches expandItem:[matches itemAtRow:i]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)exportToXHTML:(id)sender
|
|
||||||
{
|
|
||||||
NSString *exported = [py exportToXHTMLwithColumns:[self getColumnsOrder]];
|
|
||||||
[[NSWorkspace sharedWorkspace] openFile:exported];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)moveMarked:(id)sender
|
|
||||||
{
|
|
||||||
int mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count)
|
|
||||||
return;
|
|
||||||
NSOpenPanel *op = [NSOpenPanel openPanel];
|
|
||||||
[op setCanChooseFiles:NO];
|
|
||||||
[op setCanChooseDirectories:YES];
|
|
||||||
[op setCanCreateDirectories:YES];
|
|
||||||
[op setAllowsMultipleSelection:NO];
|
|
||||||
[op setTitle:@"Select a directory to move marked files to"];
|
|
||||||
if ([op runModalForTypes:nil] == NSOKButton)
|
|
||||||
{
|
|
||||||
NSString *directory = [[op filenames] objectAtIndex:0];
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py setRemoveEmptyFolders:[ud objectForKey:@"removeEmptyFolders"]];
|
|
||||||
[py copyOrMove:b2n(NO) markedTo:directory recreatePath:[ud objectForKey:@"recreatePathType"]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)resetColumnsToDefault:(id)sender
|
|
||||||
{
|
|
||||||
// Virtual
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)showPreferencesPanel:(id)sender
|
|
||||||
{
|
|
||||||
[preferencesPanel showWindow:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)switchSelected:(id)sender
|
|
||||||
{
|
|
||||||
// It might look like a complicated way to get the length of the current dupe list on the py side
|
|
||||||
// but after a lot of fussing around, believe it or not, it actually is.
|
|
||||||
int matchesTag = _powerMode ? 2 : 0;
|
|
||||||
int startLen = [[py getOutlineView:matchesTag childCountsForPath:[NSArray array]] count];
|
|
||||||
[self performPySelection:[self getSelectedPaths:YES]];
|
|
||||||
[py makeSelectedReference];
|
|
||||||
// In some cases (when in a filtered view in Power Marker mode, it's possible that the demoted
|
|
||||||
// ref is not a part of the filter, making the table smaller. In those cases, we want to do a
|
|
||||||
// complete reload of the table to avoid a crash.
|
|
||||||
if ([[py getOutlineView:matchesTag childCountsForPath:[NSArray array]] count] == startLen)
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsUpdatedNotification object:self];
|
|
||||||
else
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)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
|
|
||||||
{
|
|
||||||
[[(AppDelegateBase *)app detailsPanel] toggleVisibility];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)togglePowerMarker:(id)sender
|
|
||||||
{
|
|
||||||
if ([pmSwitch selectedSegment] == 1)
|
|
||||||
[pmSwitch setSelectedSegment:0];
|
|
||||||
else
|
|
||||||
[pmSwitch setSelectedSegment:1];
|
|
||||||
[self changePowerMarker:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delegate */
|
|
||||||
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView didClickTableColumn:(NSTableColumn *)tableColumn
|
|
||||||
{
|
|
||||||
if ([[outlineView sortDescriptors] count] < 1)
|
|
||||||
return;
|
|
||||||
NSSortDescriptor *sd = [[outlineView sortDescriptors] objectAtIndex:0];
|
|
||||||
if (_powerMode)
|
|
||||||
[py sortDupesBy:i2n([[sd key] intValue]) ascending:b2n([sd ascending])];
|
|
||||||
else
|
|
||||||
[py sortGroupsBy:i2n([[sd key] intValue]) ascending:b2n([sd ascending])];
|
|
||||||
[matches reloadData];
|
|
||||||
[self expandAll:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)windowWillClose:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[NSApp hide:NSApp];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)jobCompleted:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
int r = n2i([py getOperationalErrorCount]);
|
|
||||||
id lastAction = [[ProgressController mainProgressController] jobId];
|
|
||||||
if ([lastAction isEqualTo:jobCopy]) {
|
|
||||||
if (r > 0)
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be copied.",r]];
|
|
||||||
else
|
|
||||||
[Dialogs showMessage:@"All marked files were copied sucessfully."];
|
|
||||||
}
|
|
||||||
else if ([lastAction isEqualTo:jobMove]) {
|
|
||||||
if (r > 0)
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:@"%d file(s) couldn't be moved. They were kept in the results, and still are marked.",r]];
|
|
||||||
else
|
|
||||||
[Dialogs showMessage:@"All marked files were moved sucessfully."];
|
|
||||||
}
|
|
||||||
else if ([lastAction isEqualTo:jobDelete]) {
|
|
||||||
if (r > 0) {
|
|
||||||
NSString *msg = @"%d file(s) couldn't be sent to Trash. They were kept in the results, "\
|
|
||||||
"and still are marked. See the F.A.Q. section in the help file for details.";
|
|
||||||
[Dialogs showMessage:[NSString stringWithFormat:msg,r]];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
[Dialogs showMessage:@"All marked files were sucessfully sent to Trash."];
|
|
||||||
}
|
|
||||||
else if ([lastAction isEqualTo:jobScan]) {
|
|
||||||
NSInteger groupCount = [[py getOutlineView:0 childCountsForPath:[NSArray array]] count];
|
|
||||||
if (groupCount == 0)
|
|
||||||
[Dialogs showMessage:@"No duplicates found."];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-activate toolbar items right after the progress bar stops showing instead of waiting until
|
|
||||||
// a mouse-over is performed
|
|
||||||
[[[self window] toolbar] validateVisibleItems];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)jobInProgress:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[Dialogs showMessage:@"A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)jobStarted:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSDictionary *ui = [aNotification userInfo];
|
|
||||||
NSString *desc = [ui valueForKey:@"desc"];
|
|
||||||
[[ProgressController mainProgressController] setJobDesc:desc];
|
|
||||||
NSString *jobid = [ui valueForKey:@"jobid"];
|
|
||||||
// NSLog(jobid);
|
|
||||||
[[ProgressController mainProgressController] setJobId:jobid];
|
|
||||||
[[ProgressController mainProgressController] showSheetForParent:[self window]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)registrationRequired:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSString *msg = @"This is a demo version, which only allows you 10 delete/copy/move actions per session. You cannot continue.";
|
|
||||||
[Dialogs showMessage:msg];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)resultsChanged:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[matches reloadData];
|
|
||||||
[self expandAll:nil];
|
|
||||||
[self outlineViewSelectionDidChange:nil];
|
|
||||||
[self refreshStats];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)resultsUpdated:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[matches invalidateBuffers];
|
|
||||||
[matches invalidateMarkings];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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 2013 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 2013 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
|
||||||
17
cocoa/base/StatsLabel.h
Normal file
17
cocoa/base/StatsLabel.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#import "HSGUIController.h"
|
||||||
|
#import "PyStatsLabel.h"
|
||||||
|
|
||||||
|
@interface StatsLabel : HSGUIController {}
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aLabelView;
|
||||||
|
- (PyStatsLabel *)model;
|
||||||
|
- (NSTextField *)labelView;
|
||||||
|
@end
|
||||||
34
cocoa/base/StatsLabel.m
Normal file
34
cocoa/base/StatsLabel.m
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
|
This software is licensed under the "BSD" License as described in the "LICENSE" file,
|
||||||
|
which should be included with this package. The terms are also available at
|
||||||
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "StatsLabel.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
|
||||||
|
@implementation StatsLabel
|
||||||
|
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aLabelView
|
||||||
|
{
|
||||||
|
return [super initWithPyRef:aPyRef wrapperClass:[PyStatsLabel class]
|
||||||
|
callbackClassName:@"StatsLabelView" view:aLabelView];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (PyStatsLabel *)model
|
||||||
|
{
|
||||||
|
return (PyStatsLabel *)model;
|
||||||
|
}
|
||||||
|
|
||||||
|
- (NSTextField *)labelView
|
||||||
|
{
|
||||||
|
return (NSTextField *)view;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)refresh
|
||||||
|
{
|
||||||
|
[[self labelView] setStringValue:[[self model] display]];
|
||||||
|
}
|
||||||
|
@end
|
||||||
151
cocoa/base/en.lproj/Localizable.strings
Normal file
151
cocoa/base/en.lproj/Localizable.strings
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
|
||||||
|
"%@ 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 iTunes Library" = "Add iTunes Library";
|
||||||
|
"Add New Folder..." = "Add New Folder...";
|
||||||
|
"Add Selected to Ignore List" = "Add Selected to Ignore List";
|
||||||
|
"Advanced" = "Advanced";
|
||||||
|
"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.";
|
||||||
|
"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";
|
||||||
|
"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";
|
||||||
|
"You have unsaved results, do you really want to continue?" = "You have unsaved results, do you really want to continue?";
|
||||||
|
"You have unsaved results, do you really want to quit?" = "You have unsaved results, do you really want to quit?";
|
||||||
|
"Zoom" = "Zoom";
|
||||||
49
cocoa/base/main.m
Normal file
49
cocoa/base/main.m
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2013 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", "Trigger-happy mode"],
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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 2013 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)
|
||||||
343
cocoa/inter/app_pe.py
Normal file
343
cocoa/inter/app_pe.py
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
# Created By: Virgil Dupras
|
||||||
|
# Created On: 2006/11/13
|
||||||
|
# Copyright 2013 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,
|
||||||
|
ScanType.TriggerHappyMode,
|
||||||
|
][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 2013 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 2013 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,24 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
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 "DirectoryPanel.h"
|
|
||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase
|
@interface AppDelegate : AppDelegateBase {}
|
||||||
{
|
- (void)removeDeadTracks;
|
||||||
DirectoryPanel *_directoryPanel;
|
|
||||||
}
|
|
||||||
- (IBAction)openWebsite:(id)sender;
|
|
||||||
- (IBAction)toggleDirectories:(id)sender;
|
|
||||||
|
|
||||||
- (DirectoryPanel *)directoryPanel;
|
|
||||||
- (PyDupeGuru *)py;
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,45 +1,36 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "../../cocoalib/ProgressController.h"
|
#import "ProgressController.h"
|
||||||
#import "../../cocoalib/RegistrationInterface.h"
|
#import "Utils.h"
|
||||||
#import "../../cocoalib/Utils.h"
|
#import "ValueTransformers.h"
|
||||||
#import "../../cocoalib/ValueTransformers.h"
|
#import "Dialogs.h"
|
||||||
#import "../../cocoalib/Dialogs.h"
|
|
||||||
#import "DetailsPanel.h"
|
#import "DetailsPanel.h"
|
||||||
|
#import "DirectoryPanel.h"
|
||||||
|
#import "ResultWindow.h"
|
||||||
#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:@"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
|
||||||
@@ -55,62 +46,23 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)openWebsite:(id)sender
|
- (NSString *)homepageURL
|
||||||
{
|
{
|
||||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru_me"]];
|
return @"http://www.hardcoded.net/dupeguru_me/";
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)toggleDirectories:(id)sender
|
- (ResultWindowBase *)createResultWindow
|
||||||
{
|
{
|
||||||
[[self directoryPanel] toggleVisible:sender];
|
return [[ResultWindow alloc] initWithParentApp:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (DirectoryPanel *)createDirectoryPanel
|
||||||
- (DetailsPanelBase *)detailsPanel
|
|
||||||
{
|
{
|
||||||
if (!_detailsPanel)
|
return [[DirectoryPanelME alloc] initWithParentApp:self];
|
||||||
_detailsPanel = [[DetailsPanel alloc] initWithPy:py];
|
|
||||||
return _detailsPanel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (DirectoryPanel *)directoryPanel
|
- (void)removeDeadTracks
|
||||||
{
|
{
|
||||||
if (!_directoryPanel)
|
[(ResultWindow *)[self resultWindow] removeDeadTracks];
|
||||||
_directoryPanel = [[DirectoryPanel alloc] initWithParentApp:self];
|
|
||||||
return _directoryPanel;
|
|
||||||
}
|
|
||||||
- (PyDupeGuru *)py { return (PyDupeGuru *)py; }
|
|
||||||
|
|
||||||
//Delegate
|
|
||||||
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
if (![[result window] isVisible])
|
|
||||||
[result showWindow:NSApp];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)applicationWillTerminate:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[ud setObject: [result getColumnsOrder] forKey:@"columnsOrder"];
|
|
||||||
[ud setObject: [result getColumnsWidth] forKey:@"columnsWidth"];
|
|
||||||
[py saveIgnoreList];
|
|
||||||
[py saveResults];
|
|
||||||
int sc = [ud integerForKey:@"sessionCountSinceLastIgnorePurge"];
|
|
||||||
if (sc >= 10)
|
|
||||||
{
|
|
||||||
sc = -1;
|
|
||||||
[py purgeIgnoreList];
|
|
||||||
}
|
|
||||||
sc++;
|
|
||||||
[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 RecentDirectories so it saves the user defaults
|
|
||||||
[recentDirectories release];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)recentDirecoryClicked:(NSString *)directory
|
|
||||||
{
|
|
||||||
[[self directoryPanel] addDirectory:directory];
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "../base/Consts.h"
|
#import "../base/Consts.h"
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "../base/DetailsPanel.h"
|
#import "DetailsPanelBase.h"
|
||||||
|
|
||||||
|
|
||||||
@interface DetailsPanel : DetailsPanelBase
|
@interface DetailsPanel : DetailsPanelBase
|
||||||
@end
|
@end
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "DetailsPanel.h"
|
#import "DetailsPanel.h"
|
||||||
|
#import "DetailsPanel_UI.h"
|
||||||
|
|
||||||
@implementation DetailsPanel
|
@implementation DetailsPanel
|
||||||
|
- (NSWindow *)createWindow
|
||||||
|
{
|
||||||
|
return createDetailsPanel_UI(self);
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "../base/DirectoryPanel.h"
|
#import "../base/DirectoryPanel.h"
|
||||||
|
|
||||||
@interface DirectoryPanel : DirectoryPanelBase
|
@interface DirectoryPanelME : DirectoryPanel
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
- (IBAction)addiTunes:(id)sender;
|
- (IBAction)addiTunes:(id)sender;
|
||||||
|
|||||||
@@ -1,31 +1,32 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "DirectoryPanel.h"
|
#import "DirectoryPanel.h"
|
||||||
|
|
||||||
@implementation DirectoryPanel
|
@implementation DirectoryPanelME
|
||||||
- (IBAction)addiTunes:(id)sender
|
- (id)initWithParentApp:(id)aParentApp
|
||||||
{
|
{
|
||||||
[self addDirectory:[@"~/Music/iTunes/iTunes Music" stringByExpandingTildeInPath]];
|
self = [super initWithParentApp:aParentApp];
|
||||||
|
_alwaysShowPopUp = YES;
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)popupAddDirectoryMenu:(id)sender
|
- (void)fillPopUpMenu
|
||||||
{
|
{
|
||||||
|
[super fillPopUpMenu];
|
||||||
NSMenu *m = [addButtonPopUp menu];
|
NSMenu *m = [addButtonPopUp menu];
|
||||||
while ([m numberOfItems] > 0)
|
NSMenuItem *mi = [m insertItemWithTitle:NSLocalizedString(@"Add iTunes Library", @"") action:@selector(addiTunes:)
|
||||||
[m removeItemAtIndex:0];
|
keyEquivalent:@"" atIndex:1];
|
||||||
NSMenuItem *mi = [m addItemWithTitle:@"Add New Directory..." action:@selector(askForDirectory:) keyEquivalent:@""];
|
|
||||||
[mi setTarget:self];
|
[mi setTarget:self];
|
||||||
mi = [m addItemWithTitle:@"Add iTunes Directory" action:@selector(addiTunes:) keyEquivalent:@""];
|
}
|
||||||
[mi setTarget:self];
|
|
||||||
[m addItem:[NSMenuItem separatorItem]];
|
- (IBAction)addiTunes:(id)sender
|
||||||
[_recentDirectories fillMenu:m];
|
{
|
||||||
[addButtonPopUp selectItem: nil];
|
[self addDirectory:@"iTunes Library"];
|
||||||
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
|
||||||
}
|
}
|
||||||
@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,23 +13,25 @@
|
|||||||
<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>5.7.0</string>
|
<string>{version}</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<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 "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "../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;
|
|
||||||
- (int)deadTrackCount;
|
|
||||||
@end
|
|
||||||
@@ -1,38 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "../../cocoalib/Outline.h"
|
#import "ResultWindowBase.h"
|
||||||
#import "../base/ResultWindow.h"
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
|
|
||||||
@interface ResultWindow : ResultWindowBase
|
@interface ResultWindow : ResultWindowBase {}
|
||||||
{
|
- (void)removeDeadTracks;
|
||||||
IBOutlet NSSearchField *filterField;
|
|
||||||
|
|
||||||
NSString *_lastAction;
|
|
||||||
NSMutableIndexSet *_deltaColumns;
|
|
||||||
}
|
|
||||||
- (IBAction)clearIgnoreList:(id)sender;
|
|
||||||
- (IBAction)filter:(id)sender;
|
|
||||||
- (IBAction)ignoreSelected:(id)sender;
|
|
||||||
- (IBAction)markAll:(id)sender;
|
|
||||||
- (IBAction)markInvert:(id)sender;
|
|
||||||
- (IBAction)markNone:(id)sender;
|
|
||||||
- (IBAction)markSelected:(id)sender;
|
|
||||||
- (IBAction)markToggle:(id)sender;
|
|
||||||
- (IBAction)openSelected:(id)sender;
|
|
||||||
- (IBAction)refresh:(id)sender;
|
|
||||||
- (IBAction)removeDeadTracks:(id)sender;
|
|
||||||
- (IBAction)removeMarked:(id)sender;
|
|
||||||
- (IBAction)removeSelected:(id)sender;
|
|
||||||
- (IBAction)renameSelected:(id)sender;
|
|
||||||
- (IBAction)revealSelected:(id)sender;
|
|
||||||
- (IBAction)startDuplicateScan:(id)sender;
|
|
||||||
- (IBAction)toggleDelta:(id)sender;
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,301 +1,77 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "ResultWindow.h"
|
#import "ResultWindow.h"
|
||||||
#import "../../cocoalib/Dialogs.h"
|
#import "Dialogs.h"
|
||||||
#import "../../cocoalib/ProgressController.h"
|
#import "Utils.h"
|
||||||
#import "../../cocoalib/RegistrationInterface.h"
|
#import "PyDupeGuru.h"
|
||||||
#import "../../cocoalib/Utils.h"
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "Consts.h"
|
#import "Consts.h"
|
||||||
|
#import "ProgressController.h"
|
||||||
|
|
||||||
@implementation ResultWindow
|
@implementation ResultWindow
|
||||||
/* Override */
|
/* Override */
|
||||||
- (void)awakeFromNib
|
- (void)setScanOptions
|
||||||
{
|
{
|
||||||
[super awakeFromNib];
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
[[self window] setTitle:@"dupeGuru Music Edition"];
|
[model setScanType:n2i([ud objectForKey:@"scanType"])];
|
||||||
_displayDelta = NO;
|
[model enable:n2b([ud objectForKey:@"scanTagTrack"]) scanForTag:@"track"];
|
||||||
_powerMode = NO;
|
[model enable:n2b([ud objectForKey:@"scanTagArtist"]) scanForTag:@"artist"];
|
||||||
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,7)] retain];
|
[model enable:n2b([ud objectForKey:@"scanTagAlbum"]) scanForTag:@"album"];
|
||||||
[_deltaColumns removeIndex:6];
|
[model enable:n2b([ud objectForKey:@"scanTagTitle"]) scanForTag:@"title"];
|
||||||
[deltaSwitch setSelectedSegment:0];
|
[model enable:n2b([ud objectForKey:@"scanTagGenre"]) scanForTag:@"genre"];
|
||||||
[pmSwitch setSelectedSegment:0];
|
[model enable:n2b([ud objectForKey:@"scanTagYear"]) scanForTag:@"year"];
|
||||||
[py setDisplayDeltaValues:b2n(_displayDelta)];
|
[model setMinMatchPercentage:n2i([ud objectForKey:@"minMatchPercentage"])];
|
||||||
[matches setTarget:self];
|
[model setWordWeighting:n2b([ud objectForKey:@"wordWeighting"])];
|
||||||
[matches setDoubleAction:@selector(openSelected:)];
|
[model setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
|
||||||
[self refreshStats];
|
[model setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
|
[model setMatchSimilarWords:n2b([ud objectForKey:@"matchSimilarWords"])];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)initResultColumns
|
||||||
|
{
|
||||||
|
HSColumnDef defs[] = {
|
||||||
|
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
|
||||||
|
{@"name", 235, 16, 0, YES, nil},
|
||||||
|
{@"folder_path", 120, 16, 0, YES, nil},
|
||||||
|
{@"size", 63, 16, 0, YES, nil},
|
||||||
|
{@"duration", 50, 16, 0, YES, nil},
|
||||||
|
{@"bitrate", 50, 16, 0, YES, nil},
|
||||||
|
{@"samplerate", 60, 16, 0, YES, nil},
|
||||||
|
{@"extension", 40, 16, 0, YES, nil},
|
||||||
|
{@"mtime", 120, 16, 0, YES, nil},
|
||||||
|
{@"title", 120, 16, 0, YES, nil},
|
||||||
|
{@"artist", 120, 16, 0, YES, nil},
|
||||||
|
{@"album", 120, 16, 0, YES, nil},
|
||||||
|
{@"genre", 80, 16, 0, YES, nil},
|
||||||
|
{@"year", 40, 16, 0, YES, nil},
|
||||||
|
{@"track", 40, 16, 0, YES, nil},
|
||||||
|
{@"comment", 120, 16, 0, YES, nil},
|
||||||
|
{@"percentage", 57, 16, 0, YES, nil},
|
||||||
|
{@"words", 120, 16, 0, YES, nil},
|
||||||
|
{@"dupe_count", 80, 16, 0, YES, nil},
|
||||||
|
nil
|
||||||
|
};
|
||||||
|
[[table columns] initializeColumns:defs];
|
||||||
|
NSTableColumn *c = [matches tableColumnWithIdentifier:@"marked"];
|
||||||
|
[[c dataCell] setButtonType:NSSwitchButton];
|
||||||
|
[[c dataCell] setControlSize:NSSmallControlSize];
|
||||||
|
c = [matches tableColumnWithIdentifier:@"size"];
|
||||||
|
[[c dataCell] setAlignment:NSRightTextAlignment];
|
||||||
|
c = [matches tableColumnWithIdentifier:@"duration"];
|
||||||
|
[[c dataCell] setAlignment:NSRightTextAlignment];
|
||||||
|
c = [matches tableColumnWithIdentifier:@"bitrate"];
|
||||||
|
[[c dataCell] setAlignment:NSRightTextAlignment];
|
||||||
|
[[table columns] restoreColumns];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
- (IBAction)clearIgnoreList:(id)sender
|
- (void)removeDeadTracks
|
||||||
{
|
{
|
||||||
int i = n2i([py getIgnoreListCount]);
|
[model scanDeadTracks];
|
||||||
if (!i)
|
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"Do you really want to remove all %d items from the ignore list?",i]] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[py clearIgnoreList];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)filter:(id)sender
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py setEscapeFilterRegexp:b2n(!n2b([ud objectForKey:@"useRegexpFilter"]))];
|
|
||||||
[py applyFilter:[filterField stringValue]];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)ignoreSelected:(id)sender
|
|
||||||
{
|
|
||||||
NSArray *nodeList = [self getSelected:YES];
|
|
||||||
if (![nodeList count])
|
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[self performPySelection:[self getSelectedPaths:YES]];
|
|
||||||
[py addSelectedToIgnoreList];
|
|
||||||
[py removeSelected];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markAll:(id)sender
|
|
||||||
{
|
|
||||||
[py markAll];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markInvert:(id)sender
|
|
||||||
{
|
|
||||||
[py markInvert];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markNone:(id)sender
|
|
||||||
{
|
|
||||||
[py markNone];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markSelected:(id)sender
|
|
||||||
{
|
|
||||||
[self performPySelection:[self getSelectedPaths:YES]];
|
|
||||||
[py toggleSelectedMark];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markToggle:(id)sender
|
|
||||||
{
|
|
||||||
OVNode *node = [matches itemAtRow:[matches clickedRow]];
|
|
||||||
[self performPySelection:[NSArray arrayWithObject:p2a([node indexPath])]];
|
|
||||||
[py toggleSelectedMark];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)openSelected:(id)sender
|
|
||||||
{
|
|
||||||
[self performPySelection:[self getSelectedPaths:NO]];
|
|
||||||
[py openSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)refresh:(id)sender
|
|
||||||
{
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)removeDeadTracks:(id)sender
|
|
||||||
{
|
|
||||||
[(PyDupeGuru *)py scanDeadTracks];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)removeMarked:(id)sender
|
|
||||||
{
|
|
||||||
int mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count)
|
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[py removeMarked];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)removeSelected:(id)sender
|
|
||||||
{
|
|
||||||
NSArray *nodeList = [self getSelected:YES];
|
|
||||||
if (![nodeList count])
|
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[self performPySelection:[self getSelectedPaths:YES]];
|
|
||||||
[py removeSelected];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)renameSelected:(id)sender
|
|
||||||
{
|
|
||||||
int col = [matches columnWithIdentifier:@"0"];
|
|
||||||
int row = [matches selectedRow];
|
|
||||||
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)resetColumnsToDefault:(id)sender
|
|
||||||
{
|
|
||||||
NSMutableArray *columnsOrder = [NSMutableArray array];
|
|
||||||
[columnsOrder addObject:@"0"];
|
|
||||||
[columnsOrder addObject:@"2"];
|
|
||||||
[columnsOrder addObject:@"3"];
|
|
||||||
[columnsOrder addObject:@"4"];
|
|
||||||
[columnsOrder addObject:@"16"];
|
|
||||||
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
|
||||||
[columnsWidth setObject:i2n(214) forKey:@"0"];
|
|
||||||
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
|
||||||
[columnsWidth setObject:i2n(50) forKey:@"3"];
|
|
||||||
[columnsWidth setObject:i2n(50) forKey:@"4"];
|
|
||||||
[columnsWidth setObject:i2n(57) forKey:@"16"];
|
|
||||||
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)revealSelected:(id)sender
|
|
||||||
{
|
|
||||||
[self performPySelection:[self getSelectedPaths:NO]];
|
|
||||||
[py revealSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)startDuplicateScan:(id)sender
|
|
||||||
{
|
|
||||||
if ([matches numberOfRows] > 0)
|
|
||||||
{
|
|
||||||
if ([Dialogs askYesNo:@"Are you sure you want to start a new duplicate scan?"] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
PyDupeGuru *_py = (PyDupeGuru *)py;
|
|
||||||
[_py setScanType:[ud objectForKey:@"scanType"]];
|
|
||||||
[_py enable:[ud objectForKey:@"scanTagTrack"] scanForTag:@"track"];
|
|
||||||
[_py enable:[ud objectForKey:@"scanTagArtist"] scanForTag:@"artist"];
|
|
||||||
[_py enable:[ud objectForKey:@"scanTagAlbum"] scanForTag:@"album"];
|
|
||||||
[_py enable:[ud objectForKey:@"scanTagTitle"] scanForTag:@"title"];
|
|
||||||
[_py enable:[ud objectForKey:@"scanTagGenre"] scanForTag:@"genre"];
|
|
||||||
[_py enable:[ud objectForKey:@"scanTagYear"] scanForTag:@"year"];
|
|
||||||
[_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]];
|
|
||||||
[_py setWordWeighting:[ud objectForKey:@"wordWeighting"]];
|
|
||||||
[_py setMixFileKind:[ud objectForKey:@"mixFileKind"]];
|
|
||||||
[_py setMatchSimilarWords:[ud objectForKey:@"matchSimilarWords"]];
|
|
||||||
int r = n2i([py doScan]);
|
|
||||||
[matches reloadData];
|
|
||||||
[self refreshStats];
|
|
||||||
if (r == 1)
|
|
||||||
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
|
|
||||||
if (r == 3)
|
|
||||||
{
|
|
||||||
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
|
||||||
[app toggleDirectories:nil];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleDelta:(id)sender
|
|
||||||
{
|
|
||||||
if ([deltaSwitch selectedSegment] == 1)
|
|
||||||
[deltaSwitch setSelectedSegment:0];
|
|
||||||
else
|
|
||||||
[deltaSwitch setSelectedSegment:1];
|
|
||||||
[self changeDelta:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Public */
|
|
||||||
- (void)initResultColumns
|
|
||||||
{
|
|
||||||
NSTableColumn *refCol = [matches tableColumnWithIdentifier:@"0"];
|
|
||||||
_resultColumns = [[NSMutableArray alloc] init];
|
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"0"]]; // File Name
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:1 title:@"Directory" width:120 refCol:refCol]];
|
|
||||||
NSTableColumn *sizeCol = [self getColumnForIdentifier:2 title:@"Size (MB)" width:63 refCol:refCol];
|
|
||||||
[[sizeCol dataCell] setAlignment:NSRightTextAlignment];
|
|
||||||
[_resultColumns addObject:sizeCol];
|
|
||||||
NSTableColumn *timeCol = [self getColumnForIdentifier:3 title:@"Time" width:50 refCol:refCol];
|
|
||||||
[[timeCol dataCell] setAlignment:NSRightTextAlignment];
|
|
||||||
[_resultColumns addObject:timeCol];
|
|
||||||
NSTableColumn *brCol = [self getColumnForIdentifier:4 title:@"Bitrate" width:50 refCol:refCol];
|
|
||||||
[[brCol dataCell] setAlignment:NSRightTextAlignment];
|
|
||||||
[_resultColumns addObject:brCol];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Sample Rate" width:60 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Kind" width:40 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Creation" width:120 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Modification" width:120 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:9 title:@"Title" width:120 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:10 title:@"Artist" width:120 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:11 title:@"Album" width:120 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:12 title:@"Genre" width:80 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:13 title:@"Year" width:40 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:14 title:@"Track Number" width:40 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:15 title:@"Comment" width:120 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:16 title:@"Match %" width:57 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:17 title:@"Words Used" width:120 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:18 title:@"Dupe Count" width:80 refCol:refCol]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delegate */
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
|
||||||
{
|
|
||||||
OVNode *node = item;
|
|
||||||
if ([[tableColumn identifier] isEqual:@"mark"])
|
|
||||||
{
|
|
||||||
[cell setEnabled: [node isMarkable]];
|
|
||||||
}
|
|
||||||
if ([cell isKindOfClass:[NSTextFieldCell class]])
|
|
||||||
{
|
|
||||||
// Determine if the text color will be blue due to directory being reference.
|
|
||||||
NSTextFieldCell *textCell = cell;
|
|
||||||
if ([node isMarkable])
|
|
||||||
[textCell setTextColor:[NSColor blackColor]];
|
|
||||||
else
|
|
||||||
[textCell setTextColor:[NSColor blueColor]];
|
|
||||||
if ((_displayDelta) && (_powerMode || ([node level] > 1)))
|
|
||||||
{
|
|
||||||
int i = [[tableColumn identifier] intValue];
|
|
||||||
if ([_deltaColumns containsIndex:i])
|
|
||||||
[textCell setTextColor:[NSColor orangeColor]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)jobCompleted:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[super jobCompleted:aNotification];
|
|
||||||
id lastAction = [[ProgressController mainProgressController] jobId];
|
|
||||||
if ([lastAction isEqualTo:jobScanDeadTracks])
|
|
||||||
{
|
|
||||||
int deadTrackCount = [(PyDupeGuru *)py deadTrackCount];
|
|
||||||
if (deadTrackCount > 0)
|
|
||||||
{
|
|
||||||
NSString *msg = @"Your iTunes Library contains %d dead tracks ready to be removed. Continue?";
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:msg,deadTrackCount]] == NSAlertFirstButtonReturn)
|
|
||||||
[(PyDupeGuru *)py removeDeadTracks];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
[Dialogs showMessage:@"You have no dead tracks in your iTunes Library"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
|
|
||||||
{
|
|
||||||
[self performPySelection:[self getSelectedPaths:NO]];
|
|
||||||
[py refreshDetailsWithSelected];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:DuplicateSelectionChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)resultsMarkingChanged:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[matches invalidateMarkings];
|
|
||||||
[self refreshStats];
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,244 +1,17 @@
|
|||||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
#
|
#
|
||||||
# This software is licensed under the "HS" 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/hs_license
|
# http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
|
||||||
import objc
|
from hscommon.trans import install_gettext_trans_under_cocoa
|
||||||
from AppKit import *
|
install_gettext_trans_under_cocoa()
|
||||||
|
|
||||||
from core_me.app_cocoa import DupeGuruME
|
from cocoa.inter import PySelectableList, PyColumns, PyTable
|
||||||
from core.scanner import (SCAN_TYPE_FILENAME, SCAN_TYPE_FIELDS, SCAN_TYPE_FIELDS_NO_ORDER,
|
|
||||||
SCAN_TYPE_TAG, SCAN_TYPE_CONTENT, SCAN_TYPE_CONTENT_AUDIO)
|
|
||||||
|
|
||||||
# Fix py2app imports which chokes on relative imports
|
from inter.all import *
|
||||||
from core_me import app_cocoa, data, fs, scanner
|
from inter.app_me import PyDupeGuru
|
||||||
from core import app, app_cocoa, data, directories, engine, export, ignore, results, scanner, fs
|
|
||||||
from hsmedia import aiff, flac, genres, id3v1, id3v2, mp4, mpeg, ogg, wma
|
|
||||||
from hsutil import conflict
|
|
||||||
|
|
||||||
class PyApp(NSObject):
|
|
||||||
pass #fake class
|
|
||||||
|
|
||||||
class PyDupeGuru(PyApp):
|
|
||||||
def init(self):
|
|
||||||
self = super(PyDupeGuru,self).init()
|
|
||||||
self.app = DupeGuruME()
|
|
||||||
return self
|
|
||||||
|
|
||||||
#---Directories
|
|
||||||
def addDirectory_(self,directory):
|
|
||||||
return self.app.add_directory(directory)
|
|
||||||
|
|
||||||
def removeDirectory_(self,index):
|
|
||||||
self.app.RemoveDirectory(index)
|
|
||||||
|
|
||||||
def setDirectory_state_(self,node_path,state):
|
|
||||||
self.app.SetDirectoryState(node_path,state)
|
|
||||||
|
|
||||||
#---Results
|
|
||||||
def clearIgnoreList(self):
|
|
||||||
self.app.scanner.ignore_list.Clear()
|
|
||||||
|
|
||||||
def doScan(self):
|
|
||||||
return self.app.start_scanning()
|
|
||||||
|
|
||||||
def exportToXHTMLwithColumns_(self, column_ids):
|
|
||||||
return self.app.export_to_xhtml(column_ids)
|
|
||||||
|
|
||||||
def loadIgnoreList(self):
|
|
||||||
self.app.load_ignore_list()
|
|
||||||
|
|
||||||
def loadResults(self):
|
|
||||||
self.app.load()
|
|
||||||
|
|
||||||
def markAll(self):
|
|
||||||
self.app.results.mark_all()
|
|
||||||
|
|
||||||
def markNone(self):
|
|
||||||
self.app.results.mark_none()
|
|
||||||
|
|
||||||
def markInvert(self):
|
|
||||||
self.app.results.mark_invert()
|
|
||||||
|
|
||||||
def purgeIgnoreList(self):
|
|
||||||
self.app.PurgeIgnoreList()
|
|
||||||
|
|
||||||
def toggleSelectedMark(self):
|
|
||||||
self.app.ToggleSelectedMarkState()
|
|
||||||
|
|
||||||
def saveIgnoreList(self):
|
|
||||||
self.app.save_ignore_list()
|
|
||||||
|
|
||||||
def saveResults(self):
|
|
||||||
self.app.save()
|
|
||||||
|
|
||||||
def refreshDetailsWithSelected(self):
|
|
||||||
self.app.RefreshDetailsWithSelected()
|
|
||||||
|
|
||||||
def selectedResultNodePaths(self):
|
|
||||||
return self.app.selected_result_node_paths()
|
|
||||||
|
|
||||||
def selectResultNodePaths_(self,node_paths):
|
|
||||||
self.app.SelectResultNodePaths(node_paths)
|
|
||||||
|
|
||||||
def selectedPowerMarkerNodePaths(self):
|
|
||||||
return self.app.selected_powermarker_node_paths()
|
|
||||||
|
|
||||||
def selectPowerMarkerNodePaths_(self,node_paths):
|
|
||||||
self.app.SelectPowerMarkerNodePaths(node_paths)
|
|
||||||
|
|
||||||
#---Actions
|
|
||||||
def addSelectedToIgnoreList(self):
|
|
||||||
self.app.AddSelectedToIgnoreList()
|
|
||||||
|
|
||||||
def applyFilter_(self, filter):
|
|
||||||
self.app.apply_filter(filter)
|
|
||||||
|
|
||||||
def deleteMarked(self):
|
|
||||||
self.app.delete_marked()
|
|
||||||
|
|
||||||
def makeSelectedReference(self):
|
|
||||||
self.app.MakeSelectedReference()
|
|
||||||
|
|
||||||
def copyOrMove_markedTo_recreatePath_(self,copy,destination,recreate_path):
|
|
||||||
self.app.copy_or_move_marked(copy, destination, recreate_path)
|
|
||||||
|
|
||||||
def openSelected(self):
|
|
||||||
self.app.OpenSelected()
|
|
||||||
|
|
||||||
def removeDeadTracks(self):
|
|
||||||
self.app.remove_dead_tracks()
|
|
||||||
|
|
||||||
def removeMarked(self):
|
|
||||||
self.app.results.perform_on_marked(lambda x:True, True)
|
|
||||||
|
|
||||||
def removeSelected(self):
|
|
||||||
self.app.RemoveSelected()
|
|
||||||
|
|
||||||
def renameSelected_(self,newname):
|
|
||||||
return self.app.RenameSelected(newname)
|
|
||||||
|
|
||||||
def revealSelected(self):
|
|
||||||
self.app.RevealSelected()
|
|
||||||
|
|
||||||
def scanDeadTracks(self):
|
|
||||||
self.app.scan_dead_tracks()
|
|
||||||
|
|
||||||
#---Misc
|
|
||||||
def sortDupesBy_ascending_(self,key,asc):
|
|
||||||
self.app.sort_dupes(key,asc)
|
|
||||||
|
|
||||||
def sortGroupsBy_ascending_(self,key,asc):
|
|
||||||
self.app.sort_groups(key,asc)
|
|
||||||
|
|
||||||
#---Information
|
|
||||||
@objc.signature('i@:')
|
|
||||||
def deadTrackCount(self):
|
|
||||||
return len(self.app.dead_tracks)
|
|
||||||
|
|
||||||
def getIgnoreListCount(self):
|
|
||||||
return len(self.app.scanner.ignore_list)
|
|
||||||
|
|
||||||
def getMarkCount(self):
|
|
||||||
return self.app.results.mark_count
|
|
||||||
|
|
||||||
def getStatLine(self):
|
|
||||||
return self.app.stat_line
|
|
||||||
|
|
||||||
def getOperationalErrorCount(self):
|
|
||||||
return self.app.last_op_error_count
|
|
||||||
|
|
||||||
#---Data
|
|
||||||
@objc.signature('i@:i')
|
|
||||||
def getOutlineViewMaxLevel_(self, tag):
|
|
||||||
return self.app.GetOutlineViewMaxLevel(tag)
|
|
||||||
|
|
||||||
@objc.signature('@@:i@')
|
|
||||||
def getOutlineView_childCountsForPath_(self, tag, node_path):
|
|
||||||
return self.app.GetOutlineViewChildCounts(tag, node_path)
|
|
||||||
|
|
||||||
def getOutlineView_valuesForIndexes_(self,tag,node_path):
|
|
||||||
return self.app.GetOutlineViewValues(tag,node_path)
|
|
||||||
|
|
||||||
def getOutlineView_markedAtIndexes_(self,tag,node_path):
|
|
||||||
return self.app.GetOutlineViewMarked(tag,node_path)
|
|
||||||
|
|
||||||
def getTableViewCount_(self,tag):
|
|
||||||
return self.app.GetTableViewCount(tag)
|
|
||||||
|
|
||||||
def getTableViewMarkedIndexes_(self,tag):
|
|
||||||
return self.app.GetTableViewMarkedIndexes(tag)
|
|
||||||
|
|
||||||
def getTableView_valuesForRow_(self,tag,row):
|
|
||||||
return self.app.GetTableViewValues(tag,row)
|
|
||||||
|
|
||||||
#---Properties
|
|
||||||
def setMinMatchPercentage_(self, percentage):
|
|
||||||
self.app.scanner.min_match_percentage = int(percentage)
|
|
||||||
|
|
||||||
def setScanType_(self, scan_type):
|
|
||||||
try:
|
|
||||||
self.app.scanner.scan_type = [
|
|
||||||
SCAN_TYPE_FILENAME,
|
|
||||||
SCAN_TYPE_FIELDS,
|
|
||||||
SCAN_TYPE_FIELDS_NO_ORDER,
|
|
||||||
SCAN_TYPE_TAG,
|
|
||||||
SCAN_TYPE_CONTENT,
|
|
||||||
SCAN_TYPE_CONTENT_AUDIO
|
|
||||||
][scan_type]
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setWordWeighting_(self, words_are_weighted):
|
|
||||||
self.app.scanner.word_weighting = words_are_weighted
|
|
||||||
|
|
||||||
def setMixFileKind_(self, mix_file_kind):
|
|
||||||
self.app.scanner.mix_file_kind = mix_file_kind
|
|
||||||
|
|
||||||
def setDisplayDeltaValues_(self, display_delta_values):
|
|
||||||
self.app.display_delta_values = display_delta_values
|
|
||||||
|
|
||||||
def setMatchSimilarWords_(self, match_similar_words):
|
|
||||||
self.app.scanner.match_similar_words = match_similar_words
|
|
||||||
|
|
||||||
def setEscapeFilterRegexp_(self, escape_filter_regexp):
|
|
||||||
self.app.options['escape_filter_regexp'] = escape_filter_regexp
|
|
||||||
|
|
||||||
def setRemoveEmptyFolders_(self, remove_empty_folders):
|
|
||||||
self.app.options['clean_empty_dirs'] = remove_empty_folders
|
|
||||||
|
|
||||||
def enable_scanForTag_(self, enable, scan_tag):
|
|
||||||
if enable:
|
|
||||||
self.app.scanner.scanned_tags.add(scan_tag)
|
|
||||||
else:
|
|
||||||
self.app.scanner.scanned_tags.discard(scan_tag)
|
|
||||||
|
|
||||||
#---Worker
|
|
||||||
def getJobProgress(self):
|
|
||||||
return self.app.progress.last_progress
|
|
||||||
|
|
||||||
def getJobDesc(self):
|
|
||||||
return self.app.progress.last_desc
|
|
||||||
|
|
||||||
def cancelJob(self):
|
|
||||||
self.app.progress.job_cancelled = True
|
|
||||||
|
|
||||||
#---Registration
|
|
||||||
def appName(self):
|
|
||||||
return "dupeGuru Music Edition"
|
|
||||||
|
|
||||||
def demoLimitDescription(self):
|
|
||||||
return self.app.DEMO_LIMIT_DESC
|
|
||||||
|
|
||||||
@objc.signature('i@:')
|
|
||||||
def isRegistered(self):
|
|
||||||
return self.app.registered
|
|
||||||
|
|
||||||
@objc.signature('i@:@@')
|
|
||||||
def isCodeValid_withEmail_(self, code, email):
|
|
||||||
return self.app.is_code_valid(code, email)
|
|
||||||
|
|
||||||
def setRegisteredCode_andEmail_(self, code, email):
|
|
||||||
self.app.set_registration(code, email)
|
|
||||||
|
|
||||||
|
# When built under virtualenv, the dependency collector misses this module, so we have to force it
|
||||||
|
# to see the module.
|
||||||
|
import distutils.sysconfig
|
||||||
@@ -1,491 +0,0 @@
|
|||||||
// !$*UTF8*$!
|
|
||||||
{
|
|
||||||
archiveVersion = 1;
|
|
||||||
classes = {
|
|
||||||
};
|
|
||||||
objectVersion = 44;
|
|
||||||
objects = {
|
|
||||||
|
|
||||||
/* Begin PBXAppleScriptBuildPhase section */
|
|
||||||
CE6B288A0AFB7FC900508D93 /* AppleScript */ = {
|
|
||||||
isa = PBXAppleScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
contextName = "";
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
isSharedContext = 0;
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXAppleScriptBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
|
||||||
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
|
|
||||||
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
|
|
||||||
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */ = {isa = PBXBuildFile; fileRef = CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */; };
|
|
||||||
CE1425890AFB718500BD5167 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; };
|
|
||||||
CE14259F0AFB719300BD5167 /* Sparkle.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE1425880AFB718500BD5167 /* Sparkle.framework */; };
|
|
||||||
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9409914ACE003581CE /* AppDelegate.m */; };
|
|
||||||
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE381C9A09914ADF003581CE /* ResultWindow.m */; };
|
|
||||||
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */ = {isa = PBXBuildFile; fileRef = CE381CF509915304003581CE /* dg_cocoa.plugin */; };
|
|
||||||
CE3FBDD31094637800B72D77 /* DetailsPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE3FBDD11094637800B72D77 /* DetailsPanel.xib */; };
|
|
||||||
CE3FBDD41094637800B72D77 /* DirectoryPanel.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE3FBDD21094637800B72D77 /* DirectoryPanel.xib */; };
|
|
||||||
CE49DEF60FDFEB810098617B /* BRSingleLineFormatter.m in Sources */ = {isa = PBXBuildFile; fileRef = CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */; };
|
|
||||||
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE10FC6C12E00EC695D /* Dialogs.m */; };
|
|
||||||
CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */; };
|
|
||||||
CE515DF50FC6C12E00EC695D /* Outline.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE50FC6C12E00EC695D /* Outline.m */; };
|
|
||||||
CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DE70FC6C12E00EC695D /* ProgressController.m */; };
|
|
||||||
CE515DF70FC6C12E00EC695D /* RecentDirectories.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */; };
|
|
||||||
CE515DF80FC6C12E00EC695D /* RegistrationInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEC0FC6C12E00EC695D /* RegistrationInterface.m */; };
|
|
||||||
CE515DF90FC6C12E00EC695D /* Table.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DEE0FC6C12E00EC695D /* Table.m */; };
|
|
||||||
CE515DFA0FC6C12E00EC695D /* Utils.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DF00FC6C12E00EC695D /* Utils.m */; };
|
|
||||||
CE515DFB0FC6C12E00EC695D /* ValueTransformers.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515DF20FC6C12E00EC695D /* ValueTransformers.m */; };
|
|
||||||
CE515E020FC6C13E00EC695D /* ErrorReportWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE515DFC0FC6C13E00EC695D /* ErrorReportWindow.xib */; };
|
|
||||||
CE515E030FC6C13E00EC695D /* progress.nib in Resources */ = {isa = PBXBuildFile; fileRef = CE515DFE0FC6C13E00EC695D /* progress.nib */; };
|
|
||||||
CE515E040FC6C13E00EC695D /* registration.nib in Resources */ = {isa = PBXBuildFile; fileRef = CE515E000FC6C13E00EC695D /* registration.nib */; };
|
|
||||||
CE515E1D0FC6C19300EC695D /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E160FC6C19300EC695D /* AppDelegate.m */; };
|
|
||||||
CE515E1E0FC6C19300EC695D /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E190FC6C19300EC695D /* DirectoryPanel.m */; };
|
|
||||||
CE515E1F0FC6C19300EC695D /* ResultWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CE515E1C0FC6C19300EC695D /* ResultWindow.m */; };
|
|
||||||
CE6032C00FE6784C007E33FF /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE6032BF0FE6784C007E33FF /* DetailsPanel.m */; };
|
|
||||||
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CE68EE6609ABC48000971085 /* DirectoryPanel.m */; };
|
|
||||||
CE6E0E9F1054EB97008D9390 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */; };
|
|
||||||
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE848A1809DD85810004CB44 /* Consts.h */; };
|
|
||||||
CE900AD2109B238600754048 /* Preferences.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD1109B238600754048 /* Preferences.xib */; };
|
|
||||||
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE900AD6109B2A9B00754048 /* MainMenu.xib */; };
|
|
||||||
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CECA899A09DB132E00A3D774 /* DetailsPanel.h */; };
|
|
||||||
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */ = {isa = PBXBuildFile; fileRef = CECA899B09DB132E00A3D774 /* DetailsPanel.m */; };
|
|
||||||
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */ = {isa = PBXBuildFile; fileRef = CEEB135109C837A2004D2330 /* dupeguru.icns */; };
|
|
||||||
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC294509C89E3D00D9F998 /* folder32.png */; };
|
|
||||||
CEFC295509C89FF200D9F998 /* details32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295309C89FF200D9F998 /* details32.png */; };
|
|
||||||
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */ = {isa = PBXBuildFile; fileRef = CEFC295409C89FF200D9F998 /* preferences32.png */; };
|
|
||||||
/* End PBXBuildFile section */
|
|
||||||
|
|
||||||
/* Begin PBXCopyFilesBuildPhase section */
|
|
||||||
CECC02B709A36E8200CC0A94 /* CopyFiles */ = {
|
|
||||||
isa = PBXCopyFilesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
dstPath = "";
|
|
||||||
dstSubfolderSpec = 10;
|
|
||||||
files = (
|
|
||||||
CE14259F0AFB719300BD5167 /* Sparkle.framework in CopyFiles */,
|
|
||||||
CECA899C09DB132E00A3D774 /* DetailsPanel.h in CopyFiles */,
|
|
||||||
CE848A1909DD85810004CB44 /* Consts.h in CopyFiles */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
|
||||||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
|
|
||||||
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
|
|
||||||
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
|
|
||||||
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
|
|
||||||
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; };
|
|
||||||
8D1107320486CEB800E47090 /* dupeGuru ME.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "dupeGuru ME.app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
|
||||||
CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dupeguru_me_help; path = ../../help_me/dupeguru_me_help; sourceTree = "<group>"; };
|
|
||||||
CE1425880AFB718500BD5167 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = /Library/Frameworks/Sparkle.framework; sourceTree = "<absolute>"; };
|
|
||||||
CE381C9409914ACE003581CE /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE381C9509914ACE003581CE /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE381C9A09914ADF003581CE /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE381C9B09914ADF003581CE /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE381CF509915304003581CE /* dg_cocoa.plugin */ = {isa = PBXFileReference; lastKnownFileType = folder; name = dg_cocoa.plugin; path = dg_cocoa.plugin; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE3FBDD11094637800B72D77 /* DetailsPanel.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = DetailsPanel.xib; path = ../../base/xib/DetailsPanel.xib; sourceTree = "<group>"; };
|
|
||||||
CE3FBDD21094637800B72D77 /* DirectoryPanel.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = DirectoryPanel.xib; path = ../../base/xib/DirectoryPanel.xib; sourceTree = "<group>"; };
|
|
||||||
CE49DEF20FDFEB810098617B /* BRSingleLineFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = BRSingleLineFormatter.h; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = BRSingleLineFormatter.m; path = ../../cocoalib/brsinglelineformatter/BRSingleLineFormatter.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE00FC6C12E00EC695D /* Dialogs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Dialogs.h; path = ../../cocoalib/Dialogs.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE10FC6C12E00EC695D /* Dialogs.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Dialogs.m; path = ../../cocoalib/Dialogs.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE20FC6C12E00EC695D /* HSErrorReportWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = HSErrorReportWindow.h; path = ../../cocoalib/HSErrorReportWindow.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = HSErrorReportWindow.m; path = ../../cocoalib/HSErrorReportWindow.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE40FC6C12E00EC695D /* Outline.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Outline.h; path = ../../cocoalib/Outline.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE50FC6C12E00EC695D /* Outline.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Outline.m; path = ../../cocoalib/Outline.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE60FC6C12E00EC695D /* ProgressController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ProgressController.h; path = ../../cocoalib/ProgressController.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE70FC6C12E00EC695D /* ProgressController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ProgressController.m; path = ../../cocoalib/ProgressController.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE80FC6C12E00EC695D /* PyApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyApp.h; path = ../../cocoalib/PyApp.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DE90FC6C12E00EC695D /* RecentDirectories.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RecentDirectories.h; path = ../../cocoalib/RecentDirectories.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RecentDirectories.m; path = ../../cocoalib/RecentDirectories.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DEB0FC6C12E00EC695D /* RegistrationInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RegistrationInterface.h; path = ../../cocoalib/RegistrationInterface.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DEC0FC6C12E00EC695D /* RegistrationInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RegistrationInterface.m; path = ../../cocoalib/RegistrationInterface.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DED0FC6C12E00EC695D /* Table.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Table.h; path = ../../cocoalib/Table.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DEE0FC6C12E00EC695D /* Table.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Table.m; path = ../../cocoalib/Table.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DEF0FC6C12E00EC695D /* Utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Utils.h; path = ../../cocoalib/Utils.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DF00FC6C12E00EC695D /* Utils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Utils.m; path = ../../cocoalib/Utils.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DF10FC6C12E00EC695D /* ValueTransformers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ValueTransformers.h; path = ../../cocoalib/ValueTransformers.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DF20FC6C12E00EC695D /* ValueTransformers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ValueTransformers.m; path = ../../cocoalib/ValueTransformers.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515DFD0FC6C13E00EC695D /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = ../../cocoalib/English.lproj/ErrorReportWindow.xib; sourceTree = "<group>"; };
|
|
||||||
CE515DFF0FC6C13E00EC695D /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = ../../cocoalib/English.lproj/progress.nib; sourceTree = "<group>"; };
|
|
||||||
CE515E010FC6C13E00EC695D /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = ../../cocoalib/English.lproj/registration.nib; sourceTree = "<group>"; };
|
|
||||||
CE515E150FC6C19300EC695D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = ../base/AppDelegate.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515E160FC6C19300EC695D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = ../base/AppDelegate.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515E170FC6C19300EC695D /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Consts.h; path = ../base/Consts.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515E180FC6C19300EC695D /* DirectoryPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DirectoryPanel.h; path = ../base/DirectoryPanel.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515E190FC6C19300EC695D /* DirectoryPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DirectoryPanel.m; path = ../base/DirectoryPanel.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PyDupeGuru.h; path = ../base/PyDupeGuru.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515E1B0FC6C19300EC695D /* ResultWindow.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ResultWindow.h; path = ../base/ResultWindow.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE515E1C0FC6C19300EC695D /* ResultWindow.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ResultWindow.m; path = ../base/ResultWindow.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE6032BE0FE6784C007E33FF /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = DetailsPanel.h; path = ../base/DetailsPanel.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE6032BF0FE6784C007E33FF /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = DetailsPanel.m; path = ../base/DetailsPanel.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE68EE6509ABC48000971085 /* DirectoryPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DirectoryPanel.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE68EE6609ABC48000971085 /* DirectoryPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DirectoryPanel.m; sourceTree = SOURCE_ROOT; };
|
|
||||||
CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = dsa_pub.pem; path = ../base/dsa_pub.pem; sourceTree = "<group>"; };
|
|
||||||
CE848A1809DD85810004CB44 /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Consts.h; sourceTree = "<group>"; };
|
|
||||||
CE900AD1109B238600754048 /* Preferences.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = Preferences.xib; sourceTree = "<group>"; };
|
|
||||||
CE900AD6109B2A9B00754048 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = MainMenu.xib; path = ../../base/xib/MainMenu.xib; sourceTree = "<group>"; };
|
|
||||||
CECA899A09DB132E00A3D774 /* DetailsPanel.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = DetailsPanel.h; sourceTree = "<group>"; };
|
|
||||||
CECA899B09DB132E00A3D774 /* DetailsPanel.m */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.objc; path = DetailsPanel.m; sourceTree = "<group>"; };
|
|
||||||
CEEB135109C837A2004D2330 /* dupeguru.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = dupeguru.icns; sourceTree = "<group>"; };
|
|
||||||
CEFC294509C89E3D00D9F998 /* folder32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = folder32.png; path = ../../images/folder32.png; sourceTree = SOURCE_ROOT; };
|
|
||||||
CEFC295309C89FF200D9F998 /* details32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = details32.png; path = ../../images/details32.png; sourceTree = SOURCE_ROOT; };
|
|
||||||
CEFC295409C89FF200D9F998 /* preferences32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = preferences32.png; path = ../../images/preferences32.png; sourceTree = SOURCE_ROOT; };
|
|
||||||
CEFF18A009A4D387005E6321 /* PyDupeGuru.h */ = {isa = PBXFileReference; fileEncoding = 5; lastKnownFileType = sourcecode.c.h; path = PyDupeGuru.h; sourceTree = SOURCE_ROOT; };
|
|
||||||
/* End PBXFileReference section */
|
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
|
||||||
8D11072E0486CEB800E47090 /* Frameworks */ = {
|
|
||||||
isa = PBXFrameworksBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */,
|
|
||||||
CE1425890AFB718500BD5167 /* Sparkle.framework in Frameworks */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXFrameworksBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
|
||||||
080E96DDFE201D6D7F000001 /* Classes */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
CE381C9509914ACE003581CE /* AppDelegate.h */,
|
|
||||||
CE381C9409914ACE003581CE /* AppDelegate.m */,
|
|
||||||
CE848A1809DD85810004CB44 /* Consts.h */,
|
|
||||||
CECA899A09DB132E00A3D774 /* DetailsPanel.h */,
|
|
||||||
CECA899B09DB132E00A3D774 /* DetailsPanel.m */,
|
|
||||||
CE68EE6509ABC48000971085 /* DirectoryPanel.h */,
|
|
||||||
CE68EE6609ABC48000971085 /* DirectoryPanel.m */,
|
|
||||||
CEFF18A009A4D387005E6321 /* PyDupeGuru.h */,
|
|
||||||
CE381C9B09914ADF003581CE /* ResultWindow.h */,
|
|
||||||
CE381C9A09914ADF003581CE /* ResultWindow.m */,
|
|
||||||
);
|
|
||||||
name = Classes;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
CE1425880AFB718500BD5167 /* Sparkle.framework */,
|
|
||||||
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
|
|
||||||
);
|
|
||||||
name = "Linked Frameworks";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
29B97324FDCFA39411CA2CEA /* AppKit.framework */,
|
|
||||||
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */,
|
|
||||||
29B97325FDCFA39411CA2CEA /* Foundation.framework */,
|
|
||||||
);
|
|
||||||
name = "Other Frameworks";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
19C28FACFE9D520D11CA2CBB /* Products */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
8D1107320486CEB800E47090 /* dupeGuru ME.app */,
|
|
||||||
);
|
|
||||||
name = Products;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
29B97314FDCFA39411CA2CEA /* dupeguru */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
080E96DDFE201D6D7F000001 /* Classes */,
|
|
||||||
CE515E140FC6C17900EC695D /* dgbase */,
|
|
||||||
CE515DDD0FC6C09400EC695D /* cocoalib */,
|
|
||||||
29B97315FDCFA39411CA2CEA /* Other Sources */,
|
|
||||||
29B97317FDCFA39411CA2CEA /* Resources */,
|
|
||||||
29B97323FDCFA39411CA2CEA /* Frameworks */,
|
|
||||||
19C28FACFE9D520D11CA2CBB /* Products */,
|
|
||||||
);
|
|
||||||
name = dupeguru;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
29B97316FDCFA39411CA2CEA /* main.m */,
|
|
||||||
);
|
|
||||||
name = "Other Sources";
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
29B97317FDCFA39411CA2CEA /* Resources */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
CE3FBDD01094637800B72D77 /* xib */,
|
|
||||||
CE073F5409CAE1A3005C1D2F /* dupeguru_me_help */,
|
|
||||||
CE381CF509915304003581CE /* dg_cocoa.plugin */,
|
|
||||||
CEFC294309C89E0000D9F998 /* images */,
|
|
||||||
CEEB135109C837A2004D2330 /* dupeguru.icns */,
|
|
||||||
8D1107310486CEB800E47090 /* Info.plist */,
|
|
||||||
CE6E0E9E1054EB97008D9390 /* dsa_pub.pem */,
|
|
||||||
);
|
|
||||||
name = Resources;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
|
|
||||||
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
|
|
||||||
);
|
|
||||||
name = Frameworks;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
CE3FBDD01094637800B72D77 /* xib */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
CE900AD6109B2A9B00754048 /* MainMenu.xib */,
|
|
||||||
CE3FBDD11094637800B72D77 /* DetailsPanel.xib */,
|
|
||||||
CE3FBDD21094637800B72D77 /* DirectoryPanel.xib */,
|
|
||||||
CE900AD1109B238600754048 /* Preferences.xib */,
|
|
||||||
);
|
|
||||||
path = xib;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
CE49DEF10FDFEB810098617B /* brsinglelineformatter */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
CE49DEF20FDFEB810098617B /* BRSingleLineFormatter.h */,
|
|
||||||
CE49DEF30FDFEB810098617B /* BRSingleLineFormatter.m */,
|
|
||||||
);
|
|
||||||
name = brsinglelineformatter;
|
|
||||||
path = ../../cocoalib/brsinglelineformatter;
|
|
||||||
sourceTree = SOURCE_ROOT;
|
|
||||||
};
|
|
||||||
CE515DDD0FC6C09400EC695D /* cocoalib */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
CE49DEF10FDFEB810098617B /* brsinglelineformatter */,
|
|
||||||
CE515DFC0FC6C13E00EC695D /* ErrorReportWindow.xib */,
|
|
||||||
CE515DFE0FC6C13E00EC695D /* progress.nib */,
|
|
||||||
CE515E000FC6C13E00EC695D /* registration.nib */,
|
|
||||||
CE515DE00FC6C12E00EC695D /* Dialogs.h */,
|
|
||||||
CE515DE10FC6C12E00EC695D /* Dialogs.m */,
|
|
||||||
CE515DE20FC6C12E00EC695D /* HSErrorReportWindow.h */,
|
|
||||||
CE515DE30FC6C12E00EC695D /* HSErrorReportWindow.m */,
|
|
||||||
CE515DE40FC6C12E00EC695D /* Outline.h */,
|
|
||||||
CE515DE50FC6C12E00EC695D /* Outline.m */,
|
|
||||||
CE515DE60FC6C12E00EC695D /* ProgressController.h */,
|
|
||||||
CE515DE70FC6C12E00EC695D /* ProgressController.m */,
|
|
||||||
CE515DE80FC6C12E00EC695D /* PyApp.h */,
|
|
||||||
CE515DE90FC6C12E00EC695D /* RecentDirectories.h */,
|
|
||||||
CE515DEA0FC6C12E00EC695D /* RecentDirectories.m */,
|
|
||||||
CE515DEB0FC6C12E00EC695D /* RegistrationInterface.h */,
|
|
||||||
CE515DEC0FC6C12E00EC695D /* RegistrationInterface.m */,
|
|
||||||
CE515DED0FC6C12E00EC695D /* Table.h */,
|
|
||||||
CE515DEE0FC6C12E00EC695D /* Table.m */,
|
|
||||||
CE515DEF0FC6C12E00EC695D /* Utils.h */,
|
|
||||||
CE515DF00FC6C12E00EC695D /* Utils.m */,
|
|
||||||
CE515DF10FC6C12E00EC695D /* ValueTransformers.h */,
|
|
||||||
CE515DF20FC6C12E00EC695D /* ValueTransformers.m */,
|
|
||||||
);
|
|
||||||
name = cocoalib;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
CE515E140FC6C17900EC695D /* dgbase */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
CE515E150FC6C19300EC695D /* AppDelegate.h */,
|
|
||||||
CE515E160FC6C19300EC695D /* AppDelegate.m */,
|
|
||||||
CE515E170FC6C19300EC695D /* Consts.h */,
|
|
||||||
CE6032BE0FE6784C007E33FF /* DetailsPanel.h */,
|
|
||||||
CE6032BF0FE6784C007E33FF /* DetailsPanel.m */,
|
|
||||||
CE515E180FC6C19300EC695D /* DirectoryPanel.h */,
|
|
||||||
CE515E190FC6C19300EC695D /* DirectoryPanel.m */,
|
|
||||||
CE515E1A0FC6C19300EC695D /* PyDupeGuru.h */,
|
|
||||||
CE515E1B0FC6C19300EC695D /* ResultWindow.h */,
|
|
||||||
CE515E1C0FC6C19300EC695D /* ResultWindow.m */,
|
|
||||||
);
|
|
||||||
name = dgbase;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
CEFC294309C89E0000D9F998 /* images */ = {
|
|
||||||
isa = PBXGroup;
|
|
||||||
children = (
|
|
||||||
CEFC295309C89FF200D9F998 /* details32.png */,
|
|
||||||
CEFC295409C89FF200D9F998 /* preferences32.png */,
|
|
||||||
CEFC294509C89E3D00D9F998 /* folder32.png */,
|
|
||||||
);
|
|
||||||
name = images;
|
|
||||||
sourceTree = "<group>";
|
|
||||||
};
|
|
||||||
/* End PBXGroup section */
|
|
||||||
|
|
||||||
/* Begin PBXNativeTarget section */
|
|
||||||
8D1107260486CEB800E47090 /* dupeguru */ = {
|
|
||||||
isa = PBXNativeTarget;
|
|
||||||
buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "dupeguru" */;
|
|
||||||
buildPhases = (
|
|
||||||
8D1107290486CEB800E47090 /* Resources */,
|
|
||||||
8D11072C0486CEB800E47090 /* Sources */,
|
|
||||||
8D11072E0486CEB800E47090 /* Frameworks */,
|
|
||||||
CECC02B709A36E8200CC0A94 /* CopyFiles */,
|
|
||||||
CE6B288A0AFB7FC900508D93 /* AppleScript */,
|
|
||||||
);
|
|
||||||
buildRules = (
|
|
||||||
);
|
|
||||||
dependencies = (
|
|
||||||
);
|
|
||||||
name = dupeguru;
|
|
||||||
productInstallPath = "$(HOME)/Applications";
|
|
||||||
productName = dupeguru;
|
|
||||||
productReference = 8D1107320486CEB800E47090 /* dupeGuru ME.app */;
|
|
||||||
productType = "com.apple.product-type.application";
|
|
||||||
};
|
|
||||||
/* End PBXNativeTarget section */
|
|
||||||
|
|
||||||
/* Begin PBXProject section */
|
|
||||||
29B97313FDCFA39411CA2CEA /* Project object */ = {
|
|
||||||
isa = PBXProject;
|
|
||||||
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */;
|
|
||||||
compatibilityVersion = "Xcode 3.0";
|
|
||||||
hasScannedForEncodings = 1;
|
|
||||||
mainGroup = 29B97314FDCFA39411CA2CEA /* dupeguru */;
|
|
||||||
projectDirPath = "";
|
|
||||||
projectRoot = "";
|
|
||||||
targets = (
|
|
||||||
8D1107260486CEB800E47090 /* dupeguru */,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
/* End PBXProject section */
|
|
||||||
|
|
||||||
/* Begin PBXResourcesBuildPhase section */
|
|
||||||
8D1107290486CEB800E47090 /* Resources */ = {
|
|
||||||
isa = PBXResourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
CE381D0509915304003581CE /* dg_cocoa.plugin in Resources */,
|
|
||||||
CE073F6309CAE1A3005C1D2F /* dupeguru_me_help in Resources */,
|
|
||||||
CEEB135209C837A2004D2330 /* dupeguru.icns in Resources */,
|
|
||||||
CEFC294609C89E3D00D9F998 /* folder32.png in Resources */,
|
|
||||||
CEFC295509C89FF200D9F998 /* details32.png in Resources */,
|
|
||||||
CEFC295609C89FF200D9F998 /* preferences32.png in Resources */,
|
|
||||||
CE515E020FC6C13E00EC695D /* ErrorReportWindow.xib in Resources */,
|
|
||||||
CE515E030FC6C13E00EC695D /* progress.nib in Resources */,
|
|
||||||
CE515E040FC6C13E00EC695D /* registration.nib in Resources */,
|
|
||||||
CE6E0E9F1054EB97008D9390 /* dsa_pub.pem in Resources */,
|
|
||||||
CE3FBDD31094637800B72D77 /* DetailsPanel.xib in Resources */,
|
|
||||||
CE3FBDD41094637800B72D77 /* DirectoryPanel.xib in Resources */,
|
|
||||||
CE900AD2109B238600754048 /* Preferences.xib in Resources */,
|
|
||||||
CE900AD7109B2A9B00754048 /* MainMenu.xib in Resources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXResourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXSourcesBuildPhase section */
|
|
||||||
8D11072C0486CEB800E47090 /* Sources */ = {
|
|
||||||
isa = PBXSourcesBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
8D11072D0486CEB800E47090 /* main.m in Sources */,
|
|
||||||
CE381C9609914ACE003581CE /* AppDelegate.m in Sources */,
|
|
||||||
CE381C9C09914ADF003581CE /* ResultWindow.m in Sources */,
|
|
||||||
CE68EE6809ABC48000971085 /* DirectoryPanel.m in Sources */,
|
|
||||||
CECA899D09DB132E00A3D774 /* DetailsPanel.m in Sources */,
|
|
||||||
CE515DF30FC6C12E00EC695D /* Dialogs.m in Sources */,
|
|
||||||
CE515DF40FC6C12E00EC695D /* HSErrorReportWindow.m in Sources */,
|
|
||||||
CE515DF50FC6C12E00EC695D /* Outline.m in Sources */,
|
|
||||||
CE515DF60FC6C12E00EC695D /* ProgressController.m in Sources */,
|
|
||||||
CE515DF70FC6C12E00EC695D /* RecentDirectories.m in Sources */,
|
|
||||||
CE515DF80FC6C12E00EC695D /* RegistrationInterface.m in Sources */,
|
|
||||||
CE515DF90FC6C12E00EC695D /* Table.m in Sources */,
|
|
||||||
CE515DFA0FC6C12E00EC695D /* Utils.m in Sources */,
|
|
||||||
CE515DFB0FC6C12E00EC695D /* ValueTransformers.m in Sources */,
|
|
||||||
CE515E1D0FC6C19300EC695D /* AppDelegate.m in Sources */,
|
|
||||||
CE515E1E0FC6C19300EC695D /* DirectoryPanel.m in Sources */,
|
|
||||||
CE515E1F0FC6C19300EC695D /* ResultWindow.m in Sources */,
|
|
||||||
CE49DEF60FDFEB810098617B /* BRSingleLineFormatter.m in Sources */,
|
|
||||||
CE6032C00FE6784C007E33FF /* DetailsPanel.m in Sources */,
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
};
|
|
||||||
/* End PBXSourcesBuildPhase section */
|
|
||||||
|
|
||||||
/* Begin PBXVariantGroup section */
|
|
||||||
CE515DFC0FC6C13E00EC695D /* ErrorReportWindow.xib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
CE515DFD0FC6C13E00EC695D /* English */,
|
|
||||||
);
|
|
||||||
name = ErrorReportWindow.xib;
|
|
||||||
sourceTree = SOURCE_ROOT;
|
|
||||||
};
|
|
||||||
CE515DFE0FC6C13E00EC695D /* progress.nib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
CE515DFF0FC6C13E00EC695D /* English */,
|
|
||||||
);
|
|
||||||
name = progress.nib;
|
|
||||||
sourceTree = SOURCE_ROOT;
|
|
||||||
};
|
|
||||||
CE515E000FC6C13E00EC695D /* registration.nib */ = {
|
|
||||||
isa = PBXVariantGroup;
|
|
||||||
children = (
|
|
||||||
CE515E010FC6C13E00EC695D /* English */,
|
|
||||||
);
|
|
||||||
name = registration.nib;
|
|
||||||
sourceTree = SOURCE_ROOT;
|
|
||||||
};
|
|
||||||
/* End PBXVariantGroup section */
|
|
||||||
|
|
||||||
/* Begin XCBuildConfiguration section */
|
|
||||||
C01FCF4C08A954540054247B /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
GCC_GENERATE_DEBUGGING_SYMBOLS = NO;
|
|
||||||
GCC_MODEL_TUNING = G5;
|
|
||||||
INFOPLIST_FILE = Info.plist;
|
|
||||||
INSTALL_PATH = "$(HOME)/Applications";
|
|
||||||
PRODUCT_NAME = "dupeGuru ME";
|
|
||||||
WRAPPER_EXTENSION = app;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
C01FCF5008A954540054247B /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ARCHS = "$(ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1)";
|
|
||||||
ARCHS_STANDARD_32_BIT_PRE_XCODE_3_1 = "ppc i386";
|
|
||||||
GCC_C_LANGUAGE_STANDARD = c99;
|
|
||||||
GCC_WARN_ABOUT_RETURN_TYPE = YES;
|
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.5;
|
|
||||||
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk";
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
|
||||||
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "dupeguru" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
C01FCF4C08A954540054247B /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "dupeguru" */ = {
|
|
||||||
isa = XCConfigurationList;
|
|
||||||
buildConfigurations = (
|
|
||||||
C01FCF5008A954540054247B /* Release */,
|
|
||||||
);
|
|
||||||
defaultConfigurationIsVisible = 0;
|
|
||||||
defaultConfigurationName = Release;
|
|
||||||
};
|
|
||||||
/* End XCConfigurationList section */
|
|
||||||
};
|
|
||||||
rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
|
||||||
{
|
|
||||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
||||||
NSString *pluginPath = [[NSBundle mainBundle]
|
|
||||||
pathForResource:@"dg_cocoa"
|
|
||||||
ofType:@"plugin"];
|
|
||||||
NSBundle *pluginBundle = [NSBundle bundleWithPath:pluginPath];
|
|
||||||
[pluginBundle load];
|
|
||||||
[pool release];
|
|
||||||
return NSApplicationMain(argc, (const char **) argv);
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "../base/AppDelegate.h"
|
#import "AppDelegateBase.h"
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase
|
@interface AppDelegate : AppDelegateBase {}
|
||||||
{
|
- (void)clearPictureCache;
|
||||||
DirectoryPanel *_directoryPanel;
|
|
||||||
}
|
|
||||||
- (IBAction)openWebsite:(id)sender;
|
|
||||||
- (IBAction)toggleDirectories:(id)sender;
|
|
||||||
|
|
||||||
- (DirectoryPanel *)directoryPanel;
|
|
||||||
- (PyDupeGuru *)py;
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,110 +1,61 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
#import "AppDelegate.h"
|
||||||
#import "ProgressController.h"
|
#import "ProgressController.h"
|
||||||
#import "RegistrationInterface.h"
|
|
||||||
#import "Utils.h"
|
#import "Utils.h"
|
||||||
#import "ValueTransformers.h"
|
#import "ValueTransformers.h"
|
||||||
#import "Consts.h"
|
#import "Consts.h"
|
||||||
#import "DetailsPanel.h"
|
#import "DetailsPanel.h"
|
||||||
|
#import "DirectoryPanel.h"
|
||||||
|
#import "ResultWindow.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(0) forKey:@"scanType"];
|
||||||
[d setObject:[NSNumber numberWithInt:95] forKey:@"minMatchPercentage"];
|
[d setObject:i2n(95) forKey:@"minMatchPercentage"];
|
||||||
[d setObject:[NSNumber numberWithInt:1] forKey:@"recreatePathType"];
|
[d setObject:b2n(NO) forKey:@"matchScaled"];
|
||||||
[d setObject:[NSNumber numberWithBool:NO] forKey:@"matchScaled"];
|
return d;
|
||||||
[d setObject:[NSNumber numberWithBool:YES] forKey:@"mixFileKind"];
|
|
||||||
[d setObject:[NSNumber numberWithBool:NO] forKey:@"useRegexpFilter"];
|
|
||||||
[d setObject:[NSNumber numberWithBool:NO] forKey:@"removeEmptyFolders"];
|
|
||||||
[d setObject:[NSNumber numberWithBool:NO] forKey:@"debug"];
|
|
||||||
[d setObject:[NSArray array] forKey:@"recentDirectories"];
|
|
||||||
[d setObject:[NSArray array] forKey:@"columnsOrder"];
|
|
||||||
[d setObject:[NSDictionary dictionary] forKey:@"columnsWidth"];
|
|
||||||
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:d];
|
|
||||||
[ud registerDefaults:d];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (id)init
|
- (id)init
|
||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
_directoryPanel = nil;
|
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:0];
|
||||||
|
VTIsIntIn *vtScanTypeIsFuzzy = [[[VTIsIntIn alloc] initWithValues:i reverse:NO] autorelease];
|
||||||
|
[NSValueTransformer setValueTransformer:vtScanTypeIsFuzzy forName:@"vtScanTypeIsFuzzy"];
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (DetailsPanelBase *)detailsPanel
|
- (NSString *)homepageURL
|
||||||
{
|
{
|
||||||
if (!_detailsPanel)
|
return @"http://www.hardcoded.net/dupeguru_pe/";
|
||||||
_detailsPanel = [[DetailsPanel alloc] initWithPy:py];
|
|
||||||
return _detailsPanel;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)openWebsite:(id)sender
|
- (ResultWindowBase *)createResultWindow
|
||||||
{
|
{
|
||||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru_pe"]];
|
return [[ResultWindow alloc] initWithParentApp:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)toggleDirectories:(id)sender
|
- (DirectoryPanel *)createDirectoryPanel
|
||||||
{
|
{
|
||||||
[[self directoryPanel] toggleVisible:sender];
|
return [[DirectoryPanelPE alloc] initWithParentApp:self];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (DirectoryPanel *)directoryPanel
|
- (DetailsPanel *)createDetailsPanel
|
||||||
{
|
{
|
||||||
if (!_directoryPanel)
|
return [[DetailsPanel alloc] initWithApp:model];
|
||||||
_directoryPanel = [[DirectoryPanel alloc] initWithParentApp:self];
|
|
||||||
return _directoryPanel;
|
|
||||||
}
|
|
||||||
- (PyDupeGuru *)py { return (PyDupeGuru *)py; }
|
|
||||||
|
|
||||||
//Delegate
|
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSMenu *actionsMenu = [[[NSApp mainMenu] itemWithTitle:@"Actions"] submenu];
|
|
||||||
// index 2 is just after "Clear Ingore List"
|
|
||||||
NSMenuItem *mi = [actionsMenu insertItemWithTitle:@"Clear Picture Cache" action:@selector(clearPictureCache:) keyEquivalent:@"P" atIndex:2];
|
|
||||||
[mi setTarget:result];
|
|
||||||
[mi setKeyEquivalentModifierMask:NSCommandKeyMask|NSShiftKeyMask];
|
|
||||||
[super applicationDidFinishLaunching:aNotification];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)applicationWillBecomeActive:(NSNotification *)aNotification
|
- (void)clearPictureCache
|
||||||
{
|
{
|
||||||
if (![[result window] isVisible])
|
[(ResultWindow *)[self resultWindow] clearPictureCache];
|
||||||
[result showWindow:NSApp];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)applicationWillTerminate:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[ud setObject: [result getColumnsOrder] forKey:@"columnsOrder"];
|
|
||||||
[ud setObject: [result getColumnsWidth] forKey:@"columnsWidth"];
|
|
||||||
[py saveIgnoreList];
|
|
||||||
[py saveResults];
|
|
||||||
int sc = [ud integerForKey:@"sessionCountSinceLastIgnorePurge"];
|
|
||||||
if (sc >= 10)
|
|
||||||
{
|
|
||||||
sc = -1;
|
|
||||||
[py purgeIgnoreList];
|
|
||||||
}
|
|
||||||
sc++;
|
|
||||||
[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 RecentDirectories so it saves the user defaults
|
|
||||||
[recentDirectories release];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)recentDirecoryClicked:(NSString *)directory
|
|
||||||
{
|
|
||||||
[[self directoryPanel] addDirectory:directory];
|
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "../base/Consts.h"
|
#import "../base/Consts.h"
|
||||||
|
|||||||
@@ -1,24 +1,32 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "../base/DetailsPanel.h"
|
#import "DetailsPanelBase.h"
|
||||||
|
#import "PyDupeGuru.h"
|
||||||
|
|
||||||
@interface DetailsPanel : DetailsPanelBase
|
@interface DetailsPanel : DetailsPanelBase
|
||||||
{
|
{
|
||||||
IBOutlet NSImageView *dupeImage;
|
NSImageView *dupeImage;
|
||||||
IBOutlet NSProgressIndicator *dupeProgressIndicator;
|
NSProgressIndicator *dupeProgressIndicator;
|
||||||
IBOutlet NSImageView *refImage;
|
NSImageView *refImage;
|
||||||
IBOutlet NSProgressIndicator *refProgressIndicator;
|
NSProgressIndicator *refProgressIndicator;
|
||||||
|
|
||||||
PyApp *py;
|
PyDupeGuru *pyApp;
|
||||||
BOOL _needsRefresh;
|
BOOL _needsRefresh;
|
||||||
NSString *_dupePath;
|
NSString *_dupePath;
|
||||||
NSString *_refPath;
|
NSString *_refPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@property (readwrite, retain) NSImageView *dupeImage;
|
||||||
|
@property (readwrite, retain) NSProgressIndicator *dupeProgressIndicator;
|
||||||
|
@property (readwrite, retain) NSImageView *refImage;
|
||||||
|
@property (readwrite, retain) NSProgressIndicator *refProgressIndicator;
|
||||||
|
|
||||||
|
- (id)initWithApp:(PyDupeGuru *)aApp;
|
||||||
@end
|
@end
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "Utils.h"
|
#import "Utils.h"
|
||||||
@@ -12,17 +12,29 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
#import "PyDupeGuru.h"
|
#import "PyDupeGuru.h"
|
||||||
#import "DetailsPanel.h"
|
#import "DetailsPanel.h"
|
||||||
#import "Consts.h"
|
#import "Consts.h"
|
||||||
|
#import "DetailsPanel_UI.h"
|
||||||
|
|
||||||
@implementation DetailsPanel
|
@implementation DetailsPanel
|
||||||
- (id)initWithPy:(PyApp *)aPy
|
|
||||||
|
@synthesize dupeImage;
|
||||||
|
@synthesize dupeProgressIndicator;
|
||||||
|
@synthesize refImage;
|
||||||
|
@synthesize refProgressIndicator;
|
||||||
|
|
||||||
|
- (id)initWithApp:(PyDupeGuru *)aApp
|
||||||
{
|
{
|
||||||
self = [super initWithPy:aPy];
|
self = [super initWithPyRef:[aApp detailsPanel]];
|
||||||
py = aPy;
|
pyApp = aApp;
|
||||||
_needsRefresh = YES;
|
_needsRefresh = YES;
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageLoaded:) name:ImageLoadedNotification object:self];
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageLoaded:) name:ImageLoadedNotification object:self];
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (NSWindow *)createWindow
|
||||||
|
{
|
||||||
|
return createDetailsPanel_UI(self);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)loadImageAsync:(NSString *)imagePath
|
- (void)loadImageAsync:(NSString *)imagePath
|
||||||
{
|
{
|
||||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||||
@@ -36,18 +48,18 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[pool release];
|
[pool release];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)refresh
|
- (void)refreshDetails
|
||||||
{
|
{
|
||||||
if (!_needsRefresh)
|
if (!_needsRefresh)
|
||||||
return;
|
return;
|
||||||
[detailsTable reloadData];
|
[detailsTable reloadData];
|
||||||
|
|
||||||
NSString *refPath = [(PyDupeGuru *)py getSelectedDupeRefPath];
|
NSString *refPath = [pyApp getSelectedDupeRefPath];
|
||||||
if (_refPath != nil)
|
if (_refPath != nil)
|
||||||
[_refPath autorelease];
|
[_refPath autorelease];
|
||||||
_refPath = [refPath retain];
|
_refPath = [refPath retain];
|
||||||
[NSThread detachNewThreadSelector:@selector(loadImageAsync:) toTarget:self withObject:refPath];
|
[NSThread detachNewThreadSelector:@selector(loadImageAsync:) toTarget:self withObject:refPath];
|
||||||
NSString *dupePath = [(PyDupeGuru *)py getSelectedDupePath];
|
NSString *dupePath = [pyApp getSelectedDupePath];
|
||||||
if (_dupePath != nil)
|
if (_dupePath != nil)
|
||||||
[_dupePath autorelease];
|
[_dupePath autorelease];
|
||||||
_dupePath = [dupePath retain];
|
_dupePath = [dupePath retain];
|
||||||
@@ -59,12 +71,6 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Notifications */
|
/* Notifications */
|
||||||
- (void)duplicateSelectionChanged:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
_needsRefresh = YES;
|
|
||||||
[super duplicateSelectionChanged:aNotification];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)imageLoaded:(NSNotification *)aNotification
|
- (void)imageLoaded:(NSNotification *)aNotification
|
||||||
{
|
{
|
||||||
NSString *imagePath = [[aNotification userInfo] valueForKey:@"imagePath"];
|
NSString *imagePath = [[aNotification userInfo] valueForKey:@"imagePath"];
|
||||||
@@ -80,4 +86,11 @@ http://www.hardcoded.net/licenses/hs_license
|
|||||||
[dupeProgressIndicator stopAnimation:nil];
|
[dupeProgressIndicator stopAnimation:nil];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Python --> Cocoa */
|
||||||
|
- (void)refresh
|
||||||
|
{
|
||||||
|
_needsRefresh = YES;
|
||||||
|
[super refresh];
|
||||||
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "../base/DirectoryPanel.h"
|
#import "../base/DirectoryPanel.h"
|
||||||
|
|
||||||
@interface DirectoryPanel : DirectoryPanelBase
|
@interface DirectoryPanelPE : DirectoryPanel
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
- (IBAction)addiPhoto:(id)sender;
|
- (IBAction)addiPhoto:(id)sender;
|
||||||
|
- (IBAction)addAperture:(id)sender;
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,52 +1,40 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "DirectoryPanel.h"
|
#import "DirectoryPanel.h"
|
||||||
#import "ProgressController.h"
|
|
||||||
|
|
||||||
static NSString* jobAddIPhoto = @"jobAddIPhoto";
|
@implementation DirectoryPanelPE
|
||||||
|
|
||||||
@implementation DirectoryPanel
|
|
||||||
- (id)initWithParentApp:(id)aParentApp
|
- (id)initWithParentApp:(id)aParentApp
|
||||||
{
|
{
|
||||||
self = [super initWithParentApp:aParentApp];
|
self = [super initWithParentApp:aParentApp];
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobCompleted:) name:JobCompletedNotification object:nil];
|
_alwaysShowPopUp = YES;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)fillPopUpMenu
|
||||||
|
{
|
||||||
|
[super fillPopUpMenu];
|
||||||
|
NSMenu *m = [addButtonPopUp menu];
|
||||||
|
NSMenuItem *mi = [m insertItemWithTitle:NSLocalizedString(@"Add iPhoto Library", @"") action:@selector(addiPhoto:)
|
||||||
|
keyEquivalent:@"" atIndex:1];
|
||||||
|
[mi setTarget:self];
|
||||||
|
mi = [m insertItemWithTitle:NSLocalizedString(@"Add Aperture Library", @"") action:@selector(addAperture:)
|
||||||
|
keyEquivalent:@"" atIndex:2];
|
||||||
|
[mi setTarget:self];
|
||||||
|
}
|
||||||
|
|
||||||
- (IBAction)addiPhoto:(id)sender
|
- (IBAction)addiPhoto:(id)sender
|
||||||
{
|
{
|
||||||
[[ProgressController mainProgressController] setJobDesc:@"Adding iPhoto Library..."];
|
|
||||||
[[ProgressController mainProgressController] setJobId:jobAddIPhoto];
|
|
||||||
[[ProgressController mainProgressController] showSheetForParent:[self window]];
|
|
||||||
[self addDirectory:@"iPhoto Library"];
|
[self addDirectory:@"iPhoto Library"];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)popupAddDirectoryMenu:(id)sender
|
- (IBAction)addAperture:(id)sender
|
||||||
{
|
{
|
||||||
NSMenu *m = [addButtonPopUp menu];
|
[self addDirectory:@"Aperture Library"];
|
||||||
while ([m numberOfItems] > 0)
|
|
||||||
[m removeItemAtIndex:0];
|
|
||||||
NSMenuItem *mi = [m addItemWithTitle:@"Add New Directory..." action:@selector(askForDirectory:) keyEquivalent:@""];
|
|
||||||
[mi setTarget:self];
|
|
||||||
mi = [m addItemWithTitle:@"Add iPhoto Directory" action:@selector(addiPhoto:) keyEquivalent:@""];
|
|
||||||
[mi setTarget:self];
|
|
||||||
[m addItem:[NSMenuItem separatorItem]];
|
|
||||||
[_recentDirectories fillMenu:m];
|
|
||||||
[addButtonPopUp selectItem:nil];
|
|
||||||
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)jobCompleted:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
if ([[ProgressController mainProgressController] jobId] == jobAddIPhoto)
|
|
||||||
{
|
|
||||||
[directories reloadData];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@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_pe_help</string>
|
<string>dupeguru_pe_help</string>
|
||||||
<key>CFBundleHelpBookName</key>
|
<key>CFBundleHelpBookName</key>
|
||||||
@@ -13,23 +13,25 @@
|
|||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>dupeguru</string>
|
<string>dupeguru</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>com.hardcoded_software.dupeguru_pe</string>
|
<string>com.hardcoded-software.dupeguru-pe</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 PE</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>1.8.0</string>
|
<string>{version}</string>
|
||||||
<key>NSMainNibFile</key>
|
<key>NSMainNibFile</key>
|
||||||
<string>MainMenu</string>
|
<string>MainMenu</string>
|
||||||
<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_pe.appcast</string>
|
<string>http://www.hardcoded.net/updates/dupeguru_pe.appcast</string>
|
||||||
<key>SUPublicDSAKeyFile</key>
|
<key>SUPublicDSAKeyFile</key>
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
|
|
||||||
|
|
||||||
@interface PictureBlocks : NSObject {
|
|
||||||
}
|
|
||||||
+ (NSString *)getBlocksFromImagePath:(NSString *)imagePath blockCount:(NSNumber *)blockCount;
|
|
||||||
+ (NSSize)getImageSize:(NSString *)imagePath;
|
|
||||||
@end
|
|
||||||
|
|
||||||
|
|
||||||
NSString* GetBlocks(NSString *filePath, int blockCount);
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "PictureBlocks.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
|
|
||||||
@implementation PictureBlocks
|
|
||||||
+ (NSString *)getBlocksFromImagePath:(NSString *)imagePath blockCount:(NSNumber *)blockCount
|
|
||||||
{
|
|
||||||
return GetBlocks(imagePath, n2i(blockCount));
|
|
||||||
}
|
|
||||||
|
|
||||||
+ (NSSize)getImageSize:(NSString *)imagePath
|
|
||||||
{
|
|
||||||
CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)imagePath, kCFURLPOSIXPathStyle, FALSE);
|
|
||||||
CGImageSourceRef source = CGImageSourceCreateWithURL(fileURL, NULL);
|
|
||||||
if (source == NULL)
|
|
||||||
return NSMakeSize(0, 0);
|
|
||||||
CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, NULL);
|
|
||||||
if (image == NULL)
|
|
||||||
return NSMakeSize(0, 0);
|
|
||||||
size_t width = CGImageGetWidth(image);
|
|
||||||
size_t height = CGImageGetHeight(image);
|
|
||||||
CGImageRelease(image);
|
|
||||||
CFRelease(source);
|
|
||||||
CFRelease(fileURL);
|
|
||||||
return NSMakeSize(width, height);
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
|
|
||||||
CGContextRef MyCreateBitmapContext (int width, int height)
|
|
||||||
{
|
|
||||||
CGContextRef context = NULL;
|
|
||||||
CGColorSpaceRef colorSpace;
|
|
||||||
void * bitmapData;
|
|
||||||
int bitmapByteCount;
|
|
||||||
int bitmapBytesPerRow;
|
|
||||||
|
|
||||||
bitmapBytesPerRow = (width * 4);
|
|
||||||
bitmapByteCount = (bitmapBytesPerRow * height);
|
|
||||||
|
|
||||||
colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB);
|
|
||||||
|
|
||||||
// calloc() must be used to allocate bitmapData here because the buffer has to be zeroed.
|
|
||||||
// If it's not zeroes, when images with transparency are drawn in the context, this buffer
|
|
||||||
// will stay with undefined pixels, which means that two pictures with the same pixels will
|
|
||||||
// most likely have different blocks (which is not supposed to happen).
|
|
||||||
bitmapData = calloc(bitmapByteCount, 1);
|
|
||||||
if (bitmapData == NULL)
|
|
||||||
{
|
|
||||||
fprintf (stderr, "Memory not allocated!");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
context = CGBitmapContextCreate (bitmapData,width,height,8,bitmapBytesPerRow,colorSpace,kCGImageAlphaNoneSkipLast);
|
|
||||||
if (context== NULL)
|
|
||||||
{
|
|
||||||
free (bitmapData);
|
|
||||||
fprintf (stderr, "Context not created!");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
CGColorSpaceRelease( colorSpace );
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns 0x00RRGGBB
|
|
||||||
int GetBlock(unsigned char *imageData, int imageWidth, int imageHeight, int boxX, int boxY, int boxW, int boxH)
|
|
||||||
{
|
|
||||||
int i,j;
|
|
||||||
int totalR = 0;
|
|
||||||
int totalG = 0;
|
|
||||||
int totalB = 0;
|
|
||||||
for(i = boxY; i < boxY + boxH; i++)
|
|
||||||
{
|
|
||||||
for(j = boxX; j < boxX + boxW; j++)
|
|
||||||
{
|
|
||||||
int offset = (i * imageWidth * 4) + (j * 4);
|
|
||||||
totalR += *(imageData + offset);
|
|
||||||
totalG += *(imageData + offset + 1);
|
|
||||||
totalB += *(imageData + offset + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int pixelCount = boxH * boxW;
|
|
||||||
int result = 0;
|
|
||||||
result += (totalR / pixelCount) << 16;
|
|
||||||
result += (totalG / pixelCount) << 8;
|
|
||||||
result += (totalB / pixelCount);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString* GetBlocks (NSString* filePath, int blockCount)
|
|
||||||
{
|
|
||||||
CFURLRef fileURL = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE);
|
|
||||||
CGImageSourceRef source = CGImageSourceCreateWithURL(fileURL, NULL);
|
|
||||||
if (source == NULL)
|
|
||||||
return NULL;
|
|
||||||
CGImageRef image = CGImageSourceCreateImageAtIndex(source, 0, NULL);
|
|
||||||
if (image == NULL)
|
|
||||||
return NULL;
|
|
||||||
size_t width = CGImageGetWidth(image);
|
|
||||||
size_t height = CGImageGetHeight(image);
|
|
||||||
CGContextRef myContext = MyCreateBitmapContext(width, height);
|
|
||||||
CGRect myBoundingBox = CGRectMake (0, 0, width, height);
|
|
||||||
CGContextDrawImage(myContext, myBoundingBox, image);
|
|
||||||
unsigned char *bitmapData = CGBitmapContextGetData(myContext);
|
|
||||||
if (bitmapData == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
int blockHeight = height / blockCount;
|
|
||||||
if (blockHeight < 1)
|
|
||||||
blockHeight = 1;
|
|
||||||
int blockWidth = width / blockCount;
|
|
||||||
if (blockWidth < 1)
|
|
||||||
blockWidth = 1;
|
|
||||||
//blockCount might have changed
|
|
||||||
int blockXCount = (width / blockWidth);
|
|
||||||
int blockYCount = (height / blockHeight);
|
|
||||||
|
|
||||||
CFMutableArrayRef blocks = CFArrayCreateMutable(NULL, blockXCount * blockYCount, &kCFTypeArrayCallBacks);
|
|
||||||
int i,j;
|
|
||||||
for(i = 0; i < blockYCount; i++)
|
|
||||||
{
|
|
||||||
for(j = 0; j < blockXCount; j++)
|
|
||||||
{
|
|
||||||
int block = GetBlock(bitmapData, width, height, j * blockWidth, i * blockHeight, blockWidth, blockHeight);
|
|
||||||
CFStringRef strBlock = CFStringCreateWithFormat(NULL, NULL, CFSTR("%06x"), block);
|
|
||||||
CFArrayAppendValue(blocks, strBlock);
|
|
||||||
CFRelease(strBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CGContextRelease (myContext);
|
|
||||||
if (bitmapData) free(bitmapData);
|
|
||||||
CGImageRelease(image);
|
|
||||||
CFRelease(source);
|
|
||||||
CFRelease(fileURL);
|
|
||||||
|
|
||||||
CFStringRef result = CFStringCreateByCombiningStrings(NULL, blocks, CFSTR(""));
|
|
||||||
CFRelease(blocks);
|
|
||||||
return (NSString *)result;
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.hardcoded.net/licenses/hs_license
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "../base/PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface PyDupeGuru : PyDupeGuruBase
|
|
||||||
- (void)clearPictureCache;
|
|
||||||
- (NSString *)getSelectedDupePath;
|
|
||||||
- (NSString *)getSelectedDupeRefPath;
|
|
||||||
- (void)setMatchScaled:(NSNumber *)match_scaled;
|
|
||||||
@end
|
|
||||||
@@ -1,37 +1,14 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
#import <Cocoa/Cocoa.h>
|
||||||
#import "Outline.h"
|
#import "ResultWindowBase.h"
|
||||||
#import "../base/ResultWindow.h"
|
|
||||||
|
|
||||||
@interface ResultWindow : ResultWindowBase
|
@interface ResultWindow : ResultWindowBase {}
|
||||||
{
|
- (void)clearPictureCache;
|
||||||
IBOutlet NSSearchField *filterField;
|
|
||||||
|
|
||||||
NSMutableIndexSet *_deltaColumns;
|
|
||||||
}
|
|
||||||
- (IBAction)clearIgnoreList:(id)sender;
|
|
||||||
- (IBAction)clearPictureCache:(id)sender;
|
|
||||||
- (IBAction)filter:(id)sender;
|
|
||||||
- (IBAction)ignoreSelected:(id)sender;
|
|
||||||
- (IBAction)markAll:(id)sender;
|
|
||||||
- (IBAction)markInvert:(id)sender;
|
|
||||||
- (IBAction)markNone:(id)sender;
|
|
||||||
- (IBAction)markSelected:(id)sender;
|
|
||||||
- (IBAction)markToggle:(id)sender;
|
|
||||||
- (IBAction)openSelected:(id)sender;
|
|
||||||
- (IBAction)refresh:(id)sender;
|
|
||||||
- (IBAction)removeMarked:(id)sender;
|
|
||||||
- (IBAction)removeSelected:(id)sender;
|
|
||||||
- (IBAction)renameSelected:(id)sender;
|
|
||||||
- (IBAction)revealSelected:(id)sender;
|
|
||||||
- (IBAction)startDuplicateScan:(id)sender;
|
|
||||||
- (IBAction)toggleDelta:(id)sender;
|
|
||||||
- (IBAction)toggleDirectories:(id)sender;
|
|
||||||
@end
|
@end
|
||||||
|
|||||||
@@ -1,269 +1,58 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
|
|
||||||
This software is licensed under the "HS" 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/hs_license
|
http://www.hardcoded.net/licenses/bsd_license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#import "ResultWindow.h"
|
#import "ResultWindow.h"
|
||||||
#import "Dialogs.h"
|
#import "Dialogs.h"
|
||||||
#import "ProgressController.h"
|
|
||||||
#import "RegistrationInterface.h"
|
|
||||||
#import "Utils.h"
|
#import "Utils.h"
|
||||||
#import "AppDelegate.h"
|
#import "PyDupeGuru.h"
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation ResultWindow
|
@implementation ResultWindow
|
||||||
/* Override */
|
/* Override */
|
||||||
- (void)awakeFromNib
|
- (void)initResultColumns
|
||||||
{
|
{
|
||||||
[super awakeFromNib];
|
HSColumnDef defs[] = {
|
||||||
[[self window] setTitle:@"dupeGuru Picture Edition"];
|
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
|
||||||
_displayDelta = NO;
|
{@"name", 162, 16, 0, YES, nil},
|
||||||
_powerMode = NO;
|
{@"folder_path", 142, 16, 0, YES, nil},
|
||||||
_deltaColumns = [[NSMutableIndexSet indexSetWithIndexesInRange:NSMakeRange(2,5)] retain];
|
{@"size", 63, 16, 0, YES, nil},
|
||||||
[_deltaColumns removeIndex:3];
|
{@"extension", 40, 16, 0, YES, nil},
|
||||||
[_deltaColumns removeIndex:4];
|
{@"dimensions", 73, 16, 0, YES, nil},
|
||||||
[deltaSwitch setSelectedSegment:0];
|
{@"exif_timestamp", 120, 16, 0, YES, nil},
|
||||||
[pmSwitch setSelectedSegment:0];
|
{@"mtime", 120, 16, 0, YES, nil},
|
||||||
[py setDisplayDeltaValues:b2n(_displayDelta)];
|
{@"percentage", 58, 16, 0, YES, nil},
|
||||||
[matches setTarget:self];
|
{@"dupe_count", 80, 16, 0, YES, nil},
|
||||||
[matches setDoubleAction:@selector(openSelected:)];
|
nil
|
||||||
[self refreshStats];
|
};
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resultsMarkingChanged:) name:ResultsMarkingChangedNotification object:nil];
|
[[table columns] initializeColumns:defs];
|
||||||
|
NSTableColumn *c = [matches tableColumnWithIdentifier:@"marked"];
|
||||||
|
[[c dataCell] setButtonType:NSSwitchButton];
|
||||||
|
[[c dataCell] setControlSize:NSSmallControlSize];
|
||||||
|
c = [matches tableColumnWithIdentifier:@"size"];
|
||||||
|
[[c dataCell] setAlignment:NSRightTextAlignment];
|
||||||
|
[[table columns] restoreColumns];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void)setScanOptions
|
||||||
|
{
|
||||||
|
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||||
|
[model setScanType:n2i([ud objectForKey:@"scanType"])];
|
||||||
|
[model setMinMatchPercentage:n2i([ud objectForKey:@"minMatchPercentage"])];
|
||||||
|
[model setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
|
||||||
|
[model setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
|
||||||
|
[model setMatchScaled:n2b([ud objectForKey:@"matchScaled"])];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
- (IBAction)clearIgnoreList:(id)sender
|
- (void)clearPictureCache
|
||||||
{
|
{
|
||||||
int i = n2i([py getIgnoreListCount]);
|
NSString *msg = NSLocalizedString(@"Do you really want to remove all your cached picture analysis?", @"");
|
||||||
if (!i)
|
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
|
||||||
return;
|
return;
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"Do you really want to remove all %d items from the ignore list?",i]] == NSAlertSecondButtonReturn) // NO
|
[model clearPictureCache];
|
||||||
return;
|
|
||||||
[py clearIgnoreList];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
- (IBAction)clearPictureCache:(id)sender
|
|
||||||
{
|
|
||||||
if ([Dialogs askYesNo:@"Do you really want to remove all your cached picture analysis?"] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[(PyDupeGuru *)py clearPictureCache];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)filter:(id)sender
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[py setEscapeFilterRegexp:b2n(!n2b([ud objectForKey:@"useRegexpFilter"]))];
|
|
||||||
[py applyFilter:[filterField stringValue]];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)ignoreSelected:(id)sender
|
|
||||||
{
|
|
||||||
NSArray *nodeList = [self getSelected:YES];
|
|
||||||
if (![nodeList count])
|
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"All selected %d matches are going to be ignored in all subsequent scans. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[self performPySelection:[self getSelectedPaths:YES]];
|
|
||||||
[py addSelectedToIgnoreList];
|
|
||||||
[py removeSelected];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markAll:(id)sender
|
|
||||||
{
|
|
||||||
[py markAll];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markInvert:(id)sender
|
|
||||||
{
|
|
||||||
[py markInvert];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markNone:(id)sender
|
|
||||||
{
|
|
||||||
[py markNone];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markSelected:(id)sender
|
|
||||||
{
|
|
||||||
[self performPySelection:[self getSelectedPaths:YES]];
|
|
||||||
[py toggleSelectedMark];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)markToggle:(id)sender
|
|
||||||
{
|
|
||||||
OVNode *node = [matches itemAtRow:[matches clickedRow]];
|
|
||||||
[self performPySelection:[NSArray arrayWithObject:p2a([node indexPath])]];
|
|
||||||
[py toggleSelectedMark];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsMarkingChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)openSelected:(id)sender
|
|
||||||
{
|
|
||||||
[self performPySelection:[self getSelectedPaths:NO]];
|
|
||||||
[py openSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)refresh:(id)sender
|
|
||||||
{
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)removeMarked:(id)sender
|
|
||||||
{
|
|
||||||
int mark_count = [[py getMarkCount] intValue];
|
|
||||||
if (!mark_count)
|
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",mark_count]] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[py removeMarked];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)removeSelected:(id)sender
|
|
||||||
{
|
|
||||||
NSArray *nodeList = [self getSelected:YES];
|
|
||||||
if (![nodeList count])
|
|
||||||
return;
|
|
||||||
if ([Dialogs askYesNo:[NSString stringWithFormat:@"You are about to remove %d files from results. Continue?",[nodeList count]]] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[self performPySelection:[self getSelectedPaths:YES]];
|
|
||||||
[py removeSelected];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:ResultsChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)renameSelected:(id)sender
|
|
||||||
{
|
|
||||||
int col = [matches columnWithIdentifier:@"0"];
|
|
||||||
int row = [matches selectedRow];
|
|
||||||
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)resetColumnsToDefault:(id)sender
|
|
||||||
{
|
|
||||||
NSMutableArray *columnsOrder = [NSMutableArray array];
|
|
||||||
[columnsOrder addObject:@"0"];
|
|
||||||
[columnsOrder addObject:@"1"];
|
|
||||||
[columnsOrder addObject:@"2"];
|
|
||||||
[columnsOrder addObject:@"4"];
|
|
||||||
[columnsOrder addObject:@"7"];
|
|
||||||
NSMutableDictionary *columnsWidth = [NSMutableDictionary dictionary];
|
|
||||||
[columnsWidth setObject:i2n(121) forKey:@"0"];
|
|
||||||
[columnsWidth setObject:i2n(120) forKey:@"1"];
|
|
||||||
[columnsWidth setObject:i2n(63) forKey:@"2"];
|
|
||||||
[columnsWidth setObject:i2n(73) forKey:@"4"];
|
|
||||||
[columnsWidth setObject:i2n(58) forKey:@"7"];
|
|
||||||
[self restoreColumnsPosition:columnsOrder widths:columnsWidth];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)revealSelected:(id)sender
|
|
||||||
{
|
|
||||||
[self performPySelection:[self getSelectedPaths:NO]];
|
|
||||||
[py revealSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)startDuplicateScan:(id)sender
|
|
||||||
{
|
|
||||||
if ([matches numberOfRows] > 0)
|
|
||||||
{
|
|
||||||
if ([Dialogs askYesNo:@"Are you sure you want to start a new duplicate scan?"] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
PyDupeGuru *_py = (PyDupeGuru *)py;
|
|
||||||
[_py setMinMatchPercentage:[ud objectForKey:@"minMatchPercentage"]];
|
|
||||||
[_py setMixFileKind:[ud objectForKey:@"mixFileKind"]];
|
|
||||||
[_py setMatchScaled:[ud objectForKey:@"matchScaled"]];
|
|
||||||
int r = n2i([py doScan]);
|
|
||||||
[matches reloadData];
|
|
||||||
[self refreshStats];
|
|
||||||
if (r != 0)
|
|
||||||
[[ProgressController mainProgressController] hide];
|
|
||||||
if (r == 1)
|
|
||||||
[Dialogs showMessage:@"You cannot make a duplicate scan with only reference directories."];
|
|
||||||
if (r == 3)
|
|
||||||
{
|
|
||||||
[Dialogs showMessage:@"The selected directories contain no scannable file."];
|
|
||||||
[app toggleDirectories:nil];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleDelta:(id)sender
|
|
||||||
{
|
|
||||||
if ([deltaSwitch selectedSegment] == 1)
|
|
||||||
[deltaSwitch setSelectedSegment:0];
|
|
||||||
else
|
|
||||||
[deltaSwitch setSelectedSegment:1];
|
|
||||||
[self changeDelta:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)toggleDirectories:(id)sender
|
|
||||||
{
|
|
||||||
[(AppDelegate *)app toggleDirectories:sender];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Public */
|
|
||||||
- (void)initResultColumns
|
|
||||||
{
|
|
||||||
NSTableColumn *refCol = [matches tableColumnWithIdentifier:@"0"];
|
|
||||||
_resultColumns = [[NSMutableArray alloc] init];
|
|
||||||
[_resultColumns addObject:[matches tableColumnWithIdentifier:@"0"]]; // File Name
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:1 title:@"Directory" width:120 refCol:refCol]];
|
|
||||||
NSTableColumn *sizeCol = [self getColumnForIdentifier:2 title:@"Size (KB)" width:63 refCol:refCol];
|
|
||||||
[[sizeCol dataCell] setAlignment:NSRightTextAlignment];
|
|
||||||
[_resultColumns addObject:sizeCol];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:3 title:@"Kind" width:40 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:4 title:@"Dimensions" width:80 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:5 title:@"Creation" width:120 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:6 title:@"Modification" width:120 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:7 title:@"Match %" width:58 refCol:refCol]];
|
|
||||||
[_resultColumns addObject:[self getColumnForIdentifier:8 title:@"Dupe Count" width:80 refCol:refCol]];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Delegate */
|
|
||||||
- (void)outlineView:(NSOutlineView *)outlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
|
||||||
{
|
|
||||||
OVNode *node = item;
|
|
||||||
if ([[tableColumn identifier] isEqual:@"mark"])
|
|
||||||
{
|
|
||||||
[cell setEnabled: [node isMarkable]];
|
|
||||||
}
|
|
||||||
if ([cell isKindOfClass:[NSTextFieldCell class]])
|
|
||||||
{
|
|
||||||
// Determine if the text color will be blue due to directory being reference.
|
|
||||||
NSTextFieldCell *textCell = cell;
|
|
||||||
if ([node isMarkable])
|
|
||||||
[textCell setTextColor:[NSColor blackColor]];
|
|
||||||
else
|
|
||||||
[textCell setTextColor:[NSColor blueColor]];
|
|
||||||
if ((_displayDelta) && (_powerMode || ([node level] > 1)))
|
|
||||||
{
|
|
||||||
int i = [[tableColumn identifier] intValue];
|
|
||||||
if ([_deltaColumns containsIndex:i])
|
|
||||||
[textCell setTextColor:[NSColor orangeColor]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
|
|
||||||
{
|
|
||||||
[self performPySelection:[self getSelectedPaths:NO]];
|
|
||||||
[py refreshDetailsWithSelected];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationName:DuplicateSelectionChangedNotification object:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)resultsMarkingChanged:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[matches invalidateMarkings];
|
|
||||||
[self refreshStats];
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
@@ -1,218 +1,17 @@
|
|||||||
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
|
||||||
#
|
#
|
||||||
# This software is licensed under the "HS" 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/hs_license
|
# http://www.hardcoded.net/licenses/bsd_license
|
||||||
|
|
||||||
import objc
|
from hscommon.trans import install_gettext_trans_under_cocoa
|
||||||
from AppKit import *
|
install_gettext_trans_under_cocoa()
|
||||||
|
|
||||||
from core_pe import app_cocoa as app_pe_cocoa
|
from cocoa.inter import PySelectableList, PyColumns, PyTable
|
||||||
|
|
||||||
# Fix py2app imports which chokes on relative imports
|
from inter.all import *
|
||||||
from core import app, app_cocoa, data, directories, engine, export, ignore, results, scanner
|
from inter.app_pe import PyDupeGuru
|
||||||
from core_pe import block, cache, matchbase, data
|
|
||||||
from hsutil import conflict
|
|
||||||
|
|
||||||
class PyApp(NSObject):
|
|
||||||
pass #fake class
|
|
||||||
|
|
||||||
class PyDupeGuru(PyApp):
|
|
||||||
def init(self):
|
|
||||||
self = super(PyDupeGuru,self).init()
|
|
||||||
self.app = app_pe_cocoa.DupeGuruPE()
|
|
||||||
return self
|
|
||||||
|
|
||||||
#---Directories
|
|
||||||
def addDirectory_(self,directory):
|
|
||||||
return self.app.add_directory(directory)
|
|
||||||
|
|
||||||
def removeDirectory_(self,index):
|
|
||||||
self.app.RemoveDirectory(index)
|
|
||||||
|
|
||||||
def setDirectory_state_(self,node_path,state):
|
|
||||||
self.app.SetDirectoryState(node_path,state)
|
|
||||||
|
|
||||||
#---Results
|
|
||||||
def clearIgnoreList(self):
|
|
||||||
self.app.scanner.ignore_list.Clear()
|
|
||||||
|
|
||||||
def clearPictureCache(self):
|
|
||||||
self.app.scanner.cached_blocks.clear()
|
|
||||||
|
|
||||||
def doScan(self):
|
|
||||||
return self.app.start_scanning()
|
|
||||||
|
|
||||||
def exportToXHTMLwithColumns_(self, column_ids):
|
|
||||||
return self.app.export_to_xhtml(column_ids)
|
|
||||||
|
|
||||||
def loadIgnoreList(self):
|
|
||||||
self.app.load_ignore_list()
|
|
||||||
|
|
||||||
def loadResults(self):
|
|
||||||
self.app.load()
|
|
||||||
|
|
||||||
def markAll(self):
|
|
||||||
self.app.results.mark_all()
|
|
||||||
|
|
||||||
def markNone(self):
|
|
||||||
self.app.results.mark_none()
|
|
||||||
|
|
||||||
def markInvert(self):
|
|
||||||
self.app.results.mark_invert()
|
|
||||||
|
|
||||||
def purgeIgnoreList(self):
|
|
||||||
self.app.PurgeIgnoreList()
|
|
||||||
|
|
||||||
def toggleSelectedMark(self):
|
|
||||||
self.app.ToggleSelectedMarkState()
|
|
||||||
|
|
||||||
def saveIgnoreList(self):
|
|
||||||
self.app.save_ignore_list()
|
|
||||||
|
|
||||||
def saveResults(self):
|
|
||||||
self.app.save()
|
|
||||||
|
|
||||||
def refreshDetailsWithSelected(self):
|
|
||||||
self.app.RefreshDetailsWithSelected()
|
|
||||||
|
|
||||||
def selectedResultNodePaths(self):
|
|
||||||
return self.app.selected_result_node_paths()
|
|
||||||
|
|
||||||
def selectResultNodePaths_(self,node_paths):
|
|
||||||
self.app.SelectResultNodePaths(node_paths)
|
|
||||||
|
|
||||||
def selectedPowerMarkerNodePaths(self):
|
|
||||||
return self.app.selected_powermarker_node_paths()
|
|
||||||
|
|
||||||
def selectPowerMarkerNodePaths_(self,node_paths):
|
|
||||||
self.app.SelectPowerMarkerNodePaths(node_paths)
|
|
||||||
|
|
||||||
#---Actions
|
|
||||||
def addSelectedToIgnoreList(self):
|
|
||||||
self.app.AddSelectedToIgnoreList()
|
|
||||||
|
|
||||||
def deleteMarked(self):
|
|
||||||
self.app.delete_marked()
|
|
||||||
|
|
||||||
def applyFilter_(self, filter):
|
|
||||||
self.app.apply_filter(filter)
|
|
||||||
|
|
||||||
def makeSelectedReference(self):
|
|
||||||
self.app.MakeSelectedReference()
|
|
||||||
|
|
||||||
def copyOrMove_markedTo_recreatePath_(self,copy,destination,recreate_path):
|
|
||||||
self.app.copy_or_move_marked(copy, destination, recreate_path)
|
|
||||||
|
|
||||||
def openSelected(self):
|
|
||||||
self.app.OpenSelected()
|
|
||||||
|
|
||||||
def removeMarked(self):
|
|
||||||
self.app.results.perform_on_marked(lambda x:True,True)
|
|
||||||
|
|
||||||
def removeSelected(self):
|
|
||||||
self.app.RemoveSelected()
|
|
||||||
|
|
||||||
def renameSelected_(self,newname):
|
|
||||||
return self.app.RenameSelected(newname)
|
|
||||||
|
|
||||||
def revealSelected(self):
|
|
||||||
self.app.RevealSelected()
|
|
||||||
|
|
||||||
#---Misc
|
|
||||||
def sortDupesBy_ascending_(self,key,asc):
|
|
||||||
self.app.sort_dupes(key,asc)
|
|
||||||
|
|
||||||
def sortGroupsBy_ascending_(self,key,asc):
|
|
||||||
self.app.sort_groups(key,asc)
|
|
||||||
|
|
||||||
#---Information
|
|
||||||
def getIgnoreListCount(self):
|
|
||||||
return len(self.app.scanner.ignore_list)
|
|
||||||
|
|
||||||
def getMarkCount(self):
|
|
||||||
return self.app.results.mark_count
|
|
||||||
|
|
||||||
def getStatLine(self):
|
|
||||||
return self.app.stat_line
|
|
||||||
|
|
||||||
def getOperationalErrorCount(self):
|
|
||||||
return self.app.last_op_error_count
|
|
||||||
|
|
||||||
def getSelectedDupePath(self):
|
|
||||||
return unicode(self.app.selected_dupe_path())
|
|
||||||
|
|
||||||
def getSelectedDupeRefPath(self):
|
|
||||||
return unicode(self.app.selected_dupe_ref_path())
|
|
||||||
|
|
||||||
#---Data
|
|
||||||
@objc.signature('i@:i')
|
|
||||||
def getOutlineViewMaxLevel_(self, tag):
|
|
||||||
return self.app.GetOutlineViewMaxLevel(tag)
|
|
||||||
|
|
||||||
@objc.signature('@@:i@')
|
|
||||||
def getOutlineView_childCountsForPath_(self, tag, node_path):
|
|
||||||
return self.app.GetOutlineViewChildCounts(tag, node_path)
|
|
||||||
|
|
||||||
def getOutlineView_valuesForIndexes_(self,tag,node_path):
|
|
||||||
return self.app.GetOutlineViewValues(tag,node_path)
|
|
||||||
|
|
||||||
def getOutlineView_markedAtIndexes_(self,tag,node_path):
|
|
||||||
return self.app.GetOutlineViewMarked(tag,node_path)
|
|
||||||
|
|
||||||
def getTableViewCount_(self,tag):
|
|
||||||
return self.app.GetTableViewCount(tag)
|
|
||||||
|
|
||||||
def getTableViewMarkedIndexes_(self,tag):
|
|
||||||
return self.app.GetTableViewMarkedIndexes(tag)
|
|
||||||
|
|
||||||
def getTableView_valuesForRow_(self,tag,row):
|
|
||||||
return self.app.GetTableViewValues(tag,row)
|
|
||||||
|
|
||||||
#---Properties
|
|
||||||
def setMatchScaled_(self,match_scaled):
|
|
||||||
self.app.scanner.match_scaled = match_scaled
|
|
||||||
|
|
||||||
def setMinMatchPercentage_(self,percentage):
|
|
||||||
self.app.scanner.threshold = int(percentage)
|
|
||||||
|
|
||||||
def setMixFileKind_(self,mix_file_kind):
|
|
||||||
self.app.scanner.mix_file_kind = mix_file_kind
|
|
||||||
|
|
||||||
def setDisplayDeltaValues_(self,display_delta_values):
|
|
||||||
self.app.display_delta_values= display_delta_values
|
|
||||||
|
|
||||||
def setEscapeFilterRegexp_(self, escape_filter_regexp):
|
|
||||||
self.app.options['escape_filter_regexp'] = escape_filter_regexp
|
|
||||||
|
|
||||||
def setRemoveEmptyFolders_(self, remove_empty_folders):
|
|
||||||
self.app.options['clean_empty_dirs'] = remove_empty_folders
|
|
||||||
|
|
||||||
#---Worker
|
|
||||||
def getJobProgress(self):
|
|
||||||
return self.app.progress.last_progress
|
|
||||||
|
|
||||||
def getJobDesc(self):
|
|
||||||
return self.app.progress.last_desc
|
|
||||||
|
|
||||||
def cancelJob(self):
|
|
||||||
self.app.progress.job_cancelled = True
|
|
||||||
|
|
||||||
#---Registration
|
|
||||||
def appName(self):
|
|
||||||
return "dupeGuru Picture Edition"
|
|
||||||
|
|
||||||
def demoLimitDescription(self):
|
|
||||||
return self.app.DEMO_LIMIT_DESC
|
|
||||||
|
|
||||||
@objc.signature('i@:')
|
|
||||||
def isRegistered(self):
|
|
||||||
return self.app.registered
|
|
||||||
|
|
||||||
@objc.signature('i@:@@')
|
|
||||||
def isCodeValid_withEmail_(self, code, email):
|
|
||||||
return self.app.is_code_valid(code, email)
|
|
||||||
|
|
||||||
def setRegisteredCode_andEmail_(self, code, email):
|
|
||||||
self.app.set_registration(code, email)
|
|
||||||
|
|
||||||
|
# When built under virtualenv, the dependency collector misses this module, so we have to force it
|
||||||
|
# to see the module.
|
||||||
|
import distutils.sysconfig
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user