mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-25 16:11:39 +00:00
Compare commits
511 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e4b41feb5 | |||
|
cbfa8720f1
|
|||
|
a02c5e5b9b
|
|||
|
35e6ffd6af
|
|||
|
e957f840da
|
|||
|
85e22089bd
|
|||
|
b7d68b4458
|
|||
|
8f440603ee
|
|||
|
5d8e559ca3
|
|||
|
2c11eecf97
|
|||
|
02803f738b
|
|||
|
db27e6a645
|
|||
|
c9c35cc60d
|
|||
|
880205dbc8
|
|||
|
6456e64328
|
|||
|
f6a0c0cc6d
|
|||
|
eb57d269fc
|
|||
| 34f41dc522 | |||
|
|
77460045c4 | ||
|
|
9753afba74 | ||
|
|
1ea108fc2b | ||
|
|
2f02a6010d | ||
|
b80489fd66
|
|||
|
1d60e124ee
|
|||
|
e22d7d2fc9
|
|||
|
0a0694e095
|
|||
|
3da9d5d869
|
|||
|
78fb052d77
|
|||
|
9805cba10d
|
|||
|
4c3dfe2f1f
|
|||
|
b0baa5bfd6
|
|||
| 22996ee914 | |||
|
|
31ec9c667f | ||
|
3045361243
|
|||
|
809116c764
|
|||
|
83f401595d
|
|||
|
814d145366
|
|||
|
efb76c7686
|
|||
|
47dbe805bb
|
|||
|
f11fccc889
|
|||
|
2e13c4ccb5
|
|||
|
da72ffd1fd
|
|||
|
2c9437bef4
|
|||
|
f9085386a6
|
|||
|
d576a7043c
|
|||
|
1ef5f56158
|
|||
|
f9316de244
|
|||
|
0189c29f47
|
|||
|
b4fa1d68f0
|
|||
|
16df882481
|
|||
|
58c04ff9ad
|
|||
|
6b8f85e39a
|
|||
|
2fff1a3436
|
|||
|
a685524dd5
|
|||
|
74918e2c56
|
|||
|
18895d983b
|
|||
|
fe720208ea
|
|||
| 091d9e9239 | |||
|
5a4958cff9
|
|||
|
be10b462fc
|
|||
|
d62b13bcdb
|
|||
|
06eca11f0b
|
|||
|
2879f18e0d
|
|||
|
3ee21771f9
|
|||
| c0ba6fb57a | |||
|
bc942b8263
|
|||
|
ffe6b7047c
|
|||
|
9446f37fad
|
|||
|
af19660c18
|
|||
|
99ad297906
|
|||
| e11f996dfc | |||
|
|
e95306e58f | ||
|
|
891a875990 | ||
|
|
545a5a75fb | ||
|
|
7b764f183e | ||
|
fdc8a17d26
|
|||
|
cb3bbbec6e
|
|||
|
c51a82a2ce
|
|||
|
0cd8f5e948
|
|||
|
9c09607c08
|
|||
|
3bd342770c
|
|||
| 14b456dcf9 | |||
|
|
3dccb686e2 | ||
| 0db66baace | |||
| e3828ae2ca | |||
|
|
23c59787e5 | ||
| 2f8d603251 | |||
|
|
a51f263632 | ||
|
|
718ca5b313 | ||
|
|
277bc3fbb8 | ||
|
|
e07dfd5955 | ||
| 4641bd6ec9 | |||
|
|
a6f83ad3d7 | ||
|
|
ab8750eedb | ||
|
|
22033211d6 | ||
| 0b46ca2222 | |||
| 72e0f76242 | |||
|
|
65c1d463f8 | ||
| e6c791ab0a | |||
|
|
78f5088101 | ||
|
|
095df5eb95 | ||
|
|
f1ae478433 | ||
|
|
c4dcfd3d4b | ||
| 0840104edf | |||
|
|
6b4b436251 | ||
|
|
d18b8c10ec | ||
|
4a40b346a4
|
|||
|
035cdc23b1
|
|||
|
fbdb333457
|
|||
|
e36aab177c
|
|||
|
77116ba94b
|
|||
|
d7f79aefd2
|
|||
|
4c939f379c
|
|||
|
d098fe2281
|
|||
| 09cfbad38d | |||
|
|
528dedd813 | ||
| b30d67b834 | |||
|
|
3e6e74e2a9 | ||
|
|
b919b3ddc8 | ||
|
|
be3862fa8f | ||
|
|
da09920553 | ||
|
|
2baba3bfa0 | ||
|
a659a70dbe
|
|||
|
c9e48a5e3b
|
|||
|
68711162d1
|
|||
|
0b0fd36629
|
|||
|
bf5d151799
|
|||
|
e29a427caf
|
|||
|
95ccbad92b
|
|||
| 421a58a61c | |||
|
|
b5a3313f80 | ||
|
|
116ac18e13 | ||
|
|
32dcd90b50 | ||
|
|
c2fef8d624 | ||
|
fd0adc77b3
|
|||
|
6a03e1e399
|
|||
|
ae51842007
|
|||
| ab6acd9e88 | |||
|
6a2c1eb293
|
|||
| 7b4c31d262 | |||
|
|
5553414205 | ||
|
|
b138dfad33 | ||
| 701e6d4bb2 | |||
|
b44d1652b6
|
|||
|
|
990eaaa797 | ||
|
|
348ce95f83 | ||
|
|
3255bdf0a2 | ||
|
|
1058247b44 | ||
|
|
7414f82e28 | ||
|
|
8105bb709f | ||
|
ec628751af
|
|||
|
|
288023d03e | ||
|
|
7740dfca0e | ||
|
1e12ad8d4c
|
|||
|
|
c1d94d6771 | ||
| 7f691d3c31 | |||
|
|
a93bd3aeee | ||
|
|
39d353d073 | ||
|
|
b76e86686a | ||
|
|
b5f59d27c9 | ||
|
|
f0d3dec517 | ||
|
|
90c7c067b7 | ||
|
c8cfa954d5
|
|||
|
|
e533a396fb | ||
|
|
4b4cc04e87 | ||
|
e822a67b38
|
|||
|
c30c3400d4
|
|||
|
d539517525
|
|||
|
|
07eba09ec2 | ||
|
|
7f19647e4b | ||
| bf7d720126 | |||
|
|
6bc619055e | ||
|
|
452d1604bd | ||
|
|
680cb581c1 | ||
| 1d05f8910d | |||
|
|
bd09b30468 | ||
| 8d9933d035 | |||
|
|
cf5ba038d7 | ||
|
|
59ce740369 | ||
|
|
92feba5f08 | ||
|
|
a265b71d36 | ||
| 8d26c921a0 | |||
|
|
32d66cd19b | ||
|
|
735ba2fd0e | ||
|
|
b16b6ecf4d | ||
|
|
2875448c71 | ||
|
|
51b76385c0 | ||
|
|
b9f8dd6ea0 | ||
|
|
6623b04403 | ||
|
|
424d34a7ed | ||
|
|
2a032d24bc | ||
|
|
b8af2a4eb5 | ||
|
|
a55e02b36d | ||
|
|
18c933b4bf | ||
|
|
ea11a566af | ||
|
|
584e9c92d9 | ||
|
|
4a1641e39d | ||
|
|
26d18945b1 | ||
|
|
3382bd5e5b | ||
|
|
9f223f3964 | ||
|
|
2eaf7e7893 | ||
|
|
a26de27c47 | ||
|
|
21e62b7374 | ||
| 9e6b117327 | |||
|
|
3333d26557 | ||
|
|
6e81042989 | ||
|
|
470307aa3c | ||
|
|
089f00adb8 | ||
|
|
76fbfc2822 | ||
|
|
866bf996cf | ||
|
|
0104d8922c | ||
|
|
fbd7c4fe5f | ||
|
|
de5e61293b | ||
|
|
a3e402a3af | ||
|
|
056fa819cc | ||
|
|
3be1ee87c6 | ||
|
|
628d772766 | ||
|
|
acdeb01206 | ||
| ab402d4024 | |||
|
|
d2cdcc989b | ||
|
|
2620d0080c | ||
|
|
63a9f00552 | ||
|
|
87f9317805 | ||
|
|
a542168a0d | ||
|
|
86e1b55b02 | ||
|
|
1b3b40543b | ||
|
|
dd6ffe08d7 | ||
|
|
11254381a8 | ||
|
|
23642815f6 | ||
|
|
7e4f371841 | ||
|
|
9b8637ffc8 | ||
|
|
79613f9b1e | ||
|
|
fa54e93236 | ||
|
|
8fb82ae3d8 | ||
|
|
eab5003e61 | ||
|
|
da8c493c9f | ||
|
|
9795f14176 | ||
|
|
1937120ad7 | ||
|
|
1823575af4 | ||
|
|
7dc9f25b06 | ||
| 5502b48089 | |||
| f02b66fd54 | |||
| d2235f9bc9 | |||
|
|
5f5f9232c1 | ||
| c36fd84512 | |||
|
|
63b2f95cfa | ||
|
|
d193e1fd12 | ||
|
|
f0adf35db4 | ||
|
|
49a1beb225 | ||
|
|
f19b5d6ea6 | ||
|
|
730fadf63f | ||
|
|
9ae0d7e5cf | ||
| 1167519730 | |||
|
|
cf64565012 | ||
|
|
298f659f6e | ||
|
|
3539263437 | ||
|
|
6213d50670 | ||
|
|
ac941037ff | ||
|
|
733b3b0ed4 | ||
|
|
9168d72f38 | ||
|
|
75621cc816 | ||
|
|
3c816b2f11 | ||
|
|
85d6e05cd4 | ||
|
|
66127d025e | ||
|
|
58c675d1fa | ||
|
|
95b8406c7b | ||
|
|
3eddeb6aeb | ||
|
|
56912a7108 | ||
|
|
7ab299874d | ||
|
|
a4265e7fff | ||
|
|
db228ec8a3 | ||
|
|
61fc4f07ae | ||
|
|
b0a256f0d4 | ||
|
|
4ee9479a5f | ||
|
|
e7b3252534 | ||
|
|
36ab84423a | ||
|
|
370b582c9b | ||
|
|
9f15139d5f | ||
|
|
011939f5ee | ||
|
|
977c20f7c4 | ||
|
|
aa79b31aae | ||
|
|
970bb5e19d | ||
|
|
a706d0ebe5 | ||
|
|
b7abcf2989 | ||
|
|
8103cb3664 | ||
|
|
c3797918d2 | ||
|
|
60ddb9b596 | ||
|
|
a29f3fb407 | ||
|
|
c6162914ed | ||
|
|
02bd822ca0 | ||
|
|
ea6197626b | ||
|
|
468a736bfb | ||
|
|
f42df12a29 | ||
|
|
9b48e1851d | ||
|
|
c973224fa4 | ||
|
092cf1471b
|
|||
|
|
5cbe342d5b | ||
|
4f252480d3
|
|||
|
5cc439d846
|
|||
|
|
c6f5031dd8 | ||
|
|
eb6946343b | ||
|
|
e41a6b878c | ||
|
ee2671a5f3
|
|||
|
e05c72ad8c
|
|||
| 7658cdafbc | |||
|
ecf005fad0
|
|||
| de0542d2a8 | |||
|
|
bcb26507fe | ||
| c35db7f698 | |||
|
d2193328a7
|
|||
|
|
ed64428c80 | ||
|
|
e89156e55c | ||
|
4c9309ea9c
|
|||
| 1c00331bc2 | |||
| 427e32f406 | |||
|
|
b048fa5968 | ||
| d5a6ca7488 | |||
|
|
d15dea7aa0 | ||
|
|
ccb1c75f22 | ||
|
|
dffbed8e22 | ||
|
|
50ce010212 | ||
|
|
0e8cd32a6e | ||
|
|
ea191a8924 | ||
| 6abcedddda | |||
| debf309a9a | |||
|
|
4b1c925ab1 | ||
|
|
1c0990f610 | ||
|
|
89f2dc3b15 | ||
|
|
ffae58040d | ||
| 0cc1cb4cb8 | |||
|
|
dab762f05e | ||
| c4a6958ef0 | |||
| 98c6f12b39 | |||
| 5d21454789 | |||
| 3e4fe5b765 | |||
|
|
bd0f53bcbe | ||
|
|
d820fcc7b1 | ||
|
de8a0a21b2
|
|||
|
7ba8aa3514
|
|||
|
359d6498f7
|
|||
|
2ea02bd7b5
|
|||
| 8506d482af | |||
|
411d0d6e4a
|
|||
|
95ff6b6b76
|
|||
|
334f6fe989
|
|||
|
|
080bb8935c | ||
| ad2a07a289 | |||
|
|
c61a7f1aaf | ||
|
|
f536f32b19 | ||
|
|
8cdff7b48c | ||
|
|
718e99e880 | ||
|
|
3c2ef97ee2 | ||
|
|
2f439d0fc7 | ||
|
|
4f234f272f | ||
|
|
18acaae888 | ||
|
|
be7d558dfe | ||
|
|
0b12236537 | ||
|
|
ed2a0bcd4d | ||
|
11e57b0316
|
|||
|
c661905350
|
|||
|
d819719eca
|
|||
| 08722a30f2 | |||
|
a1cc0fe946
|
|||
| 2a2c0061f1 | |||
|
15bfe059c7
|
|||
| a1cacbe72b | |||
| 0a5db4c5c1 | |||
|
1b879259a4
|
|||
|
136342f7d0
|
|||
| 9eb15509c1 | |||
|
dde2c9bf8f
|
|||
| f7e20c8aa6 | |||
|
1a04f6ee86
|
|||
|
bd3d47bf19
|
|||
|
f953bc4af4
|
|||
| 10ac536c3b | |||
|
ab9703b86e
|
|||
|
79b97311e9
|
|||
| 48936b53a8 | |||
|
|
9bf1887109 | ||
| 30b6e5c68d | |||
| 20202d8dfa | |||
| 6c6271bc69 | |||
| f349f6a9b9 | |||
|
|
afe1d4ed2e | ||
|
|
c37037ca4a | ||
| 445f51d595 | |||
| 6132d7c211 | |||
|
|
79adbfd4f2 | ||
|
|
45b907a529 | ||
|
|
d5fef949e9 | ||
|
|
899a42f6a9 | ||
|
|
93a3978747 | ||
|
|
5d15cd4c97 | ||
|
|
7936339909 | ||
|
|
2f31dc7aab | ||
|
|
a6b1e6e9ab | ||
| 8cd0ef4c2b | |||
|
|
50e26928f4 | ||
|
|
84011fb46d | ||
|
|
8861f6296e | ||
|
|
35ea499857 | ||
|
|
a82a19e074 | ||
|
|
e72cf917f1 | ||
|
|
245ed0ddec | ||
|
|
f51f94e03d | ||
|
|
6a28017c49 | ||
|
|
dc6933c90c | ||
|
|
e0281dd740 | ||
|
|
79e99db1d3 | ||
|
|
76cc2000ab | ||
|
|
e4b6e12d4c | ||
|
|
c58a4817ca | ||
|
|
f7adb5f11e | ||
|
|
c43044ea4c | ||
|
|
cc01e8eb09 | ||
|
|
1c20e5c770 | ||
|
|
edcff588e2 | ||
|
|
26aad6e948 | ||
|
|
c303a490ef | ||
|
|
6ed4499a97 | ||
|
|
aa7499aa12 | ||
|
|
63558d647a | ||
|
|
eb3f7d65de | ||
|
|
ac8a336c4a | ||
|
|
0206f2fd15 | ||
|
|
b41d3f7efc | ||
|
|
c43d37582e | ||
|
|
30a278719b | ||
|
|
87ef46ca15 | ||
|
|
9f3ec065ed | ||
|
|
e19056048c | ||
|
|
76e5817ff3 | ||
|
|
20dc2d63fd | ||
|
|
28d2aa8197 | ||
|
|
5be9d537a5 | ||
|
|
b97e89d4d8 | ||
|
|
0f4992de47 | ||
|
|
55ad9ef33a | ||
|
|
e69a1764a0 | ||
|
|
215307df93 | ||
|
|
3aa99c396b | ||
|
|
9f2c3e7732 | ||
|
|
d660cef245 | ||
|
|
bdd404ce0e | ||
|
|
df9f72d9bf | ||
|
|
53bbc5901c | ||
|
|
0959f4581e | ||
|
|
b1ef3dc8fe | ||
|
|
334f4dd2ae | ||
|
|
fbdd1d866e | ||
|
|
64e86c9ff9 | ||
|
|
80f659858c | ||
|
|
ef8f8f0e44 | ||
|
|
b7a7282c2a | ||
|
|
668821301c | ||
|
|
13fb06a693 | ||
|
|
61b219ff43 | ||
|
|
c4aeda0bd0 | ||
|
|
76f3332d36 | ||
|
|
b47b1e11af | ||
|
|
168d94910b | ||
|
|
ca3172044f | ||
|
|
f66849b09d | ||
|
|
8c1078aa71 | ||
|
|
b780816e3c | ||
|
|
fb8a384a6a | ||
|
|
2be4ae8f65 | ||
|
|
f8686ffb55 | ||
|
|
3093a42553 | ||
|
|
83d934fd4f | ||
|
|
f3c09c7a8d | ||
|
|
a65077f871 | ||
|
|
d4919054f9 | ||
|
|
773f6651e6 | ||
|
|
9a25670552 | ||
|
|
8c9ef3ea29 | ||
|
|
7256adb4d4 | ||
|
|
ad45a6e16e | ||
|
|
c865f84c16 | ||
|
|
7d749779f2 | ||
|
|
8b878b7b13 | ||
|
|
0056f696df | ||
|
|
abd2f3a9d6 | ||
|
|
5c57a2a8fc | ||
|
|
dc76f9744e | ||
|
|
130581db53 | ||
|
|
9ed4b7abf0 | ||
|
|
a0a90e8ef8 | ||
|
|
197acbf5b3 | ||
|
|
09d5243648 | ||
|
|
10169bee9c | ||
|
|
bb8a41f8c5 | ||
|
|
bb1f0f5be6 | ||
|
|
4b6f8b45e2 | ||
|
|
2ed1b82ecf | ||
|
|
de9122c3cb | ||
|
|
632650b483 | ||
|
|
c05f01853d | ||
|
|
15539eb3c5 | ||
|
|
b9874cc7ed | ||
|
|
13a2868dd2 | ||
|
|
abb1345c49 | ||
|
|
9c53b2218c | ||
|
|
4b3c1e2828 | ||
|
|
b64f9f5ec0 | ||
|
|
40d9a486e2 | ||
|
|
6930e092e0 | ||
|
|
6b41223a22 | ||
|
|
d15321a8e9 | ||
|
|
d6533cbfa2 |
5
.ctags
Normal file
5
.ctags
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
-R
|
||||||
|
--exclude=build
|
||||||
|
--exclude=env
|
||||||
|
--exclude=.tox
|
||||||
|
--python-kinds=-i
|
||||||
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
|
||||||
|
**Desktop (please complete the following information):**
|
||||||
|
- OS: [e.g. Windows 10 / OSX 10.15 / Ubuntu 20.04 / Arch Linux]
|
||||||
|
- Version [e.g. 4.1.0]
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here. You may include the debug log although it is normally best to attach it as a file.
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
50
.github/workflows/codeql-analysis.yml
vendored
Normal file
50
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ master ]
|
||||||
|
schedule:
|
||||||
|
- cron: '24 20 * * 2'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'cpp', 'python' ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v1
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
- if: matrix.language == 'cpp'
|
||||||
|
name: Build Cpp
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install python3-pyqt5
|
||||||
|
make modules
|
||||||
|
- if: matrix.language == 'python'
|
||||||
|
name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v1
|
||||||
|
# Analysis
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v1
|
||||||
84
.github/workflows/default.yml
vendored
Normal file
84
.github/workflows/default.yml
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Workflow lints, and checks format in parallel then runs tests on all platforms
|
||||||
|
|
||||||
|
name: Default CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python 3.10
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt -r requirements-extra.txt
|
||||||
|
- name: Lint with flake8
|
||||||
|
run: |
|
||||||
|
flake8 .
|
||||||
|
format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python 3.10
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: '3.10'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt -r requirements-extra.txt
|
||||||
|
- name: Check format with black
|
||||||
|
run: |
|
||||||
|
black .
|
||||||
|
test:
|
||||||
|
needs: [lint, format]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
python-version: [3.6, 3.7, 3.8, 3.9, '3.10']
|
||||||
|
exclude:
|
||||||
|
- os: macos-latest
|
||||||
|
python-version: 3.6
|
||||||
|
- os: macos-latest
|
||||||
|
python-version: 3.7
|
||||||
|
- os: macos-latest
|
||||||
|
python-version: 3.8
|
||||||
|
- os: windows-latest
|
||||||
|
python-version: 3.6
|
||||||
|
- os: windows-latest
|
||||||
|
python-version: 3.7
|
||||||
|
- os: windows-latest
|
||||||
|
python-version: 3.8
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install -r requirements.txt -r requirements-extra.txt
|
||||||
|
- name: Build python modules
|
||||||
|
run: |
|
||||||
|
python build.py --modules
|
||||||
|
- name: Run tests
|
||||||
|
run: |
|
||||||
|
pytest core hscommon
|
||||||
|
- name: Upload Artifacts
|
||||||
|
if: matrix.os == 'ubuntu-latest'
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: modules ${{ matrix.python-version }}
|
||||||
|
path: ${{ github.workspace }}/**/*.so
|
||||||
21
.gitignore
vendored
21
.gitignore
vendored
@@ -1,25 +1,30 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
*.pyc
|
__pycache__
|
||||||
|
*.egg-info
|
||||||
*.so
|
*.so
|
||||||
*.mo
|
*.mo
|
||||||
*.pyd
|
|
||||||
*.waf*
|
*.waf*
|
||||||
.lock-waf*
|
.lock-waf*
|
||||||
.idea
|
|
||||||
.tox
|
.tox
|
||||||
|
/tags
|
||||||
|
*.eggs
|
||||||
|
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
install
|
env*
|
||||||
installer_tmp-cache
|
|
||||||
env
|
|
||||||
/deps
|
/deps
|
||||||
cocoa/autogen
|
cocoa/autogen
|
||||||
|
|
||||||
/run.py
|
/run.py
|
||||||
/conf.json
|
|
||||||
/cocoa/*/Info.plist
|
/cocoa/*/Info.plist
|
||||||
/cocoa/*/build
|
/cocoa/*/build
|
||||||
/qt/base/*_rc.py
|
/qt/*_rc.py
|
||||||
/help/*/conf.py
|
/help/*/conf.py
|
||||||
/help/*/changelog.rst
|
/help/*/changelog.rst
|
||||||
|
/transifex
|
||||||
|
|
||||||
|
*.pyd
|
||||||
|
*.exe
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
|||||||
1
.sonarcloud.properties
Normal file
1
.sonarcloud.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
sonar.python.version=3.6, 3.7, 3.8, 3.9, 3.10
|
||||||
11
.tx/config
11
.tx/config
@@ -1,21 +1,26 @@
|
|||||||
[main]
|
[main]
|
||||||
host = https://www.transifex.com
|
host = https://www.transifex.com
|
||||||
|
|
||||||
[dupeguru.core]
|
[dupeguru-1.core]
|
||||||
file_filter = locale/<lang>/LC_MESSAGES/core.po
|
file_filter = locale/<lang>/LC_MESSAGES/core.po
|
||||||
source_file = locale/core.pot
|
source_file = locale/core.pot
|
||||||
source_lang = en
|
source_lang = en
|
||||||
type = PO
|
type = PO
|
||||||
|
|
||||||
[dupeguru.columns]
|
[dupeguru-1.columns]
|
||||||
file_filter = locale/<lang>/LC_MESSAGES/columns.po
|
file_filter = locale/<lang>/LC_MESSAGES/columns.po
|
||||||
source_file = locale/columns.pot
|
source_file = locale/columns.pot
|
||||||
source_lang = en
|
source_lang = en
|
||||||
type = PO
|
type = PO
|
||||||
|
|
||||||
[dupeguru.ui]
|
[dupeguru-1.ui]
|
||||||
file_filter = locale/<lang>/LC_MESSAGES/ui.po
|
file_filter = locale/<lang>/LC_MESSAGES/ui.po
|
||||||
source_file = locale/ui.pot
|
source_file = locale/ui.pot
|
||||||
source_lang = en
|
source_lang = en
|
||||||
type = PO
|
type = PO
|
||||||
|
|
||||||
|
[dupeguru-1.qtlib]
|
||||||
|
file_filter = qtlib/locale/<lang>/LC_MESSAGES/qtlib.po
|
||||||
|
source_file = qtlib/locale/qtlib.pot
|
||||||
|
source_lang = en
|
||||||
|
type = PO
|
||||||
|
|||||||
21
CREDITS
Normal file
21
CREDITS
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
To know who contributed to dupeGuru, you can look at the commit log, but not all contributions
|
||||||
|
result in a commit. This file lists contributors who don't necessarily appear in the commit log.
|
||||||
|
|
||||||
|
* Jason Cho, Exchange icon
|
||||||
|
* schollidesign (https://findicons.com/pack/1035/human_o2), Zoom-in, Zoom-out, Zoom-best-fit, Zoom-original icons
|
||||||
|
* Jérôme Cantin, Main icon
|
||||||
|
* Gregor Tätzner, German localization
|
||||||
|
* Frank Weber, German localization
|
||||||
|
* Eric Dee, Chinese localization
|
||||||
|
* Aleš Nehyba, Czech localization
|
||||||
|
* Paolo Rossi, Italian localization
|
||||||
|
* Hrant Ohanyan, Armenian localization
|
||||||
|
* Igor Pavlov, Russian localization
|
||||||
|
* Kyrill Detinov, Russian localization
|
||||||
|
* Yuri Petrashko, Ukrainian localization
|
||||||
|
* Nickolas Pohilets, Ukrainian localization
|
||||||
|
* Victor Figueiredo, Brazilian localization
|
||||||
|
* Phan Anh, Vietnamese localization
|
||||||
|
* Gabriel Koutilellis, Greek localization
|
||||||
|
|
||||||
|
Thanks!
|
||||||
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
recursive-include core *.h
|
||||||
|
recursive-include core *.m
|
||||||
|
include run.py
|
||||||
|
graft locale
|
||||||
|
graft help
|
||||||
|
graft qtlib/locale
|
||||||
123
Makefile
Normal file
123
Makefile
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
PYTHON ?= python3
|
||||||
|
PYTHON_VERSION_MINOR := $(shell ${PYTHON} -c "import sys; print(sys.version_info.minor)")
|
||||||
|
PYRCC5 ?= pyrcc5
|
||||||
|
REQ_MINOR_VERSION = 6
|
||||||
|
PREFIX ?= /usr/local
|
||||||
|
|
||||||
|
# Window compatability via Msys2
|
||||||
|
# - venv creates Scripts instead of bin
|
||||||
|
# - compile generates .pyd instead of .so
|
||||||
|
# - venv with --sytem-site-packages has issues on windows as well...
|
||||||
|
|
||||||
|
ifeq ($(shell ${PYTHON} -c "import platform; print(platform.system())"), Windows)
|
||||||
|
BIN = Scripts
|
||||||
|
SO = *.pyd
|
||||||
|
VENV_OPTIONS =
|
||||||
|
else
|
||||||
|
BIN = bin
|
||||||
|
SO = *.so
|
||||||
|
VENV_OPTIONS = --system-site-packages
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Set this variable if all dependencies are already met on the system. We will then avoid the
|
||||||
|
# whole vitualenv creation and pip install dance.
|
||||||
|
NO_VENV ?=
|
||||||
|
|
||||||
|
ifdef NO_VENV
|
||||||
|
VENV_PYTHON = $(PYTHON)
|
||||||
|
else
|
||||||
|
VENV_PYTHON = ./env/$(BIN)/python
|
||||||
|
endif
|
||||||
|
|
||||||
|
# If you're installing into a path that is not going to be the final path prefix (such as a
|
||||||
|
# sandbox), set DESTDIR to that path.
|
||||||
|
|
||||||
|
# Our build scripts are not very "make like" yet and perform their task in a bundle. For now, we
|
||||||
|
# use one of each file to act as a representative, a target, of these groups.
|
||||||
|
|
||||||
|
packages = hscommon qtlib core qt
|
||||||
|
localedirs = $(wildcard locale/*/LC_MESSAGES)
|
||||||
|
pofiles = $(wildcard locale/*/LC_MESSAGES/*.po)
|
||||||
|
mofiles = $(patsubst %.po,%.mo,$(pofiles))
|
||||||
|
|
||||||
|
vpath %.po $(localedirs)
|
||||||
|
vpath %.mo $(localedirs)
|
||||||
|
|
||||||
|
all: | env i18n modules qt/dg_rc.py
|
||||||
|
@echo "Build complete! You can run dupeGuru with 'make run'"
|
||||||
|
|
||||||
|
run:
|
||||||
|
$(VENV_PYTHON) run.py
|
||||||
|
|
||||||
|
pyc: | env
|
||||||
|
${VENV_PYTHON} -m compileall ${packages}
|
||||||
|
|
||||||
|
reqs:
|
||||||
|
ifneq ($(shell test $(PYTHON_VERSION_MINOR) -ge $(REQ_MINOR_VERSION); echo $$?),0)
|
||||||
|
$(error "Python 3.${REQ_MINOR_VERSION}+ required. Aborting.")
|
||||||
|
endif
|
||||||
|
ifndef NO_VENV
|
||||||
|
@${PYTHON} -m venv -h > /dev/null || \
|
||||||
|
echo "Creation of our virtualenv failed. If you're on Ubuntu, you probably need python3-venv."
|
||||||
|
endif
|
||||||
|
@${PYTHON} -c 'import PyQt5' >/dev/null 2>&1 || \
|
||||||
|
{ echo "PyQt 5.4+ required. Install it and try again. Aborting"; exit 1; }
|
||||||
|
|
||||||
|
env: | reqs
|
||||||
|
ifndef NO_VENV
|
||||||
|
@echo "Creating our virtualenv"
|
||||||
|
${PYTHON} -m venv env
|
||||||
|
$(VENV_PYTHON) -m pip install -r requirements.txt
|
||||||
|
# We can't use the "--system-site-packages" flag on creation because otherwise we end up with
|
||||||
|
# the system's pip and that messes up things in some cases (notably in Gentoo).
|
||||||
|
${PYTHON} -m venv --upgrade ${VENV_OPTIONS} env
|
||||||
|
endif
|
||||||
|
|
||||||
|
build/help: | env
|
||||||
|
$(VENV_PYTHON) build.py --doc
|
||||||
|
|
||||||
|
qt/dg_rc.py: qt/dg.qrc
|
||||||
|
$(PYRCC5) qt/dg.qrc > qt/dg_rc.py
|
||||||
|
|
||||||
|
i18n: $(mofiles)
|
||||||
|
|
||||||
|
%.mo: %.po
|
||||||
|
msgfmt -o $@ $<
|
||||||
|
|
||||||
|
modules: | env
|
||||||
|
$(VENV_PYTHON) build.py --modules
|
||||||
|
|
||||||
|
mergepot: | env
|
||||||
|
$(VENV_PYTHON) build.py --mergepot
|
||||||
|
|
||||||
|
normpo: | env
|
||||||
|
$(VENV_PYTHON) build.py --normpo
|
||||||
|
|
||||||
|
install: all pyc
|
||||||
|
mkdir -p ${DESTDIR}${PREFIX}/share/dupeguru
|
||||||
|
cp -rf ${packages} locale ${DESTDIR}${PREFIX}/share/dupeguru
|
||||||
|
cp -f run.py ${DESTDIR}${PREFIX}/share/dupeguru/run.py
|
||||||
|
chmod 755 ${DESTDIR}${PREFIX}/share/dupeguru/run.py
|
||||||
|
mkdir -p ${DESTDIR}${PREFIX}/bin
|
||||||
|
ln -sf ${PREFIX}/share/dupeguru/run.py ${DESTDIR}${PREFIX}/bin/dupeguru
|
||||||
|
mkdir -p ${DESTDIR}${PREFIX}/share/applications
|
||||||
|
cp -f pkg/dupeguru.desktop ${DESTDIR}${PREFIX}/share/applications
|
||||||
|
mkdir -p ${DESTDIR}${PREFIX}/share/pixmaps
|
||||||
|
cp -f images/dgse_logo_128.png ${DESTDIR}${PREFIX}/share/pixmaps/dupeguru.png
|
||||||
|
|
||||||
|
installdocs: build/help
|
||||||
|
mkdir -p ${DESTDIR}${PREFIX}/share/dupeguru
|
||||||
|
cp -rf build/help ${DESTDIR}${PREFIX}/share/dupeguru
|
||||||
|
|
||||||
|
uninstall:
|
||||||
|
rm -rf "${DESTDIR}${PREFIX}/share/dupeguru"
|
||||||
|
rm -f "${DESTDIR}${PREFIX}/bin/dupeguru"
|
||||||
|
rm -f "${DESTDIR}${PREFIX}/share/applications/dupeguru.desktop"
|
||||||
|
rm -f "${DESTDIR}${PREFIX}/share/pixmaps/dupeguru.png"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf build
|
||||||
|
-rm locale/*/LC_MESSAGES/*.mo
|
||||||
|
-rm core/pe/*.$(SO) qt/pe/*.$(SO)
|
||||||
|
|
||||||
|
.PHONY: clean normpo mergepot modules i18n reqs run pyc install uninstall all
|
||||||
144
README.md
144
README.md
@@ -1,112 +1,104 @@
|
|||||||
# dupeGuru
|
# dupeGuru
|
||||||
|
|
||||||
[dupeGuru][dupeguru] is a cross-platform (Linux, OS X, Windows) GUI tool to find duplicate files in
|
[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
|
a system. It is 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
|
[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 Qt5.
|
is written in Objective-C and uses Cocoa. On Linux, it is written in Python and uses Qt5.
|
||||||
|
|
||||||
dupeGuru comes in 3 editions (standard, music and picture) which are all buildable from this same
|
The Cocoa UI of dupeGuru is hosted in a separate repo: https://github.com/arsenetar/dupeguru-cocoa
|
||||||
source tree. You choose the edition you want to build in a `configure.py` flag.
|
|
||||||
|
|
||||||
# Current status: People wanted
|
## Current status
|
||||||
|
Still looking for additional help especially with regards to:
|
||||||
|
* OSX maintenance: reproducing bugs & cocoa version, building package with Cocoa UI.
|
||||||
|
* Linux maintenance: reproducing bugs, maintaining PPA repository, Debian package.
|
||||||
|
* Translations: updating missing strings, transifex project at https://www.transifex.com/voltaicideas/dupeguru-1
|
||||||
|
* Documentation: keeping it up-to-date.
|
||||||
|
|
||||||
dupeGuru has currently only one maintainer, me. This is a dangerous situation that needs to be
|
## Contents of this folder
|
||||||
corrected.
|
|
||||||
|
|
||||||
The goal is to eventually have another active maintainer, but before we can get there, the project
|
|
||||||
needs more contributors. It is very much lacking on that side right now.
|
|
||||||
|
|
||||||
Whatever your skills, if you are remotely interestested in being a contributor, I'm interested in
|
|
||||||
mentoring you. If that's the case, please refer to [the open ticket on the subject][contrib-issue]
|
|
||||||
and let's get started.
|
|
||||||
|
|
||||||
# Contents of this folder
|
|
||||||
|
|
||||||
This folder contains the source for dupeGuru. Its documentation is in `help`, but is also
|
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:
|
[available online][documentation] in its built form. Here's how this source tree is organized:
|
||||||
|
|
||||||
* core: Contains the core logic code for dupeGuru. It's Python code.
|
* 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.
|
* qt: UI code for the Qt toolkit. It's written in Python and uses PyQt.
|
||||||
* images: Images used by the different UI codebases.
|
* images: Images used by the different UI codebases.
|
||||||
* pkg: Skeleton files required to create different packages
|
* pkg: Skeleton files required to create different packages
|
||||||
* help: Help document, written for Sphinx.
|
* help: Help document, written for Sphinx.
|
||||||
* locale: .po files for localisation.
|
* locale: .po files for localization.
|
||||||
|
|
||||||
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.
|
* 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.
|
* qtlib: A collection of helpers used across Qt UI codebases of HS applications.
|
||||||
|
|
||||||
# How to build dupeGuru from source
|
## How to build dupeGuru from source
|
||||||
|
|
||||||
## The very, very, very easy way
|
### Windows & macOS specific additional instructions
|
||||||
|
For windows instructions see the [Windows Instructions](Windows.md).
|
||||||
|
|
||||||
If you're on Linux or Mac, there's a bootstrap script that will make building very, very easy. There
|
For macos instructions (qt version) see the [macOS Instructions](macos.md).
|
||||||
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
|
### Prerequisites
|
||||||
|
* [Python 3.6+][python]
|
||||||
|
* PyQt5
|
||||||
|
|
||||||
and follow instructions from the script. You can then ignore the rest of the build documentation.
|
### System Setup
|
||||||
|
When running in a linux based environment the following system packages or equivalents are needed to build:
|
||||||
|
* python3-pyqt5
|
||||||
|
* pyqt5-dev-tools (on some systems, see note)
|
||||||
|
* python3-wheel (for hsaudiotag3k)
|
||||||
|
* python3-venv (only if using a virtual environment)
|
||||||
|
* python3-dev
|
||||||
|
* build-essential
|
||||||
|
|
||||||
## Prerequisites installation
|
|
||||||
|
|
||||||
Prerequisites are installed through `pip`. However, some of them are not "pip installable" and have
|
Note: On some linux systems pyrcc5 is not put on the path when installing python3-pyqt5, this will cause some issues with the resource files (and icons). These systems should have a respective pyqt5-dev-tools package, which should also be installed. The presence of pyrcc5 can be checked with `which pyrcc5`. Debian based systems need the extra package, and Arch does not.
|
||||||
to be installed manually.
|
|
||||||
|
|
||||||
* All systems: [Python 3.3+][python] and [setuptools][setuptools]
|
To create packages the following are also needed:
|
||||||
* Mac OS X: The last XCode to have the 10.7 SDK included. Python 3.4+.
|
* python3-setuptools
|
||||||
* Windows: Visual Studio 2010, [PyQt 5.0+][pyqt], [cx_Freeze][cxfreeze] and
|
* debhelper
|
||||||
[Advanced Installer][advinst] (you only need the last two if you want to create an installer)
|
|
||||||
|
|
||||||
On Ubuntu (14.04+), the apt-get command to install all pre-requisites is:
|
### Building with Make
|
||||||
|
dupeGuru comes with a makefile that can be used to build and run:
|
||||||
|
|
||||||
$ apt-get install python3-dev python3-pyqt5 pyqt5-dev-tools
|
$ make && make run
|
||||||
|
|
||||||
On Arch, it's:
|
### Building without Make
|
||||||
|
|
||||||
$ pacman -S python-pyqt5
|
$ cd <dupeGuru directory>
|
||||||
|
$ python3 -m venv --system-site-packages ./env
|
||||||
## Setting up the virtual environment
|
$ source ./env/bin/activate
|
||||||
|
$ pip install -r requirements.txt
|
||||||
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 (unless you use Python 3.4+), so it has to be installed
|
|
||||||
manually:
|
|
||||||
|
|
||||||
$ pyvenv --system-site-packages env
|
|
||||||
$ source env/bin/activate
|
|
||||||
$ python get-pip.py
|
|
||||||
|
|
||||||
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 build.py
|
||||||
$ python run.py
|
$ python run.py
|
||||||
|
|
||||||
You can also package dupeGuru into an installable package with:
|
### Generating Debian/Ubuntu package
|
||||||
|
To generate packages the extra requirements in requirements-extra.txt must be installed, the
|
||||||
|
steps are as follows:
|
||||||
|
|
||||||
|
$ cd <dupeGuru directory>
|
||||||
|
$ python3 -m venv --system-site-packages ./env
|
||||||
|
$ source ./env/bin/activate
|
||||||
|
$ pip install -r requirements.txt -r requirements-extra.txt
|
||||||
|
$ python build.py --clean
|
||||||
$ python package.py
|
$ python package.py
|
||||||
|
|
||||||
[dupeguru]: http://www.hardcoded.net/dupeguru/
|
This can be made a one-liner (once in the directory) as:
|
||||||
[cross-toolkit]: http://www.hardcoded.net/articles/cross-toolkit-software
|
|
||||||
[contrib-issue]: https://github.com/hsoft/dupeguru/issues/300
|
|
||||||
[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
|
|
||||||
|
|
||||||
|
$ bash -c "python3 -m venv --system-site-packages env && source env/bin/activate && pip install -r requirements.txt -r requirements-extra.txt && python build.py --clean && python package.py"
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
|
||||||
|
The complete test suite is run with [Tox 1.7+][tox]. If you have it installed system-wide, you
|
||||||
|
don't even need to set up a virtualenv. Just `cd` into the root project folder and run `tox`.
|
||||||
|
|
||||||
|
If you don't have Tox system-wide, install it in your virtualenv with `pip install tox` and then
|
||||||
|
run `tox`.
|
||||||
|
|
||||||
|
You can also run automated tests without Tox. Extra requirements for running tests are in
|
||||||
|
`requirements-extra.txt`. So, you can do `pip install -r requirements-extra.txt` inside your
|
||||||
|
virtualenv and then `py.test core hscommon`
|
||||||
|
|
||||||
|
[dupeguru]: https://dupeguru.voltaicideas.net/
|
||||||
|
[cross-toolkit]: http://www.hardcoded.net/articles/cross-toolkit-software
|
||||||
|
[documentation]: http://dupeguru.voltaicideas.net/help/en/
|
||||||
|
[python]: http://www.python.org/
|
||||||
|
[pyqt]: http://www.riverbankcomputing.com
|
||||||
|
[tox]: https://tox.readthedocs.org/en/latest/
|
||||||
|
|||||||
55
Windows.md
Normal file
55
Windows.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
## How to build dupeGuru for Windows
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Python 3.6+][python]
|
||||||
|
- [Visual Studio 2019][vs] or [Visual Studio Build Tools 2019][vsBuildTools] with the Windows 10 SDK
|
||||||
|
- [nsis][nsis] (for installer creation)
|
||||||
|
- [msys2][msys2] (for using makefile method)
|
||||||
|
|
||||||
|
NOTE: When installing Visual Studio or the Visual Studio Build Tools with the Windows 10 SDK on versions of Windows below 10 be sure to make sure that the Universal CRT is installed before installing Visual studio as noted in the [Windows 10 SDK Notes][win10sdk] and found at [KB2999226][KB2999226].
|
||||||
|
|
||||||
|
After installing python it is recommended to update setuptools before compiling packages. To update run (example is for python launcher and 3.8):
|
||||||
|
|
||||||
|
$ py -3.8 -m pip install --upgrade setuptools
|
||||||
|
|
||||||
|
More details on setting up python for compiling packages on windows can be found on the [python wiki][pythonWindowsCompilers] Take note of the required vc++ versions.
|
||||||
|
|
||||||
|
### With build.py (preferred)
|
||||||
|
To build with a different python version 3.6 vs 3.8 or 32 bit vs 64 bit specify that version instead of -3.8 to the `py` command below. If you want to build additional versions while keeping all virtual environments setup use a different location for each virtual environment.
|
||||||
|
|
||||||
|
$ cd <dupeGuru directory>
|
||||||
|
$ py -3.8 -m venv .\env
|
||||||
|
$ .\env\Scripts\activate
|
||||||
|
$ pip install -r requirements.txt
|
||||||
|
$ python build.py
|
||||||
|
$ python run.py
|
||||||
|
|
||||||
|
### With makefile
|
||||||
|
It is possible to build dupeGuru with the makefile on windows using a compatable POSIX environment. The following steps have been tested using [msys2][msys2]. Before running make:
|
||||||
|
1. Install msys2 or other POSIX environment
|
||||||
|
2. Install PyQt5 globally via pip
|
||||||
|
3. Use the respective console for msys2 it is `msys2 msys`
|
||||||
|
|
||||||
|
Then the following execution of the makefile should work. Pass the correct value for PYTHON to the makefile if not on the path as python3.
|
||||||
|
|
||||||
|
$ cd <dupeGuru directory>
|
||||||
|
$ make PYTHON='py -3.8'
|
||||||
|
$ make run
|
||||||
|
|
||||||
|
### Generate Windows Installer Packages
|
||||||
|
You need to use the respective x86 or x64 version of python to build the 32 bit and 64 bit versions. The build scripts will automatically detect the python architecture for you. When using build.py make sure the resulting python works before continuing to package.py. NOTE: package.py looks for the 'makensis' executable in the default location for a 64 bit windows system. The extra requirements need to be installed to run packaging: `pip install -r requirements-extra.txt`. Run the following in the respective virtual environment.
|
||||||
|
|
||||||
|
$ python package.py
|
||||||
|
|
||||||
|
### Running tests
|
||||||
|
The complete test suite can be run with tox just like on linux. NOTE: The extra requirements need to be installed to run unit tests: `pip install -r requirements-extra.txt`.
|
||||||
|
|
||||||
|
[python]: http://www.python.org/
|
||||||
|
[nsis]: http://nsis.sourceforge.net/Main_Page
|
||||||
|
[vs]: https://www.visualstudio.com/downloads/#visual-studio-community-2019
|
||||||
|
[vsBuildTools]: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2019
|
||||||
|
[win10sdk]: https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk
|
||||||
|
[KB2999226]: https://support.microsoft.com/en-us/help/2999226/update-for-universal-c-runtime-in-windows
|
||||||
|
[pythonWindowsCompilers]: https://wiki.python.org/moin/WindowsCompilers
|
||||||
|
[msys2]: http://www.msys2.org/
|
||||||
53
bootstrap.sh
53
bootstrap.sh
@@ -1,53 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
PYTHON=python3
|
|
||||||
command -v $PYTHON -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.
|
|
||||||
if ! $PYTHON -m venv env ; then
|
|
||||||
# We're probably under braindead Ubuntu 14.04 which completely messed up ensurepip.
|
|
||||||
# Work around it :(
|
|
||||||
echo "Ubuntu 14.04's version of Python 3.4 is braindead stupid, but we work around it anyway..."
|
|
||||||
$PYTHON -m venv --without-pip env
|
|
||||||
fi
|
|
||||||
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
|
|
||||||
if ! $PYTHON -m venv env --upgrade --system-site-packages ; then
|
|
||||||
# We're probably under v3.4.1 and experiencing http://bugs.python.org/issue21643
|
|
||||||
# Work around it.
|
|
||||||
echo "Oops, can't upgrade our venv. Trying to work around it."
|
|
||||||
rm env/lib64
|
|
||||||
$PYTHON -m venv env --upgrade --system-site-packages
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
source env/bin/activate
|
|
||||||
|
|
||||||
echo "Installing pip requirements"
|
|
||||||
if [ "$(uname)" == "Darwin" ]; then
|
|
||||||
pip install $PIPARGS -r requirements-osx.txt
|
|
||||||
else
|
|
||||||
python -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"
|
|
||||||
438
build.py
438
build.py
@@ -1,401 +1,179 @@
|
|||||||
# Created By: Virgil Dupras
|
# Copyright 2017 Virgil Dupras
|
||||||
# Created On: 2009-12-30
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
#
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
# This software is licensed under the "GPLv3" 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.gnu.org/licenses/gpl-3.0.html
|
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
import sys
|
import sys
|
||||||
import os
|
|
||||||
import os.path as op
|
|
||||||
from optparse import OptionParser
|
from optparse import OptionParser
|
||||||
import shutil
|
import shutil
|
||||||
import json
|
from multiprocessing import Pool
|
||||||
import importlib
|
|
||||||
import compileall
|
|
||||||
|
|
||||||
from setuptools import setup, Extension
|
|
||||||
|
|
||||||
|
from setuptools import sandbox
|
||||||
from hscommon import sphinxgen
|
from hscommon import sphinxgen
|
||||||
from hscommon.build import (
|
from hscommon.build import (
|
||||||
add_to_pythonpath, print_and_do, copy_packages, filereplace,
|
add_to_pythonpath,
|
||||||
get_module_version, move_all, copy_all, OSXAppStructure,
|
print_and_do,
|
||||||
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
|
fix_qt_resource_file,
|
||||||
collect_stdlib_dependencies, copy
|
|
||||||
)
|
)
|
||||||
from hscommon import loc
|
from hscommon import loc
|
||||||
from hscommon.plat import ISOSX, ISLINUX
|
|
||||||
from hscommon.util import ensure_folder, delete_files_with_pattern
|
|
||||||
|
|
||||||
def parse_args():
|
def parse_args():
|
||||||
usage = "usage: %prog [options]"
|
usage = "usage: %prog [options]"
|
||||||
parser = OptionParser(usage=usage)
|
parser = OptionParser(usage=usage)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'--clean', action='store_true', dest='clean',
|
"--clean",
|
||||||
help="Clean build folder before building"
|
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 (en)")
|
||||||
|
parser.add_option("--alldoc", action="store_true", dest="all_doc", help="Build only the help file in all languages")
|
||||||
|
parser.add_option("--loc", action="store_true", dest="loc", help="Build only localization")
|
||||||
|
parser.add_option(
|
||||||
|
"--updatepot",
|
||||||
|
action="store_true",
|
||||||
|
dest="updatepot",
|
||||||
|
help="Generate .pot files from source code.",
|
||||||
)
|
)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'--doc', action='store_true', dest='doc',
|
"--mergepot",
|
||||||
help="Build only the help file"
|
action="store_true",
|
||||||
|
dest="mergepot",
|
||||||
|
help="Update all .po files based on .pot files.",
|
||||||
)
|
)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'--loc', action='store_true', dest='loc',
|
"--normpo",
|
||||||
help="Build only localization"
|
action="store_true",
|
||||||
|
dest="normpo",
|
||||||
|
help="Normalize all PO files (do this before commit).",
|
||||||
)
|
)
|
||||||
parser.add_option(
|
parser.add_option(
|
||||||
'--cocoa-ext', action='store_true', dest='cocoa_ext',
|
"--modules",
|
||||||
help="Build only Cocoa extensions"
|
action="store_true",
|
||||||
)
|
dest="modules",
|
||||||
parser.add_option(
|
help="Build the python modules.",
|
||||||
'--cocoa-compile', action='store_true', dest='cocoa_compile',
|
|
||||||
help="Build only Cocoa executable"
|
|
||||||
)
|
|
||||||
parser.add_option(
|
|
||||||
'--xibless', action='store_true', dest='xibless',
|
|
||||||
help="Build only xibless UIs"
|
|
||||||
)
|
|
||||||
parser.add_option(
|
|
||||||
'--updatepot', action='store_true', dest='updatepot',
|
|
||||||
help="Generate .pot files from source code."
|
|
||||||
)
|
|
||||||
parser.add_option(
|
|
||||||
'--mergepot', action='store_true', dest='mergepot',
|
|
||||||
help="Update all .po files based on .pot files."
|
|
||||||
)
|
|
||||||
parser.add_option(
|
|
||||||
'--normpo', action='store_true', dest='normpo',
|
|
||||||
help="Normalize all PO files (do this before commit)."
|
|
||||||
)
|
)
|
||||||
(options, args) = parser.parse_args()
|
(options, args) = parser.parse_args()
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def cocoa_compile_command(edition):
|
|
||||||
return '{0} waf configure --edition {1} && {0} waf'.format(sys.executable, edition)
|
|
||||||
|
|
||||||
def cocoa_app(edition):
|
def build_one_help(language):
|
||||||
app_path = {
|
print("Generating Help in {}".format(language))
|
||||||
'se': 'build/dupeGuru.app',
|
current_path = Path(".").absolute()
|
||||||
'me': 'build/dupeGuru ME.app',
|
changelog_path = current_path.joinpath("help", "changelog")
|
||||||
'pe': 'build/dupeGuru PE.app',
|
tixurl = "https://github.com/arsenetar/dupeguru/issues/{}"
|
||||||
}[edition]
|
changelogtmpl = current_path.joinpath("help", "changelog.tmpl")
|
||||||
return OSXAppStructure(app_path)
|
conftmpl = current_path.joinpath("help", "conf.tmpl")
|
||||||
|
help_basepath = current_path.joinpath("help", language)
|
||||||
def build_xibless(edition, dest='cocoa/autogen'):
|
help_destpath = current_path.joinpath("build", "help", language)
|
||||||
import xibless
|
confrepl = {"language": language}
|
||||||
ensure_folder(dest)
|
sphinxgen.gen(
|
||||||
FNPAIRS = [
|
help_basepath,
|
||||||
('ignore_list_dialog.py', 'IgnoreListDialog_UI'),
|
help_destpath,
|
||||||
('deletion_options.py', 'DeletionOptions_UI'),
|
changelog_path,
|
||||||
('problem_dialog.py', 'ProblemDialog_UI'),
|
tixurl,
|
||||||
('directory_panel.py', 'DirectoryPanel_UI'),
|
confrepl,
|
||||||
('prioritize_dialog.py', 'PrioritizeDialog_UI'),
|
conftmpl,
|
||||||
('result_window.py', 'ResultWindow_UI'),
|
changelogtmpl,
|
||||||
('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', '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):
|
def build_help():
|
||||||
print("Building localizations")
|
languages = ["en", "de", "fr", "hy", "ru", "uk"]
|
||||||
build_localizations('qt', edition)
|
# Running with Pools as for some reason sphinx seems to cross contaminate the output otherwise
|
||||||
print("Building Qt stuff")
|
with Pool(len(languages)) as p:
|
||||||
print_and_do("pyrcc5 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
|
p.map(build_one_help, languages)
|
||||||
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():
|
def build_qt_localizations():
|
||||||
loc.compile_all_po(op.join('qtlib', 'locale'))
|
loc.compile_all_po(Path("qtlib", "locale"))
|
||||||
loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale')
|
loc.merge_locale_dir(Path("qtlib", "locale"), "locale")
|
||||||
|
|
||||||
def build_localizations(ui, edition):
|
|
||||||
loc.compile_all_po('locale')
|
def build_localizations():
|
||||||
if ui == 'cocoa':
|
loc.compile_all_po("locale")
|
||||||
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()
|
build_qt_localizations()
|
||||||
locale_dest = op.join('build', 'locale')
|
locale_dest = Path("build", "locale")
|
||||||
if op.exists(locale_dest):
|
if locale_dest.exists():
|
||||||
shutil.rmtree(locale_dest)
|
shutil.rmtree(locale_dest)
|
||||||
shutil.copytree('locale', locale_dest, ignore=shutil.ignore_patterns('*.po', '*.pot'))
|
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():
|
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 .pot files from source files")
|
||||||
print("Building core.pot")
|
print("Building core.pot")
|
||||||
all_cores = ['core', 'core_se', 'core_me', 'core_pe']
|
loc.generate_pot(["core"], Path("locale", "core.pot"), ["tr"])
|
||||||
loc.generate_pot(all_cores, op.join('locale', 'core.pot'), ['tr'])
|
|
||||||
print("Building columns.pot")
|
print("Building columns.pot")
|
||||||
loc.generate_pot(all_cores, op.join('locale', 'columns.pot'), ['coltr'])
|
loc.generate_pot(["core"], Path("locale", "columns.pot"), ["coltr"])
|
||||||
print("Building ui.pot")
|
print("Building ui.pot")
|
||||||
# When we're not under OS X, we don't want to overwrite ui.pot because it contains Cocoa locs
|
# 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.
|
# We want to merge the generated pot with the old pot in the most preserving way possible.
|
||||||
ui_packages = ['qt', op.join('cocoa', 'inter')]
|
ui_packages = ["qt", Path("cocoa", "inter")]
|
||||||
loc.generate_pot(ui_packages, op.join('locale', 'ui.pot'), ['tr'], merge=(not ISOSX))
|
loc.generate_pot(ui_packages, Path("locale", "ui.pot"), ["tr"], merge=True)
|
||||||
print("Building qtlib.pot")
|
print("Building qtlib.pot")
|
||||||
loc.generate_pot(['qtlib'], op.join('qtlib', 'locale', 'qtlib.pot'), ['tr'])
|
loc.generate_pot(["qtlib"], Path("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():
|
def build_mergepot():
|
||||||
print("Updating .po files using .pot files")
|
print("Updating .po files using .pot files")
|
||||||
loc.merge_pots_into_pos('locale')
|
loc.merge_pots_into_pos("locale")
|
||||||
loc.merge_pots_into_pos(op.join('qtlib', 'locale'))
|
loc.merge_pots_into_pos(Path("qtlib", "locale"))
|
||||||
loc.merge_pots_into_pos(op.join('cocoalib', 'locale'))
|
# loc.merge_pots_into_pos(Path("cocoalib", "locale"))
|
||||||
|
|
||||||
|
|
||||||
def build_normpo():
|
def build_normpo():
|
||||||
loc.normalize_all_pos('locale')
|
loc.normalize_all_pos("locale")
|
||||||
loc.normalize_all_pos(op.join('qtlib', 'locale'))
|
loc.normalize_all_pos(Path("qtlib", "locale"))
|
||||||
loc.normalize_all_pos(op.join('cocoalib', 'locale'))
|
# loc.normalize_all_pos(Path("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):
|
def build_pe_modules():
|
||||||
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")
|
print("Building PE Modules")
|
||||||
exts = [
|
# Leverage setup.py to build modules
|
||||||
Extension("_block", [op.join('core_pe', 'modules', 'block.c'), op.join('core_pe', 'modules', 'common.c')]),
|
sandbox.run_setup("setup.py", ["build_ext", "--inplace"])
|
||||||
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))
|
def build_normal():
|
||||||
add_to_pythonpath('.')
|
print("Building dupeGuru with UI qt")
|
||||||
|
add_to_pythonpath(".")
|
||||||
print("Building dupeGuru")
|
print("Building dupeGuru")
|
||||||
if edition == 'pe':
|
build_pe_modules()
|
||||||
build_pe_modules(ui)
|
print("Building localizations")
|
||||||
if ui == 'cocoa':
|
build_localizations()
|
||||||
build_cocoa(edition, dev)
|
print("Building Qt stuff")
|
||||||
elif ui == 'qt':
|
print_and_do("pyrcc5 {0} > {1}".format(Path("qt", "dg.qrc"), Path("qt", "dg_rc.py")))
|
||||||
build_qt(edition, dev, conf)
|
fix_qt_resource_file(Path("qt", "dg_rc.py"))
|
||||||
|
build_help()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
if sys.version_info < (3, 6):
|
||||||
|
sys.exit("Python < 3.6 is unsupported.")
|
||||||
options = parse_args()
|
options = parse_args()
|
||||||
conf = json.load(open('conf.json'))
|
if options.clean and Path("build").exists():
|
||||||
edition = conf['edition']
|
shutil.rmtree("build")
|
||||||
ui = conf['ui']
|
if not Path("build").exists():
|
||||||
dev = conf['dev']
|
Path("build").mkdir()
|
||||||
if dev:
|
|
||||||
print("Building in Dev mode")
|
|
||||||
if options.clean:
|
|
||||||
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
|
|
||||||
if op.exists(path):
|
|
||||||
shutil.rmtree(path)
|
|
||||||
if not op.exists('build'):
|
|
||||||
os.mkdir('build')
|
|
||||||
if options.doc:
|
if options.doc:
|
||||||
build_help(edition)
|
build_one_help("en")
|
||||||
|
elif options.all_doc:
|
||||||
|
build_help()
|
||||||
elif options.loc:
|
elif options.loc:
|
||||||
build_localizations(ui, edition)
|
build_localizations()
|
||||||
elif options.updatepot:
|
elif options.updatepot:
|
||||||
build_updatepot()
|
build_updatepot()
|
||||||
elif options.mergepot:
|
elif options.mergepot:
|
||||||
build_mergepot()
|
build_mergepot()
|
||||||
elif options.normpo:
|
elif options.normpo:
|
||||||
build_normpo()
|
build_normpo()
|
||||||
elif options.cocoa_ext:
|
elif options.modules:
|
||||||
build_cocoa_proxy_module()
|
build_pe_modules()
|
||||||
build_cocoa_bridging_interfaces(edition)
|
|
||||||
elif options.cocoa_compile:
|
|
||||||
os.chdir('cocoa')
|
|
||||||
print_and_do(cocoa_compile_command(edition))
|
|
||||||
os.chdir('..')
|
|
||||||
cocoa_app(edition).copy_executable('cocoa/build/dupeGuru')
|
|
||||||
elif options.xibless:
|
|
||||||
build_cocoalib_xibless()
|
|
||||||
build_xibless(edition)
|
|
||||||
else:
|
else:
|
||||||
build_normal(edition, ui, dev, conf)
|
build_normal()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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,19 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define JobStarted @"JobStarted"
|
|
||||||
#define JobInProgress @"JobInProgress"
|
|
||||||
#define TableFontSize @"TableFontSize"
|
|
||||||
|
|
||||||
#define jobLoad @"job_load"
|
|
||||||
#define jobScan @"job_scan"
|
|
||||||
#define jobCopy @"job_copy"
|
|
||||||
#define jobMove @"job_move"
|
|
||||||
#define jobDelete @"job_delete"
|
|
||||||
|
|
||||||
#define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType"
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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,31 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import <Python.h>
|
|
||||||
#import "PyDetailsPanel.h"
|
|
||||||
|
|
||||||
@interface DetailsPanelBase : NSWindowController <NSTableViewDataSource>
|
|
||||||
{
|
|
||||||
NSTableView *detailsTable;
|
|
||||||
|
|
||||||
PyDetailsPanel *model;
|
|
||||||
}
|
|
||||||
|
|
||||||
@property (readwrite, retain) NSTableView *detailsTable;
|
|
||||||
|
|
||||||
- (id)initWithPyRef:(PyObject *)aPyRef;
|
|
||||||
- (PyDetailsPanel *)model;
|
|
||||||
|
|
||||||
- (NSWindow *)createWindow;
|
|
||||||
- (BOOL)isVisible;
|
|
||||||
- (void)toggleVisibility;
|
|
||||||
|
|
||||||
/* Python --> Cocoa */
|
|
||||||
- (void)refresh;
|
|
||||||
@end
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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;
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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,51 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "HSOutlineView.h"
|
|
||||||
#import "HSRecentFiles.h"
|
|
||||||
#import "DirectoryOutline.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@property (readwrite, retain) NSPopUpButton *addButtonPopUp;
|
|
||||||
@property (readwrite, retain) NSPopUpButton *loadRecentButtonPopUp;
|
|
||||||
@property (readwrite, retain) HSOutlineView *outlineView;
|
|
||||||
@property (readwrite, retain) NSButton *removeButton;
|
|
||||||
@property (readwrite, retain) NSButton *loadResultsButton;
|
|
||||||
|
|
||||||
- (id)initWithParentApp:(AppDelegateBase *)aParentApp;
|
|
||||||
|
|
||||||
- (void)fillPopUpMenu; // Virtual
|
|
||||||
- (void)adjustUIToLocalization;
|
|
||||||
|
|
||||||
- (void)askForDirectory;
|
|
||||||
- (void)popupAddDirectoryMenu:(id)sender;
|
|
||||||
- (void)popupLoadRecentMenu:(id)sender;
|
|
||||||
- (void)removeSelectedDirectory;
|
|
||||||
|
|
||||||
- (void)addDirectory:(NSString *)directory;
|
|
||||||
- (void)refreshRemoveButtonText;
|
|
||||||
- (void)markAll;
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
#import "DirectoryPanel_UI.h"
|
|
||||||
#import "Dialogs.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
|
|
||||||
@implementation DirectoryPanel
|
|
||||||
|
|
||||||
@synthesize addButtonPopUp;
|
|
||||||
@synthesize loadRecentButtonPopUp;
|
|
||||||
@synthesize outlineView;
|
|
||||||
@synthesize removeButton;
|
|
||||||
@synthesize loadResultsButton;
|
|
||||||
|
|
||||||
- (id)initWithParentApp:(AppDelegateBase *)aParentApp
|
|
||||||
{
|
|
||||||
self = [super initWithWindow:nil];
|
|
||||||
[self setWindow:createDirectoryPanel_UI(self)];
|
|
||||||
_app = aParentApp;
|
|
||||||
model = [_app model];
|
|
||||||
[[self window] setTitle:[model appName]];
|
|
||||||
_alwaysShowPopUp = NO;
|
|
||||||
[self fillPopUpMenu];
|
|
||||||
_recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]];
|
|
||||||
[_recentDirectories setDelegate:self];
|
|
||||||
outline = [[DirectoryOutline alloc] initWithPyRef:[model directoryTree] outlineView:outlineView];
|
|
||||||
[self refreshRemoveButtonText];
|
|
||||||
[self adjustUIToLocalization];
|
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:)
|
|
||||||
name:NSOutlineViewSelectionDidChangeNotification object:outlineView];
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(outlineAddedFolders:)
|
|
||||||
name:DGAddedFoldersNotification object:outline];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[outline release];
|
|
||||||
[_recentDirectories release];
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Private */
|
|
||||||
|
|
||||||
- (void)fillPopUpMenu
|
|
||||||
{
|
|
||||||
NSMenu *m = [addButtonPopUp menu];
|
|
||||||
NSMenuItem *mi = [m addItemWithTitle: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 */
|
|
||||||
|
|
||||||
- (void)askForDirectory
|
|
||||||
{
|
|
||||||
NSOpenPanel *op = [NSOpenPanel openPanel];
|
|
||||||
[op setCanChooseFiles:YES];
|
|
||||||
[op setCanChooseDirectories:YES];
|
|
||||||
[op setAllowsMultipleSelection:YES];
|
|
||||||
[op setTitle:NSLocalizedString(@"Select a folder to add to the scanning list", @"")];
|
|
||||||
[op setDelegate:self];
|
|
||||||
if ([op runModal] == NSOKButton) {
|
|
||||||
for (NSURL *directoryURL in [op URLs]) {
|
|
||||||
[self addDirectory:[directoryURL path]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)popupAddDirectoryMenu:(id)sender
|
|
||||||
{
|
|
||||||
if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) {
|
|
||||||
[self askForDirectory];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[addButtonPopUp selectItem:nil];
|
|
||||||
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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];
|
|
||||||
[[outline model] removeSelectedDirectory];
|
|
||||||
[self refreshRemoveButtonText];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Public */
|
|
||||||
- (void)addDirectory:(NSString *)directory
|
|
||||||
{
|
|
||||||
[model addDirectory:directory];
|
|
||||||
[_recentDirectories addFile:directory];
|
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)refreshRemoveButtonText
|
|
||||||
{
|
|
||||||
if ([outlineView selectedRow] < 0) {
|
|
||||||
[removeButton setEnabled:NO];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[removeButton setEnabled:YES];
|
|
||||||
NSIndexPath *path = [outline selectedIndexPath];
|
|
||||||
if (path != nil) {
|
|
||||||
NSInteger state = [outline intProperty:@"state" valueAtPath:path];
|
|
||||||
BOOL shouldDisplayArrow = ([path length] > 1) && (state == 2);
|
|
||||||
NSString *imgName = shouldDisplayArrow ? @"NSGoLeftTemplate" : @"NSRemoveTemplate";
|
|
||||||
[removeButton setImage:[NSImage imageNamed:imgName]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (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 */
|
|
||||||
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
|
|
||||||
{
|
|
||||||
BOOL isdir;
|
|
||||||
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&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 */
|
|
||||||
|
|
||||||
- (void)directorySelectionChanged:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
[self refreshRemoveButtonText];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)outlineAddedFolders:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSArray *foldernames = [[aNotification userInfo] objectForKey:@"foldernames"];
|
|
||||||
for (NSString *foldername in foldernames) {
|
|
||||||
[_recentDirectories addFile:foldername];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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;
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "HSSelectableList.h"
|
|
||||||
#import "PyPrioritizeList.h"
|
|
||||||
|
|
||||||
@interface PrioritizeList : HSSelectableList {}
|
|
||||||
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView;
|
|
||||||
- (PyPrioritizeList *)model;
|
|
||||||
@end
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "PrioritizeList.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation PrioritizeList
|
|
||||||
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView
|
|
||||||
{
|
|
||||||
self = [super initWithPyRef:aPyRef wrapperClass:[PyPrioritizeList class]
|
|
||||||
callbackClassName:@"PrioritizeListView" view:aTableView];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (PyPrioritizeList *)model
|
|
||||||
{
|
|
||||||
return (PyPrioritizeList *)model;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setView:(NSTableView *)aTableView
|
|
||||||
{
|
|
||||||
[super setView:aTableView];
|
|
||||||
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
|
|
||||||
{
|
|
||||||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
|
|
||||||
[pboard declareTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType] owner:self];
|
|
||||||
[pboard setData:data forType:DGPrioritizeIndexPasteboardType];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row
|
|
||||||
proposedDropOperation:(NSTableViewDropOperation)op
|
|
||||||
{
|
|
||||||
if (op == NSTableViewDropAbove) {
|
|
||||||
return NSDragOperationMove;
|
|
||||||
}
|
|
||||||
return NSDragOperationNone;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info
|
|
||||||
row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
|
|
||||||
{
|
|
||||||
NSPasteboard* pboard = [info draggingPasteboard];
|
|
||||||
NSData* rowData = [pboard dataForType:DGPrioritizeIndexPasteboardType];
|
|
||||||
NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
|
|
||||||
[[self model] moveIndexes:[Utils indexSet2Array:rowIndexes] toIndex:row];
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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,23 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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;
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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,85 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,352 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "ResultWindowBase.h"
|
|
||||||
#import "ResultWindow_UI.h"
|
|
||||||
#import "Dialogs.h"
|
|
||||||
#import "ProgressController.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
#import "PrioritizeDialog.h"
|
|
||||||
|
|
||||||
@implementation ResultWindowBase
|
|
||||||
|
|
||||||
@synthesize optionsSwitch;
|
|
||||||
@synthesize optionsToolbarItem;
|
|
||||||
@synthesize matches;
|
|
||||||
@synthesize stats;
|
|
||||||
@synthesize filterField;
|
|
||||||
|
|
||||||
- (id)initWithParentApp:(AppDelegateBase *)aApp;
|
|
||||||
{
|
|
||||||
self = [super initWithWindow:nil];
|
|
||||||
app = aApp;
|
|
||||||
model = [app model];
|
|
||||||
[self setWindow:createResultWindow_UI(self)];
|
|
||||||
[[self window] setTitle:fmt(NSLocalizedString(@"%@ Results", @""), [model appName])];
|
|
||||||
/* Put a cute iTunes-like bottom bar */
|
|
||||||
[[self window] setContentBorderThickness:28 forEdge:NSMinYEdge];
|
|
||||||
table = [[ResultTable alloc] initWithPyRef:[model resultTable] view:matches];
|
|
||||||
statsLabel = [[StatsLabel alloc] initWithPyRef:[model statsLabel] view:stats];
|
|
||||||
problemDialog = [[ProblemDialog alloc] initWithPyRef:[model problemDialog]];
|
|
||||||
deletionOptions = [[DeletionOptions alloc] initWithPyRef:[model deletionOptions]];
|
|
||||||
[self initResultColumns];
|
|
||||||
[[table columns] setColumnsAsReadOnly];
|
|
||||||
[self fillColumnsMenu];
|
|
||||||
[matches setTarget:self];
|
|
||||||
[matches setDoubleAction:@selector(openClicked)];
|
|
||||||
[self adjustUIToLocalization];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[table release];
|
|
||||||
[statsLabel release];
|
|
||||||
[problemDialog release];
|
|
||||||
[super dealloc];
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Virtual */
|
|
||||||
- (void)initResultColumns
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)setScanOptions
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Helpers */
|
|
||||||
- (void)fillColumnsMenu
|
|
||||||
{
|
|
||||||
[[app columnsMenu] removeAllItems];
|
|
||||||
NSArray *menuItems = [[[table columns] model] menuItems];
|
|
||||||
for (NSInteger i=0; i < [menuItems count]; i++) {
|
|
||||||
NSArray *pair = [menuItems objectAtIndex:i];
|
|
||||||
NSString *display = [pair objectAtIndex:0];
|
|
||||||
BOOL marked = n2b([pair objectAtIndex:1]);
|
|
||||||
NSMenuItem *mi = [[app columnsMenu] addItemWithTitle:display action:@selector(toggleColumn:) keyEquivalent:@""];
|
|
||||||
[mi setTarget:self];
|
|
||||||
[mi setState:marked ? NSOnState : NSOffState];
|
|
||||||
[mi setTag:i];
|
|
||||||
}
|
|
||||||
[[app columnsMenu] addItem:[NSMenuItem separatorItem]];
|
|
||||||
NSMenuItem *mi = [[app columnsMenu] addItemWithTitle:NSLocalizedString(@"Reset to Default", @"")
|
|
||||||
action:@selector(resetColumnsToDefault) keyEquivalent:@""];
|
|
||||||
[mi setTarget:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)updateOptionSegments
|
|
||||||
{
|
|
||||||
[optionsSwitch setSelected:[[app detailsPanel] isVisible] forSegment:0];
|
|
||||||
[optionsSwitch setSelected:[table powerMarkerMode] forSegment:1];
|
|
||||||
[optionsSwitch setSelected:[table deltaValuesMode] forSegment:2];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)showProblemDialog
|
|
||||||
{
|
|
||||||
[problemDialog showWindow:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)adjustUIToLocalization
|
|
||||||
{
|
|
||||||
NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
|
|
||||||
NSInteger seg1delta = 0;
|
|
||||||
NSInteger seg2delta = 0;
|
|
||||||
if ([lang isEqual:@"ru"]) {
|
|
||||||
seg2delta = 20;
|
|
||||||
}
|
|
||||||
else if ([lang isEqual:@"uk"]) {
|
|
||||||
seg2delta = 20;
|
|
||||||
}
|
|
||||||
else if ([lang isEqual:@"hy"]) {
|
|
||||||
seg1delta = 20;
|
|
||||||
}
|
|
||||||
if (seg1delta || seg2delta) {
|
|
||||||
[optionsSwitch setWidth:[optionsSwitch widthForSegment:0]+seg1delta forSegment:0];
|
|
||||||
[optionsSwitch setWidth:[optionsSwitch widthForSegment:1]+seg2delta forSegment:1];
|
|
||||||
NSSize s = [optionsToolbarItem maxSize];
|
|
||||||
s.width += seg1delta + seg2delta;
|
|
||||||
[optionsToolbarItem setMaxSize:s];
|
|
||||||
[optionsToolbarItem setMinSize:s];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Actions */
|
|
||||||
- (void)changeOptions
|
|
||||||
{
|
|
||||||
NSInteger seg = [optionsSwitch selectedSegment];
|
|
||||||
if (seg == 0) {
|
|
||||||
[self toggleDetailsPanel];
|
|
||||||
}
|
|
||||||
else if (seg == 1) {
|
|
||||||
[self togglePowerMarker];
|
|
||||||
}
|
|
||||||
else if (seg == 2) {
|
|
||||||
[self toggleDelta];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)copyMarked
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
|
||||||
[model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])];
|
|
||||||
[model copyMarked];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)trashMarked
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
|
||||||
[model deleteMarked];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)filter
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[model setEscapeFilterRegexp:!n2b([ud objectForKey:@"useRegexpFilter"])];
|
|
||||||
[model applyFilter:[filterField stringValue]];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)focusOnFilterField
|
|
||||||
{
|
|
||||||
[[self window] makeFirstResponder:filterField];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)ignoreSelected
|
|
||||||
{
|
|
||||||
[model addSelectedToIgnoreList];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)invokeCustomCommand
|
|
||||||
{
|
|
||||||
[model invokeCustomCommand];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)markAll
|
|
||||||
{
|
|
||||||
[model markAll];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)markInvert
|
|
||||||
{
|
|
||||||
[model markInvert];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)markNone
|
|
||||||
{
|
|
||||||
[model markNone];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)markSelected
|
|
||||||
{
|
|
||||||
[model toggleSelectedMark];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)moveMarked
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])];
|
|
||||||
[model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])];
|
|
||||||
[model moveMarked];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)openClicked
|
|
||||||
{
|
|
||||||
if ([matches clickedRow] < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[matches selectRowIndexes:[NSIndexSet indexSetWithIndex:[matches clickedRow]] byExtendingSelection:NO];
|
|
||||||
[model openSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)openSelected
|
|
||||||
{
|
|
||||||
[model openSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeMarked
|
|
||||||
{
|
|
||||||
[model removeMarked];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeSelected
|
|
||||||
{
|
|
||||||
[model removeSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)renameSelected
|
|
||||||
{
|
|
||||||
NSInteger col = [matches columnWithIdentifier:@"name"];
|
|
||||||
NSInteger row = [matches selectedRow];
|
|
||||||
[matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)reprioritizeResults
|
|
||||||
{
|
|
||||||
PrioritizeDialog *dlg = [[PrioritizeDialog alloc] initWithApp:model];
|
|
||||||
NSInteger result = [NSApp runModalForWindow:[dlg window]];
|
|
||||||
if (result == NSRunStoppedResponse) {
|
|
||||||
[[dlg model] performReprioritization];
|
|
||||||
}
|
|
||||||
[dlg release];
|
|
||||||
[[self window] makeKeyAndOrderFront:nil];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)resetColumnsToDefault
|
|
||||||
{
|
|
||||||
[[[table columns] model] resetToDefaults];
|
|
||||||
[self fillColumnsMenu];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)revealSelected
|
|
||||||
{
|
|
||||||
[model revealSelected];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)saveResults
|
|
||||||
{
|
|
||||||
NSSavePanel *sp = [NSSavePanel savePanel];
|
|
||||||
[sp setCanCreateDirectories:YES];
|
|
||||||
[sp setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
|
|
||||||
[sp setTitle:NSLocalizedString(@"Select a file to save your results to", @"")];
|
|
||||||
if ([sp runModal] == NSOKButton) {
|
|
||||||
[model saveResultsAs:[[sp URL] path]];
|
|
||||||
[[app recentResults] addFile:[[sp URL] path]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)startDuplicateScan
|
|
||||||
{
|
|
||||||
if ([model resultsAreModified]) {
|
|
||||||
if ([Dialogs askYesNo:NSLocalizedString(@"You have unsaved results, do you really want to continue?", @"")] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
[self setScanOptions];
|
|
||||||
[model doScan];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)switchSelected
|
|
||||||
{
|
|
||||||
[model makeSelectedReference];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)toggleColumn:(id)sender
|
|
||||||
{
|
|
||||||
NSMenuItem *mi = sender;
|
|
||||||
BOOL checked = [[[table columns] model] toggleMenuItem:[mi tag]];
|
|
||||||
[mi setState:checked ? NSOnState : NSOffState];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)toggleDetailsPanel
|
|
||||||
{
|
|
||||||
[[app detailsPanel] toggleVisibility];
|
|
||||||
[self updateOptionSegments];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)toggleDelta
|
|
||||||
{
|
|
||||||
[table setDeltaValuesMode:![table deltaValuesMode]];
|
|
||||||
[self updateOptionSegments];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)togglePowerMarker
|
|
||||||
{
|
|
||||||
[table setPowerMarkerMode:![table powerMarkerMode]];
|
|
||||||
[self updateOptionSegments];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)toggleQuicklookPanel
|
|
||||||
{
|
|
||||||
if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) {
|
|
||||||
[[QLPreviewPanel sharedPreviewPanel] orderOut:nil];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
[[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Quicklook */
|
|
||||||
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel;
|
|
||||||
{
|
|
||||||
return YES;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel
|
|
||||||
{
|
|
||||||
// This document is now responsible of the preview panel
|
|
||||||
// It is allowed to set the delegate, data source and refresh panel.
|
|
||||||
previewPanel = [panel retain];
|
|
||||||
panel.delegate = table;
|
|
||||||
panel.dataSource = table;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)endPreviewPanelControl:(QLPreviewPanel *)panel
|
|
||||||
{
|
|
||||||
// This document loses its responsisibility on the preview panel
|
|
||||||
// Until the next call to -beginPreviewPanelControl: it must not
|
|
||||||
// change the panel's delegate, data source or refresh it.
|
|
||||||
[previewPanel release];
|
|
||||||
previewPanel = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
|
|
||||||
{
|
|
||||||
return ![[ProgressController mainProgressController] isShown];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (BOOL)validateMenuItem:(NSMenuItem *)item
|
|
||||||
{
|
|
||||||
if ([item action] == @selector(markAll)) {
|
|
||||||
[item setTitle:NSLocalizedString(@"Mark All", @"")];
|
|
||||||
}
|
|
||||||
return ![[ProgressController mainProgressController] isShown];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
-----BEGIN PUBLIC KEY-----
|
|
||||||
MIIDOjCCAi0GByqGSM44BAEwggIgAoIBAQDSurIL+HKbw+jsppG6tp3+WOcA4W71
|
|
||||||
nhwR/DD2Se076AtCXJcssAhuCDUm+AVkQ3l34D++aYWtLR575rCrwU4lZXfQe+b9
|
|
||||||
plHK02oOuqAY8lO5y02xoHEh7XeGunZ0u8wOVZw8MI999vIJ8rtCdvIF3r26wkjx
|
|
||||||
9sieSxVpzJHDV5JHVdK3ObkXp/ts99dOD5B3CWGS8UiroMgS0FmRl7uPuADRRn2G
|
|
||||||
srHTBYMwJvq8HFzQmDxcLldGQMAKvRKchtH+nH6ci1unSnpDUyrsCd+7qv1cSTse
|
|
||||||
qc4OgXBDQ94MfVEh6Bs0S9stYfJf8cp6iV18J0sqMb9rbP4qC56iBsXfAhUAj6tx
|
|
||||||
gwima7VaNI4YiC69jpLod3MCggEAYx+/mbU8P/xGooV9MgA3nI2v2vVNkwZVFcPa
|
|
||||||
ROLQHg+R7bAftF3+1M9AnSP2O+PnXL65DwyTOab/Z/zM/vof3LLCGLYCmzPL+xvB
|
|
||||||
6PxlqO374kFsKHEaaw66nnFWzPSdks/il0rauAiEbO8Gn/a8F2HFdA/OCCzq83l6
|
|
||||||
cOhya7kGXZxdjeIfpfiNjDqZXi+8VRNDcDXx5u/T4vpkliQ+4O8ZXjwE4z2dPHfu
|
|
||||||
Bw/N7DUalkzhZygYqcgx3tUxu3x/Pso+inmIBbk/As0uZv2nEll2CkEI6CSJIpfn
|
|
||||||
pLKNQb4E4G7h+u+8kfHcwQ59RU1uGh0PU5uM+DOPg6HsC41RwgOCAQUAAoIBABLY
|
|
||||||
T8gN8KdxWheESorvgksdG+Fizhkafpac08MCwJFF24v5a8AvZbhcCMLhChrloKcQ
|
|
||||||
19qHshRIuWbSma/OqCmQKH752PTOKxRKsmqAfO0Rej2aDJrd0s7YBMY72DqeSYPP
|
|
||||||
peLlwv0gkgRW7/EbDvBI18iTbrQLZtdqs9Xajc3dyIG5wrMtAf/Gta2oWChHlBLZ
|
|
||||||
S45++Y9ou+LtW7dMc7c+aTxbzeLG36S57kAenRzjfP8zOi3P+Cc+5b9+SZgqfFrz
|
|
||||||
/ch/HjB2zYAKq9AZSmgp9qIlOIuXnctJUD9hHivuEXFDr6xi1cxj7Q8WnX4+C58/
|
|
||||||
QyGS4lebbLQ35x6fTQ8=
|
|
||||||
-----END PUBLIC KEY-----
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
|
|
||||||
"%@ 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";
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
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'))
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
edition = args.get('edition', 'se')
|
|
||||||
dialogTitles = {
|
|
||||||
'se': "dupeGuru Preferences",
|
|
||||||
'me': "dupeGuru ME Preferences",
|
|
||||||
'pe': "dupeGuru PE Preferences",
|
|
||||||
}
|
|
||||||
dialogHeights = {
|
|
||||||
'se': 345,
|
|
||||||
'me': 365,
|
|
||||||
'pe': 275,
|
|
||||||
}
|
|
||||||
scanTypeNames = {
|
|
||||||
'se': ["Filename", "Content", "Folders"],
|
|
||||||
'me': ["Filename", "Filename - Fields", "Filename - Fields (No Order)", "Tags", "Content", "Audio Content"],
|
|
||||||
'pe': ["Contents", "EXIF Timestamp"],
|
|
||||||
}
|
|
||||||
|
|
||||||
result = Window(410, dialogHeights[edition], dialogTitles[edition])
|
|
||||||
tabView = TabView(result)
|
|
||||||
basicTab = tabView.addTab("Basic")
|
|
||||||
advancedTab = tabView.addTab("Advanced")
|
|
||||||
scanTypePopup = Popup(basicTab.view, scanTypeNames[edition])
|
|
||||||
scanTypeLabel = Label(basicTab.view, "Scan Type:")
|
|
||||||
thresholdSlider = Slider(basicTab.view, 1, 100, 80)
|
|
||||||
thresholdLabel = Label(basicTab.view, "Filter hardness:")
|
|
||||||
moreResultsLabel = Label(basicTab.view, "More results")
|
|
||||||
fewerResultsLabel = Label(basicTab.view, "Fewer results")
|
|
||||||
thresholdValueLabel = Label(basicTab.view, "")
|
|
||||||
fontSizeCombo = Combobox(basicTab.view, ["11", "12", "13", "14", "18", "24"])
|
|
||||||
fontSizeLabel = Label(basicTab.view, "Font Size:")
|
|
||||||
if edition in ('se', 'me'):
|
|
||||||
wordWeightingBox = Checkbox(basicTab.view, "Word weighting")
|
|
||||||
matchSimilarWordsBox = Checkbox(basicTab.view, "Match similar words")
|
|
||||||
elif edition == 'pe':
|
|
||||||
matchDifferentDimensionsBox = Checkbox(basicTab.view, "Match pictures of different dimensions")
|
|
||||||
mixKindBox = Checkbox(basicTab.view, "Can mix file kind")
|
|
||||||
removeEmptyFoldersBox = Checkbox(basicTab.view, "Remove empty folders on delete or move")
|
|
||||||
checkForUpdatesBox = Checkbox(basicTab.view, "Automatically check for updates")
|
|
||||||
if edition == 'se':
|
|
||||||
ignoreSmallFilesBox = Checkbox(basicTab.view, "Ignore files smaller than:")
|
|
||||||
smallFilesThresholdText = TextField(basicTab.view, "")
|
|
||||||
smallFilesThresholdSuffixLabel = Label(basicTab.view, "KB")
|
|
||||||
elif edition == 'me':
|
|
||||||
tagsToScanLabel = Label(basicTab.view, "Tags to scan:")
|
|
||||||
trackBox = Checkbox(basicTab.view, "Track")
|
|
||||||
artistBox = Checkbox(basicTab.view, "Artist")
|
|
||||||
albumBox = Checkbox(basicTab.view, "Album")
|
|
||||||
titleBox = Checkbox(basicTab.view, "Title")
|
|
||||||
genreBox = Checkbox(basicTab.view, "Genre")
|
|
||||||
yearBox = Checkbox(basicTab.view, "Year")
|
|
||||||
tagBoxes = [trackBox, artistBox, albumBox, titleBox, genreBox, yearBox]
|
|
||||||
|
|
||||||
regexpCheckbox = Checkbox(advancedTab.view, "Use regular expressions when filtering")
|
|
||||||
ignoreHardlinksBox = Checkbox(advancedTab.view, "Ignore duplicates hardlinking to the same file")
|
|
||||||
debugModeCheckbox = Checkbox(advancedTab.view, "Debug mode (restart required)")
|
|
||||||
customCommandLabel = Label(advancedTab.view, "Custom command (arguments: %d for dupe, %r for ref):")
|
|
||||||
customCommandText = TextField(advancedTab.view, "")
|
|
||||||
copyMoveLabel = Label(advancedTab.view, "Copy and Move:")
|
|
||||||
copyMovePopup = Popup(advancedTab.view, ["Right in destination", "Recreate relative path", "Recreate absolute path"])
|
|
||||||
|
|
||||||
resetToDefaultsButton = Button(result, "Reset To Defaults")
|
|
||||||
|
|
||||||
scanTypePopup.bind('selectedIndex', defaults, 'values.scanType')
|
|
||||||
thresholdSlider.bind('value', defaults, 'values.minMatchPercentage')
|
|
||||||
thresholdValueLabel.bind('value', defaults, 'values.minMatchPercentage')
|
|
||||||
fontSizeCombo.bind('value', defaults, 'values.TableFontSize')
|
|
||||||
mixKindBox.bind('value', defaults, 'values.mixFileKind')
|
|
||||||
removeEmptyFoldersBox.bind('value', defaults, 'values.removeEmptyFolders')
|
|
||||||
checkForUpdatesBox.bind('value', defaults, 'values.SUEnableAutomaticChecks')
|
|
||||||
regexpCheckbox.bind('value', defaults, 'values.useRegexpFilter')
|
|
||||||
ignoreHardlinksBox.bind('value', defaults, 'values.ignoreHardlinkMatches')
|
|
||||||
debugModeCheckbox.bind('value', defaults, 'values.DebugMode')
|
|
||||||
customCommandText.bind('value', defaults, 'values.CustomCommand')
|
|
||||||
copyMovePopup.bind('selectedIndex', defaults, 'values.recreatePathType')
|
|
||||||
if edition in ('se', 'me'):
|
|
||||||
wordWeightingBox.bind('value', defaults, 'values.wordWeighting')
|
|
||||||
matchSimilarWordsBox.bind('value', defaults, 'values.matchSimilarWords')
|
|
||||||
disableWhenContentScan = [thresholdSlider, wordWeightingBox, matchSimilarWordsBox]
|
|
||||||
for control in disableWhenContentScan:
|
|
||||||
control.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsNotContent')
|
|
||||||
if edition == 'se':
|
|
||||||
ignoreSmallFilesBox.bind('value', defaults, 'values.ignoreSmallFiles')
|
|
||||||
smallFilesThresholdText.bind('value', defaults, 'values.smallFileThreshold')
|
|
||||||
elif edition == 'me':
|
|
||||||
for box in tagBoxes:
|
|
||||||
box.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsTag')
|
|
||||||
trackBox.bind('value', defaults, 'values.scanTagTrack')
|
|
||||||
artistBox.bind('value', defaults, 'values.scanTagArtist')
|
|
||||||
albumBox.bind('value', defaults, 'values.scanTagAlbum')
|
|
||||||
titleBox.bind('value', defaults, 'values.scanTagTitle')
|
|
||||||
genreBox.bind('value', defaults, 'values.scanTagGenre')
|
|
||||||
yearBox.bind('value', defaults, 'values.scanTagYear')
|
|
||||||
elif edition == 'pe':
|
|
||||||
matchDifferentDimensionsBox.bind('value', defaults, 'values.matchScaled')
|
|
||||||
thresholdSlider.bind('enabled', defaults, 'values.scanType', valueTransformer='vtScanTypeIsFuzzy')
|
|
||||||
|
|
||||||
result.canResize = False
|
|
||||||
result.canMinimize = False
|
|
||||||
thresholdValueLabel.formatter = NumberFormatter(NumberStyle.Decimal)
|
|
||||||
thresholdValueLabel.formatter.maximumFractionDigits = 0
|
|
||||||
allLabels = [scanTypeLabel, thresholdValueLabel, moreResultsLabel, fewerResultsLabel,
|
|
||||||
thresholdLabel, fontSizeLabel, customCommandLabel, copyMoveLabel]
|
|
||||||
allCheckboxes = [mixKindBox, removeEmptyFoldersBox, checkForUpdatesBox, regexpCheckbox,
|
|
||||||
ignoreHardlinksBox, debugModeCheckbox]
|
|
||||||
if edition == 'se':
|
|
||||||
allLabels += [smallFilesThresholdSuffixLabel]
|
|
||||||
allCheckboxes += [ignoreSmallFilesBox, wordWeightingBox, matchSimilarWordsBox]
|
|
||||||
elif edition == 'me':
|
|
||||||
allLabels += [tagsToScanLabel]
|
|
||||||
allCheckboxes += tagBoxes + [wordWeightingBox, matchSimilarWordsBox]
|
|
||||||
elif edition == 'pe':
|
|
||||||
allCheckboxes += [matchDifferentDimensionsBox]
|
|
||||||
for label in allLabels:
|
|
||||||
label.controlSize = ControlSize.Small
|
|
||||||
fewerResultsLabel.alignment = TextAlignment.Right
|
|
||||||
for checkbox in allCheckboxes:
|
|
||||||
checkbox.font = scanTypeLabel.font
|
|
||||||
resetToDefaultsButton.action = Action(defaults, 'revertToInitialValues:')
|
|
||||||
|
|
||||||
scanTypeLabel.width = thresholdLabel.width = fontSizeLabel.width = 94
|
|
||||||
fontSizeCombo.width = 66
|
|
||||||
thresholdValueLabel.width = 25
|
|
||||||
resetToDefaultsButton.width = 136
|
|
||||||
if edition == 'se':
|
|
||||||
smallFilesThresholdText.width = 60
|
|
||||||
smallFilesThresholdSuffixLabel.width = 40
|
|
||||||
elif edition == 'me':
|
|
||||||
for box in tagBoxes:
|
|
||||||
box.width = 70
|
|
||||||
|
|
||||||
tabView.packToCorner(Pack.UpperLeft)
|
|
||||||
tabView.fill(Pack.Right)
|
|
||||||
resetToDefaultsButton.packRelativeTo(tabView, Pack.Below, align=Pack.Right)
|
|
||||||
tabView.fill(Pack.Below, margin=14)
|
|
||||||
tabView.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
|
||||||
scanTypePopup.packToCorner(Pack.UpperRight)
|
|
||||||
scanTypeLabel.packRelativeTo(scanTypePopup, Pack.Left)
|
|
||||||
scanTypePopup.fill(Pack.Left)
|
|
||||||
thresholdSlider.packRelativeTo(scanTypePopup, Pack.Below)
|
|
||||||
thresholdValueLabel.packRelativeTo(thresholdSlider, Pack.Right)
|
|
||||||
thresholdSlider.fill(Pack.Right)
|
|
||||||
# We want to give the labels as much space as possible, and we only "know" how much is available
|
|
||||||
# after the slider's fill operation.
|
|
||||||
moreResultsLabel.width = fewerResultsLabel.width = thresholdSlider.width // 2
|
|
||||||
moreResultsLabel.packRelativeTo(thresholdSlider, Pack.Below, align=Pack.Left, margin=6)
|
|
||||||
fewerResultsLabel.packRelativeTo(thresholdSlider, Pack.Below, align=Pack.Right, margin=6)
|
|
||||||
thresholdLabel.packRelativeTo(thresholdSlider, Pack.Left)
|
|
||||||
fontSizeCombo.packRelativeTo(moreResultsLabel, Pack.Below)
|
|
||||||
fontSizeLabel.packRelativeTo(fontSizeCombo, Pack.Left)
|
|
||||||
|
|
||||||
if edition == 'me':
|
|
||||||
tagsToScanLabel.packRelativeTo(fontSizeCombo, Pack.Below)
|
|
||||||
tagsToScanLabel.fill(Pack.Left)
|
|
||||||
tagsToScanLabel.fill(Pack.Right)
|
|
||||||
trackBox.packRelativeTo(tagsToScanLabel, Pack.Below)
|
|
||||||
trackBox.x += 10
|
|
||||||
artistBox.packRelativeTo(trackBox, Pack.Right)
|
|
||||||
albumBox.packRelativeTo(artistBox, Pack.Right)
|
|
||||||
titleBox.packRelativeTo(trackBox, Pack.Below)
|
|
||||||
genreBox.packRelativeTo(titleBox, Pack.Right)
|
|
||||||
yearBox.packRelativeTo(genreBox, Pack.Right)
|
|
||||||
viewToPackCheckboxesUnder = titleBox
|
|
||||||
else:
|
|
||||||
viewToPackCheckboxesUnder = fontSizeCombo
|
|
||||||
|
|
||||||
if edition == 'se':
|
|
||||||
checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox,
|
|
||||||
ignoreSmallFilesBox]
|
|
||||||
elif edition == 'me':
|
|
||||||
checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox,
|
|
||||||
checkForUpdatesBox]
|
|
||||||
elif edition == 'pe':
|
|
||||||
checkboxesToLayout = [matchDifferentDimensionsBox, mixKindBox, removeEmptyFoldersBox,
|
|
||||||
checkForUpdatesBox]
|
|
||||||
checkboxLayout = VLayout(checkboxesToLayout)
|
|
||||||
checkboxLayout.packRelativeTo(viewToPackCheckboxesUnder, Pack.Below)
|
|
||||||
checkboxLayout.fill(Pack.Left)
|
|
||||||
checkboxLayout.fill(Pack.Right)
|
|
||||||
|
|
||||||
if edition == 'se':
|
|
||||||
smallFilesThresholdText.packRelativeTo(ignoreSmallFilesBox, Pack.Below, margin=4)
|
|
||||||
checkForUpdatesBox.packRelativeTo(smallFilesThresholdText, Pack.Below, margin=4)
|
|
||||||
checkForUpdatesBox.fill(Pack.Right)
|
|
||||||
smallFilesThresholdText.x += 20
|
|
||||||
smallFilesThresholdSuffixLabel.packRelativeTo(smallFilesThresholdText, Pack.Right)
|
|
||||||
|
|
||||||
advancedLayout = VLayout(advancedTab.view.subviews[:])
|
|
||||||
advancedLayout.packToCorner(Pack.UpperLeft)
|
|
||||||
advancedLayout.fill(Pack.Right)
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,168 +0,0 @@
|
|||||||
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)
|
|
||||||
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2006/11/16
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
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)
|
|
||||||
@@ -1,342 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2006/11/13
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
import io
|
|
||||||
|
|
||||||
from appscript import app, its, k, CommandError, ApplicationNotFoundError
|
|
||||||
|
|
||||||
from hscommon.util import remove_invalid_xml, first
|
|
||||||
from hscommon.path import Path, pathify
|
|
||||||
from hscommon.trans import trget
|
|
||||||
from cocoa import proxy
|
|
||||||
|
|
||||||
from core.scanner import ScanType
|
|
||||||
from core import directories
|
|
||||||
from core.app import JobType
|
|
||||||
from core_pe import _block_osx
|
|
||||||
from core_pe.photo import Photo as PhotoBase
|
|
||||||
from core_pe.app import DupeGuru as DupeGuruBase
|
|
||||||
from core_pe.iphoto_plist import IPhotoPlistParser
|
|
||||||
from .app import PyDupeGuruBase
|
|
||||||
|
|
||||||
tr = trget('ui')
|
|
||||||
|
|
||||||
IPHOTO_PATH = Path('iPhoto Library')
|
|
||||||
APERTURE_PATH = Path('Aperture Library')
|
|
||||||
|
|
||||||
class Photo(PhotoBase):
|
|
||||||
HANDLED_EXTS = PhotoBase.HANDLED_EXTS.copy()
|
|
||||||
HANDLED_EXTS.update({'psd', 'nef', 'cr2', 'orf'})
|
|
||||||
|
|
||||||
def _plat_get_dimensions(self):
|
|
||||||
return _block_osx.get_image_size(str(self.path))
|
|
||||||
|
|
||||||
def _plat_get_blocks(self, block_count_per_side, orientation):
|
|
||||||
try:
|
|
||||||
blocks = _block_osx.getblocks(str(self.path), block_count_per_side, orientation)
|
|
||||||
except Exception as e:
|
|
||||||
raise IOError('The reading of "%s" failed with "%s"' % (str(self.path), str(e)))
|
|
||||||
if not blocks:
|
|
||||||
raise IOError('The picture %s could not be read' % str(self.path))
|
|
||||||
return blocks
|
|
||||||
|
|
||||||
def _get_exif_timestamp(self):
|
|
||||||
exifdata = proxy.readExifData_(str(self.path))
|
|
||||||
if exifdata:
|
|
||||||
try:
|
|
||||||
return exifdata['{Exif}']['DateTimeOriginal']
|
|
||||||
except KeyError:
|
|
||||||
return ''
|
|
||||||
else:
|
|
||||||
return ''
|
|
||||||
|
|
||||||
|
|
||||||
class IPhoto(Photo):
|
|
||||||
def __init__(self, path, db_id):
|
|
||||||
# In IPhoto, we don't care about the db_id, we find photos by path.
|
|
||||||
Photo.__init__(self, path)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_folder_path(self):
|
|
||||||
return IPHOTO_PATH
|
|
||||||
|
|
||||||
class AperturePhoto(Photo):
|
|
||||||
def __init__(self, path, db_id):
|
|
||||||
Photo.__init__(self, path)
|
|
||||||
self.db_id = db_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_folder_path(self):
|
|
||||||
return APERTURE_PATH
|
|
||||||
|
|
||||||
@pathify
|
|
||||||
def get_iphoto_or_aperture_pictures(plistpath: Path, photo_class):
|
|
||||||
# The structure of iPhoto and Aperture libraries for the base photo list are excactly the same.
|
|
||||||
if not plistpath.exists():
|
|
||||||
return []
|
|
||||||
s = plistpath.open('rt', encoding='utf-8').read()
|
|
||||||
# There was a case where a guy had 0x10 chars in his plist, causing expat errors on loading
|
|
||||||
s = remove_invalid_xml(s, replace_with='')
|
|
||||||
# It seems that iPhoto sometimes doesn't properly escape & chars. The regexp below is to find
|
|
||||||
# any & char that is not a &-based entity (&, ", etc.). based on TextMate's XML
|
|
||||||
# bundle's regexp
|
|
||||||
s, count = re.subn(r'&(?![a-zA-Z0-9_-]+|#[0-9]+|#x[0-9a-fA-F]+;)', '', s)
|
|
||||||
if count:
|
|
||||||
logging.warning("%d invalid XML entities replacement made", count)
|
|
||||||
parser = IPhotoPlistParser()
|
|
||||||
try:
|
|
||||||
plist = parser.parse(io.BytesIO(s.encode('utf-8')))
|
|
||||||
except Exception:
|
|
||||||
logging.warning("iPhoto plist parsing choked on data: %r", parser.lastdata)
|
|
||||||
raise
|
|
||||||
result = []
|
|
||||||
for key, photo_data in plist['Master Image List'].items():
|
|
||||||
if photo_data['MediaType'] != 'Image':
|
|
||||||
continue
|
|
||||||
photo_path = Path(photo_data['ImagePath'])
|
|
||||||
photo = photo_class(photo_path, key)
|
|
||||||
result.append(photo)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_iphoto_pictures(plistpath):
|
|
||||||
return get_iphoto_or_aperture_pictures(plistpath, IPhoto)
|
|
||||||
|
|
||||||
def get_aperture_pictures(plistpath):
|
|
||||||
return get_iphoto_or_aperture_pictures(plistpath, AperturePhoto)
|
|
||||||
|
|
||||||
def get_iapps_database_path(prefname):
|
|
||||||
plisturls = proxy.prefValue_inDomain_(prefname, 'com.apple.iApps')
|
|
||||||
if not plisturls:
|
|
||||||
raise directories.InvalidPathError()
|
|
||||||
plistpath = proxy.url2path_(plisturls[0])
|
|
||||||
return Path(plistpath)
|
|
||||||
|
|
||||||
def get_iphoto_database_path():
|
|
||||||
return get_iapps_database_path('iPhotoRecentDatabases')
|
|
||||||
|
|
||||||
def get_aperture_database_path():
|
|
||||||
return get_iapps_database_path('ApertureLibraries')
|
|
||||||
|
|
||||||
class Directories(directories.Directories):
|
|
||||||
def __init__(self):
|
|
||||||
directories.Directories.__init__(self, fileclasses=[Photo])
|
|
||||||
try:
|
|
||||||
self.iphoto_libpath = get_iphoto_database_path()
|
|
||||||
self.set_state(self.iphoto_libpath.parent(), directories.DirectoryState.Excluded)
|
|
||||||
except directories.InvalidPathError:
|
|
||||||
self.iphoto_libpath = None
|
|
||||||
try:
|
|
||||||
self.aperture_libpath = get_aperture_database_path()
|
|
||||||
self.set_state(self.aperture_libpath.parent(), directories.DirectoryState.Excluded)
|
|
||||||
except directories.InvalidPathError:
|
|
||||||
self.aperture_libpath = None
|
|
||||||
|
|
||||||
def _get_files(self, from_path, j):
|
|
||||||
if from_path == IPHOTO_PATH:
|
|
||||||
if self.iphoto_libpath is None:
|
|
||||||
return []
|
|
||||||
is_ref = self.get_state(from_path) == directories.DirectoryState.Reference
|
|
||||||
photos = get_iphoto_pictures(self.iphoto_libpath)
|
|
||||||
for photo in photos:
|
|
||||||
photo.is_ref = is_ref
|
|
||||||
return photos
|
|
||||||
elif from_path == APERTURE_PATH:
|
|
||||||
if self.aperture_libpath is None:
|
|
||||||
return []
|
|
||||||
is_ref = self.get_state(from_path) == directories.DirectoryState.Reference
|
|
||||||
photos = get_aperture_pictures(self.aperture_libpath)
|
|
||||||
for photo in photos:
|
|
||||||
photo.is_ref = is_ref
|
|
||||||
return photos
|
|
||||||
else:
|
|
||||||
return directories.Directories._get_files(self, from_path, j)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_subfolders(path):
|
|
||||||
if path in {IPHOTO_PATH, APERTURE_PATH}:
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
return directories.Directories.get_subfolders(path)
|
|
||||||
|
|
||||||
def add_path(self, path):
|
|
||||||
if path in {IPHOTO_PATH, APERTURE_PATH}:
|
|
||||||
if path not in self:
|
|
||||||
self._dirs.append(path)
|
|
||||||
else:
|
|
||||||
directories.Directories.add_path(self, path)
|
|
||||||
|
|
||||||
def has_iphoto_path(self):
|
|
||||||
return any(path in {IPHOTO_PATH, APERTURE_PATH} for path in self._dirs)
|
|
||||||
|
|
||||||
def has_any_file(self):
|
|
||||||
# If we don't do that, it causes a hangup in the GUI when we click Start Scanning because
|
|
||||||
# checking if there's any file to scan involves reading the whole library. If we have the
|
|
||||||
# iPhoto library, we assume we have at least one file.
|
|
||||||
if self.has_iphoto_path():
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return directories.Directories.has_any_file(self)
|
|
||||||
|
|
||||||
|
|
||||||
class DupeGuruPE(DupeGuruBase):
|
|
||||||
def __init__(self, view):
|
|
||||||
DupeGuruBase.__init__(self, view)
|
|
||||||
self.directories = Directories()
|
|
||||||
|
|
||||||
def _do_delete(self, j, *args):
|
|
||||||
def op(dupe):
|
|
||||||
j.add_progress()
|
|
||||||
return self._do_delete_dupe(dupe, *args)
|
|
||||||
|
|
||||||
self.deleted_aperture_photos = False
|
|
||||||
marked = [dupe for dupe in self.results.dupes if self.results.is_marked(dupe)]
|
|
||||||
j.start_job(self.results.mark_count, tr("Sending dupes to the Trash"))
|
|
||||||
if any(isinstance(dupe, IPhoto) for dupe in marked):
|
|
||||||
j.add_progress(0, desc=tr("Talking to iPhoto. Don't touch it!"))
|
|
||||||
try:
|
|
||||||
a = app('iPhoto')
|
|
||||||
a.activate(timeout=0)
|
|
||||||
a.select(a.photo_library_album(timeout=0), timeout=0)
|
|
||||||
except (CommandError, RuntimeError, ApplicationNotFoundError):
|
|
||||||
pass
|
|
||||||
if any(isinstance(dupe, AperturePhoto) for dupe in marked):
|
|
||||||
self.deleted_aperture_photos = True
|
|
||||||
j.add_progress(0, desc=tr("Talking to Aperture. Don't touch it!"))
|
|
||||||
try:
|
|
||||||
a = app('Aperture')
|
|
||||||
a.activate(timeout=0)
|
|
||||||
except (CommandError, RuntimeError, ApplicationNotFoundError):
|
|
||||||
pass
|
|
||||||
self.results.perform_on_marked(op, True)
|
|
||||||
|
|
||||||
def _do_delete_dupe(self, dupe, *args):
|
|
||||||
if isinstance(dupe, IPhoto):
|
|
||||||
try:
|
|
||||||
a = app('iPhoto')
|
|
||||||
album = a.photo_library_album()
|
|
||||||
if album is None:
|
|
||||||
msg = "There are communication problems with iPhoto. Try opening iPhoto first, it might solve it."
|
|
||||||
raise EnvironmentError(msg)
|
|
||||||
[photo] = album.photos[its.image_path == str(dupe.path)]()
|
|
||||||
a.remove(photo, timeout=0)
|
|
||||||
except ValueError:
|
|
||||||
msg = "Could not find photo '{}' in iPhoto Library".format(str(dupe.path))
|
|
||||||
raise EnvironmentError(msg)
|
|
||||||
except (CommandError, RuntimeError) as e:
|
|
||||||
raise EnvironmentError(str(e))
|
|
||||||
if isinstance(dupe, AperturePhoto):
|
|
||||||
try:
|
|
||||||
a = app('Aperture')
|
|
||||||
# I'm flying blind here. In my own test library, all photos are in an album with the
|
|
||||||
# id "LibraryFolder", so I'm going to guess that it's the case at least most of the
|
|
||||||
# time. As a safeguard, if we don't find any library with that id, we'll use the
|
|
||||||
# first album.
|
|
||||||
# Now, about deleting: All attempts I've made at sending photos to trash failed,
|
|
||||||
# even with normal applescript. So, what we're going to do here is to create a
|
|
||||||
# "dupeGuru Trash" project and tell the user to manually send those photos to trash.
|
|
||||||
libraries = a.libraries()
|
|
||||||
library = first(l for l in libraries if l.id == 'LibraryFolder')
|
|
||||||
if library is None:
|
|
||||||
library = libraries[0]
|
|
||||||
trash_project = a.projects["dupeGuru Trash"]
|
|
||||||
if trash_project.exists():
|
|
||||||
trash_project = trash_project()
|
|
||||||
else:
|
|
||||||
trash_project = library.make(new=k.project, with_properties={k.name: "dupeGuru Trash"})
|
|
||||||
[photo] = library.image_versions[its.id == dupe.db_id]()
|
|
||||||
photo.move(to=trash_project)
|
|
||||||
except (IndexError, ValueError):
|
|
||||||
msg = "Could not find photo '{}' in Aperture Library".format(str(dupe.path))
|
|
||||||
raise EnvironmentError(msg)
|
|
||||||
except (CommandError, RuntimeError) as e:
|
|
||||||
raise EnvironmentError(str(e))
|
|
||||||
else:
|
|
||||||
DupeGuruBase._do_delete_dupe(self, dupe, *args)
|
|
||||||
|
|
||||||
def _create_file(self, path):
|
|
||||||
if (self.directories.iphoto_libpath is not None) and (path in self.directories.iphoto_libpath.parent()):
|
|
||||||
if not hasattr(self, 'path2iphoto'):
|
|
||||||
photos = get_iphoto_pictures(self.directories.iphoto_libpath)
|
|
||||||
self.path2iphoto = {p.path: p for p in photos}
|
|
||||||
return self.path2iphoto.get(path)
|
|
||||||
if (self.directories.aperture_libpath is not None) and (path in self.directories.aperture_libpath.parent()):
|
|
||||||
if not hasattr(self, 'path2aperture'):
|
|
||||||
photos = get_aperture_pictures(self.directories.aperture_libpath)
|
|
||||||
self.path2aperture = {p.path: p for p in photos}
|
|
||||||
return self.path2aperture.get(path)
|
|
||||||
return DupeGuruBase._create_file(self, path)
|
|
||||||
|
|
||||||
def _job_completed(self, jobid):
|
|
||||||
DupeGuruBase._job_completed(self, jobid)
|
|
||||||
if jobid == JobType.Load:
|
|
||||||
if hasattr(self, 'path2iphoto'):
|
|
||||||
del self.path2iphoto
|
|
||||||
if hasattr(self, 'path2aperture'):
|
|
||||||
del self.path2aperture
|
|
||||||
if jobid == JobType.Delete and self.deleted_aperture_photos:
|
|
||||||
msg = tr("Deleted Aperture photos were sent to a project called \"dupeGuru Trash\".")
|
|
||||||
self.view.show_message(msg)
|
|
||||||
|
|
||||||
def copy_or_move(self, dupe, copy, destination, dest_type):
|
|
||||||
if isinstance(dupe, (IPhoto, AperturePhoto)):
|
|
||||||
copy = True
|
|
||||||
return DupeGuruBase.copy_or_move(self, dupe, copy, destination, dest_type)
|
|
||||||
|
|
||||||
def selected_dupe_path(self):
|
|
||||||
if not self.selected_dupes:
|
|
||||||
return None
|
|
||||||
return self.selected_dupes[0].path
|
|
||||||
|
|
||||||
def selected_dupe_ref_path(self):
|
|
||||||
if not self.selected_dupes:
|
|
||||||
return None
|
|
||||||
ref = self.results.get_group_of_duplicate(self.selected_dupes[0]).ref
|
|
||||||
if ref is self.selected_dupes[0]: # we don't want the same pic to be displayed on both sides
|
|
||||||
return None
|
|
||||||
return ref.path
|
|
||||||
|
|
||||||
def start_scanning(self):
|
|
||||||
if self.directories.has_iphoto_path():
|
|
||||||
try:
|
|
||||||
app('iPhoto')
|
|
||||||
except ApplicationNotFoundError:
|
|
||||||
self.view.show_message(tr("The iPhoto application couldn't be found."))
|
|
||||||
return
|
|
||||||
DupeGuruBase.start_scanning(self)
|
|
||||||
|
|
||||||
class PyDupeGuru(PyDupeGuruBase):
|
|
||||||
def __init__(self):
|
|
||||||
self._init(DupeGuruPE)
|
|
||||||
|
|
||||||
def clearPictureCache(self):
|
|
||||||
self.model.scanner.clear_picture_cache()
|
|
||||||
|
|
||||||
#---Information
|
|
||||||
def getSelectedDupePath(self) -> str:
|
|
||||||
return str(self.model.selected_dupe_path())
|
|
||||||
|
|
||||||
def getSelectedDupeRefPath(self) -> str:
|
|
||||||
return str(self.model.selected_dupe_ref_path())
|
|
||||||
|
|
||||||
#---Properties
|
|
||||||
def setScanType_(self, scan_type: int):
|
|
||||||
try:
|
|
||||||
self.model.scanner.scan_type = [
|
|
||||||
ScanType.FuzzyBlock,
|
|
||||||
ScanType.ExifTimestamp,
|
|
||||||
][scan_type]
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setMatchScaled_(self, match_scaled: bool):
|
|
||||||
self.model.scanner.match_scaled = match_scaled
|
|
||||||
|
|
||||||
def setMinMatchPercentage_(self, percentage: int):
|
|
||||||
self.model.scanner.threshold = percentage
|
|
||||||
@@ -1,104 +0,0 @@
|
|||||||
# Created By: Virgil Dupras
|
|
||||||
# Created On: 2009-05-24
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
# Created On: 2012-05-30
|
|
||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
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)
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
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)]
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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()
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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()
|
|
||||||
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
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()
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
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)
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
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()
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
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()
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
from cocoa.inter import PyGUIObject, GUIObjectView
|
|
||||||
|
|
||||||
class StatsLabelView(GUIObjectView):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class PyStatsLabel(PyGUIObject):
|
|
||||||
|
|
||||||
def display(self) -> str:
|
|
||||||
return self.model.display
|
|
||||||
@@ -1,282 +0,0 @@
|
|||||||
# 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,16 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "AppDelegateBase.h"
|
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase {}
|
|
||||||
- (void)removeDeadTracks;
|
|
||||||
@end
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "ProgressController.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "ValueTransformers.h"
|
|
||||||
#import "Dialogs.h"
|
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
+ (NSDictionary *)defaultPreferences
|
|
||||||
{
|
|
||||||
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:[super defaultPreferences]];
|
|
||||||
[d setObject:i2n(3) forKey:@"scanType"];
|
|
||||||
[d setObject:i2n(80) forKey:@"minMatchPercentage"];
|
|
||||||
[d setObject:b2n(NO) forKey:@"wordWeighting"];
|
|
||||||
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
|
|
||||||
[d setObject:b2n(NO) forKey:@"scanTagTrack"];
|
|
||||||
[d setObject:b2n(YES) forKey:@"scanTagArtist"];
|
|
||||||
[d setObject:b2n(YES) forKey:@"scanTagAlbum"];
|
|
||||||
[d setObject:b2n(YES) forKey:@"scanTagTitle"];
|
|
||||||
[d setObject:b2n(NO) forKey:@"scanTagGenre"];
|
|
||||||
[d setObject:b2n(NO) forKey:@"scanTagYear"];
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id)init
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:4];
|
|
||||||
[i addIndex:5];
|
|
||||||
VTIsIntIn *vtScanTypeIsNotContent = [[[VTIsIntIn alloc] initWithValues:i reverse:YES] autorelease];
|
|
||||||
[NSValueTransformer setValueTransformer:vtScanTypeIsNotContent forName:@"vtScanTypeIsNotContent"];
|
|
||||||
VTIsIntIn *vtScanTypeIsTag = [[[VTIsIntIn alloc] initWithValues:[NSIndexSet indexSetWithIndex:3] reverse:NO] autorelease];
|
|
||||||
[NSValueTransformer setValueTransformer:vtScanTypeIsTag forName:@"vtScanTypeIsTag"];
|
|
||||||
_directoryPanel = nil;
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)homepageURL
|
|
||||||
{
|
|
||||||
return @"http://www.hardcoded.net/dupeguru_me/";
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ResultWindowBase *)createResultWindow
|
|
||||||
{
|
|
||||||
return [[ResultWindow alloc] initWithParentApp:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (DirectoryPanel *)createDirectoryPanel
|
|
||||||
{
|
|
||||||
return [[DirectoryPanelME alloc] initWithParentApp:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)removeDeadTracks
|
|
||||||
{
|
|
||||||
[(ResultWindow *)[self resultWindow] removeDeadTracks];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "../base/Consts.h"
|
|
||||||
|
|
||||||
#define jobScanDeadTracks @"jobScanDeadTracks"
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "DetailsPanelBase.h"
|
|
||||||
|
|
||||||
@interface DetailsPanel : DetailsPanelBase
|
|
||||||
@end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "DetailsPanel_UI.h"
|
|
||||||
|
|
||||||
@implementation DetailsPanel
|
|
||||||
- (NSWindow *)createWindow
|
|
||||||
{
|
|
||||||
return createDetailsPanel_UI(self);
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "../base/DirectoryPanel.h"
|
|
||||||
|
|
||||||
@interface DirectoryPanelME : DirectoryPanel
|
|
||||||
{
|
|
||||||
}
|
|
||||||
- (IBAction)addiTunes:(id)sender;
|
|
||||||
@end
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
|
|
||||||
@implementation DirectoryPanelME
|
|
||||||
- (id)initWithParentApp:(id)aParentApp
|
|
||||||
{
|
|
||||||
self = [super initWithParentApp:aParentApp];
|
|
||||||
_alwaysShowPopUp = YES;
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)fillPopUpMenu
|
|
||||||
{
|
|
||||||
[super fillPopUpMenu];
|
|
||||||
NSMenu *m = [addButtonPopUp menu];
|
|
||||||
NSMenuItem *mi = [m insertItemWithTitle:NSLocalizedString(@"Add iTunes Library", @"") action:@selector(addiTunes:)
|
|
||||||
keyEquivalent:@"" atIndex:1];
|
|
||||||
[mi setTarget:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)addiTunes:(id)sender
|
|
||||||
{
|
|
||||||
[self addDirectory:@"iTunes Library"];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>English</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>dupeGuru</string>
|
|
||||||
<key>CFBundleHelpBookFolder</key>
|
|
||||||
<string>dupeguru_me_help</string>
|
|
||||||
<key>CFBundleHelpBookName</key>
|
|
||||||
<string>dupeGuru ME Help</string>
|
|
||||||
<key>CFBundleIconFile</key>
|
|
||||||
<string>dupeguru</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.hardcoded-software.dupeguru-me</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>dupeGuru ME</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>hsft</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>{version}</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>{version}</string>
|
|
||||||
<key>NSMainNibFile</key>
|
|
||||||
<string>MainMenu</string>
|
|
||||||
<key>NSPrincipalClass</key>
|
|
||||||
<string>NSApplication</string>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>© Hardcoded Software, 2014</string>
|
|
||||||
<key>SUFeedURL</key>
|
|
||||||
<string>http://www.hardcoded.net/updates/dupeguru_me.appcast</string>
|
|
||||||
<key>SUPublicDSAKeyFile</key>
|
|
||||||
<string>dsa_pub.pem</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "ResultWindowBase.h"
|
|
||||||
|
|
||||||
@interface ResultWindow : ResultWindowBase {}
|
|
||||||
- (void)removeDeadTracks;
|
|
||||||
@end
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "Dialogs.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
#import "ProgressController.h"
|
|
||||||
|
|
||||||
@implementation ResultWindow
|
|
||||||
/* Override */
|
|
||||||
- (void)setScanOptions
|
|
||||||
{
|
|
||||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
|
||||||
[model setScanType:n2i([ud objectForKey:@"scanType"])];
|
|
||||||
[model enable:n2b([ud objectForKey:@"scanTagTrack"]) scanForTag:@"track"];
|
|
||||||
[model enable:n2b([ud objectForKey:@"scanTagArtist"]) scanForTag:@"artist"];
|
|
||||||
[model enable:n2b([ud objectForKey:@"scanTagAlbum"]) scanForTag:@"album"];
|
|
||||||
[model enable:n2b([ud objectForKey:@"scanTagTitle"]) scanForTag:@"title"];
|
|
||||||
[model enable:n2b([ud objectForKey:@"scanTagGenre"]) scanForTag:@"genre"];
|
|
||||||
[model enable:n2b([ud objectForKey:@"scanTagYear"]) scanForTag:@"year"];
|
|
||||||
[model setMinMatchPercentage:n2i([ud objectForKey:@"minMatchPercentage"])];
|
|
||||||
[model setWordWeighting:n2b([ud objectForKey:@"wordWeighting"])];
|
|
||||||
[model setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
|
|
||||||
[model setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
|
|
||||||
[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 */
|
|
||||||
- (void)removeDeadTracks
|
|
||||||
{
|
|
||||||
[model scanDeadTracks];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from hscommon.trans import install_gettext_trans_under_cocoa
|
|
||||||
install_gettext_trans_under_cocoa()
|
|
||||||
|
|
||||||
from cocoa.inter import PySelectableList, PyColumns, PyTable
|
|
||||||
|
|
||||||
from inter.all import *
|
|
||||||
from inter.app_me import PyDupeGuru
|
|
||||||
|
|
||||||
# When built under virtualenv, the dependency collector misses this module, so we have to force it
|
|
||||||
# to see the module.
|
|
||||||
import distutils.sysconfig
|
|
||||||
Binary file not shown.
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "AppDelegateBase.h"
|
|
||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase {}
|
|
||||||
- (void)clearPictureCache;
|
|
||||||
@end
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "ProgressController.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "ValueTransformers.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
#import "ResultWindow.h"
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
+ (NSDictionary *)defaultPreferences
|
|
||||||
{
|
|
||||||
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:[super defaultPreferences]];
|
|
||||||
[d setObject:i2n(0) forKey:@"scanType"];
|
|
||||||
[d setObject:i2n(95) forKey:@"minMatchPercentage"];
|
|
||||||
[d setObject:b2n(NO) forKey:@"matchScaled"];
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id)init
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:0];
|
|
||||||
VTIsIntIn *vtScanTypeIsFuzzy = [[[VTIsIntIn alloc] initWithValues:i reverse:NO] autorelease];
|
|
||||||
[NSValueTransformer setValueTransformer:vtScanTypeIsFuzzy forName:@"vtScanTypeIsFuzzy"];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)homepageURL
|
|
||||||
{
|
|
||||||
return @"http://www.hardcoded.net/dupeguru_pe/";
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ResultWindowBase *)createResultWindow
|
|
||||||
{
|
|
||||||
return [[ResultWindow alloc] initWithParentApp:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (DirectoryPanel *)createDirectoryPanel
|
|
||||||
{
|
|
||||||
return [[DirectoryPanelPE alloc] initWithParentApp:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (DetailsPanel *)createDetailsPanel
|
|
||||||
{
|
|
||||||
return [[DetailsPanel alloc] initWithApp:model];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)clearPictureCache
|
|
||||||
{
|
|
||||||
[(ResultWindow *)[self resultWindow] clearPictureCache];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "../base/Consts.h"
|
|
||||||
|
|
||||||
#define ImageLoadedNotification @"ImageLoadedNotification"
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "DetailsPanelBase.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface DetailsPanel : DetailsPanelBase
|
|
||||||
{
|
|
||||||
NSImageView *dupeImage;
|
|
||||||
NSProgressIndicator *dupeProgressIndicator;
|
|
||||||
NSImageView *refImage;
|
|
||||||
NSProgressIndicator *refProgressIndicator;
|
|
||||||
|
|
||||||
PyDupeGuru *pyApp;
|
|
||||||
BOOL _needsRefresh;
|
|
||||||
NSString *_dupePath;
|
|
||||||
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
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "NSNotificationAdditions.h"
|
|
||||||
#import "NSImageAdditions.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
#import "DetailsPanel_UI.h"
|
|
||||||
|
|
||||||
@implementation DetailsPanel
|
|
||||||
|
|
||||||
@synthesize dupeImage;
|
|
||||||
@synthesize dupeProgressIndicator;
|
|
||||||
@synthesize refImage;
|
|
||||||
@synthesize refProgressIndicator;
|
|
||||||
|
|
||||||
- (id)initWithApp:(PyDupeGuru *)aApp
|
|
||||||
{
|
|
||||||
self = [super initWithPyRef:[aApp detailsPanel]];
|
|
||||||
pyApp = aApp;
|
|
||||||
_needsRefresh = YES;
|
|
||||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageLoaded:) name:ImageLoadedNotification object:self];
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSWindow *)createWindow
|
|
||||||
{
|
|
||||||
return createDetailsPanel_UI(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)loadImageAsync:(NSString *)imagePath
|
|
||||||
{
|
|
||||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
|
||||||
NSImage *image = [[NSImage alloc] initByReferencingFile:imagePath];
|
|
||||||
NSImage *thumbnail = [image imageByScalingProportionallyToSize:NSMakeSize(512,512)];
|
|
||||||
[image release];
|
|
||||||
NSMutableDictionary *params = [NSMutableDictionary dictionary];
|
|
||||||
[params setValue:imagePath forKey:@"imagePath"];
|
|
||||||
[params setValue:thumbnail forKey:@"image"];
|
|
||||||
[[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:ImageLoadedNotification object:self userInfo:params waitUntilDone:YES];
|
|
||||||
[pool release];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)refreshDetails
|
|
||||||
{
|
|
||||||
if (!_needsRefresh)
|
|
||||||
return;
|
|
||||||
[detailsTable reloadData];
|
|
||||||
|
|
||||||
NSString *refPath = [pyApp getSelectedDupeRefPath];
|
|
||||||
if (_refPath != nil)
|
|
||||||
[_refPath autorelease];
|
|
||||||
_refPath = [refPath retain];
|
|
||||||
[NSThread detachNewThreadSelector:@selector(loadImageAsync:) toTarget:self withObject:refPath];
|
|
||||||
NSString *dupePath = [pyApp getSelectedDupePath];
|
|
||||||
if (_dupePath != nil)
|
|
||||||
[_dupePath autorelease];
|
|
||||||
_dupePath = [dupePath retain];
|
|
||||||
if (![dupePath isEqual: refPath])
|
|
||||||
[NSThread detachNewThreadSelector:@selector(loadImageAsync:) toTarget:self withObject:dupePath];
|
|
||||||
[refProgressIndicator startAnimation:nil];
|
|
||||||
[dupeProgressIndicator startAnimation:nil];
|
|
||||||
_needsRefresh = NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Notifications */
|
|
||||||
- (void)imageLoaded:(NSNotification *)aNotification
|
|
||||||
{
|
|
||||||
NSString *imagePath = [[aNotification userInfo] valueForKey:@"imagePath"];
|
|
||||||
NSImage *image = [[aNotification userInfo] valueForKey:@"image"];
|
|
||||||
if ([imagePath isEqual: _refPath])
|
|
||||||
{
|
|
||||||
[refImage setImage:image];
|
|
||||||
[refProgressIndicator stopAnimation:nil];
|
|
||||||
}
|
|
||||||
if ([imagePath isEqual: _dupePath])
|
|
||||||
{
|
|
||||||
[dupeImage setImage:image];
|
|
||||||
[dupeProgressIndicator stopAnimation:nil];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Python --> Cocoa */
|
|
||||||
- (void)refresh
|
|
||||||
{
|
|
||||||
_needsRefresh = YES;
|
|
||||||
[super refresh];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "../base/DirectoryPanel.h"
|
|
||||||
|
|
||||||
@interface DirectoryPanelPE : DirectoryPanel
|
|
||||||
{
|
|
||||||
}
|
|
||||||
- (IBAction)addiPhoto:(id)sender;
|
|
||||||
- (IBAction)addAperture:(id)sender;
|
|
||||||
@end
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
|
|
||||||
@implementation DirectoryPanelPE
|
|
||||||
- (id)initWithParentApp:(id)aParentApp
|
|
||||||
{
|
|
||||||
self = [super initWithParentApp:aParentApp];
|
|
||||||
_alwaysShowPopUp = YES;
|
|
||||||
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
|
|
||||||
{
|
|
||||||
[self addDirectory:@"iPhoto Library"];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (IBAction)addAperture:(id)sender
|
|
||||||
{
|
|
||||||
[self addDirectory:@"Aperture Library"];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>English</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>dupeGuru</string>
|
|
||||||
<key>CFBundleHelpBookFolder</key>
|
|
||||||
<string>dupeguru_pe_help</string>
|
|
||||||
<key>CFBundleHelpBookName</key>
|
|
||||||
<string>dupeGuru PE Help</string>
|
|
||||||
<key>CFBundleIconFile</key>
|
|
||||||
<string>dupeguru</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.hardcoded-software.dupeguru-pe</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>dupeGuru PE</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>hsft</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>{version}</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>{version}</string>
|
|
||||||
<key>NSMainNibFile</key>
|
|
||||||
<string>MainMenu</string>
|
|
||||||
<key>NSPrincipalClass</key>
|
|
||||||
<string>NSApplication</string>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>© Hardcoded Software, 2014</string>
|
|
||||||
<key>SUFeedURL</key>
|
|
||||||
<string>http://www.hardcoded.net/updates/dupeguru_pe.appcast</string>
|
|
||||||
<key>SUPublicDSAKeyFile</key>
|
|
||||||
<string>dsa_pub.pem</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "ResultWindowBase.h"
|
|
||||||
|
|
||||||
@interface ResultWindow : ResultWindowBase {}
|
|
||||||
- (void)clearPictureCache;
|
|
||||||
@end
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "Dialogs.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@implementation ResultWindow
|
|
||||||
/* Override */
|
|
||||||
- (void)initResultColumns
|
|
||||||
{
|
|
||||||
HSColumnDef defs[] = {
|
|
||||||
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
|
|
||||||
{@"name", 162, 16, 0, YES, nil},
|
|
||||||
{@"folder_path", 142, 16, 0, YES, nil},
|
|
||||||
{@"size", 63, 16, 0, YES, nil},
|
|
||||||
{@"extension", 40, 16, 0, YES, nil},
|
|
||||||
{@"dimensions", 73, 16, 0, YES, nil},
|
|
||||||
{@"exif_timestamp", 120, 16, 0, YES, nil},
|
|
||||||
{@"mtime", 120, 16, 0, YES, nil},
|
|
||||||
{@"percentage", 58, 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];
|
|
||||||
[[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 */
|
|
||||||
- (void)clearPictureCache
|
|
||||||
{
|
|
||||||
NSString *msg = NSLocalizedString(@"Do you really want to remove all your cached picture analysis?", @"");
|
|
||||||
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
|
|
||||||
return;
|
|
||||||
[model clearPictureCache];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
#
|
|
||||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
# which should be included with this package. The terms are also available at
|
|
||||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
|
|
||||||
from hscommon.trans import install_gettext_trans_under_cocoa
|
|
||||||
install_gettext_trans_under_cocoa()
|
|
||||||
|
|
||||||
from cocoa.inter import PySelectableList, PyColumns, PyTable
|
|
||||||
|
|
||||||
from inter.all import *
|
|
||||||
from inter.app_pe import PyDupeGuru
|
|
||||||
|
|
||||||
# When built under virtualenv, the dependency collector misses this module, so we have to force it
|
|
||||||
# to see the module.
|
|
||||||
import distutils.sysconfig
|
|
||||||
Binary file not shown.
@@ -1,70 +0,0 @@
|
|||||||
ownerclass = 'DetailsPanel'
|
|
||||||
ownerimport = 'DetailsPanel.h'
|
|
||||||
|
|
||||||
result = Panel(593, 398, "Details of Selected File")
|
|
||||||
table = TableView(result)
|
|
||||||
split = SplitView(result, 2, vertical=True)
|
|
||||||
leftSplit, rightSplit = split.subviews
|
|
||||||
selectedLabel = Label(leftSplit, "Selected")
|
|
||||||
selectedImage = ImageView(leftSplit, 'NSApplicationIcon')
|
|
||||||
leftSpinner = ProgressIndicator(leftSplit)
|
|
||||||
referenceLabel = Label(rightSplit, "Reference")
|
|
||||||
referenceImage = ImageView(rightSplit, 'NSApplicationIcon')
|
|
||||||
rightSpinner = ProgressIndicator(rightSplit)
|
|
||||||
|
|
||||||
owner.detailsTable = table
|
|
||||||
owner.dupeImage = selectedImage
|
|
||||||
owner.dupeProgressIndicator = leftSpinner
|
|
||||||
owner.refImage = referenceImage
|
|
||||||
owner.refProgressIndicator = rightSpinner
|
|
||||||
table.dataSource = owner
|
|
||||||
|
|
||||||
result.style = PanelStyle.Utility
|
|
||||||
result.xProportion = 0.6
|
|
||||||
result.yProportion = 0.6
|
|
||||||
result.canMinimize = False
|
|
||||||
result.autosaveName = 'DetailsPanel'
|
|
||||||
result.minSize = Size(451, 240)
|
|
||||||
|
|
||||||
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.height = 165
|
|
||||||
|
|
||||||
sides = [
|
|
||||||
(leftSplit, selectedLabel, selectedImage, leftSpinner),
|
|
||||||
(rightSplit, referenceLabel, referenceImage, rightSpinner),
|
|
||||||
]
|
|
||||||
for subSplit, label, image, spinner in sides:
|
|
||||||
label.alignment = TextAlignment.Center
|
|
||||||
spinner.style = const.NSProgressIndicatorSpinningStyle
|
|
||||||
spinner.controlSize = const.NSSmallControlSize
|
|
||||||
spinner.displayedWhenStopped = False
|
|
||||||
|
|
||||||
label.packToCorner(Pack.UpperLeft, margin=0)
|
|
||||||
label.fill(Pack.Right, margin=0)
|
|
||||||
label.setAnchor(Pack.UpperLeft, growX=True)
|
|
||||||
image.packRelativeTo(label, Pack.Below)
|
|
||||||
image.fill(Pack.LowerRight, margin=0)
|
|
||||||
image.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
|
||||||
spinner.y = label.y
|
|
||||||
spinner.x = subSplit.width - 30
|
|
||||||
spinner.setAnchor(Pack.UpperRight)
|
|
||||||
|
|
||||||
table.packToCorner(Pack.UpperLeft, margin=0)
|
|
||||||
table.fill(Pack.Right, margin=0)
|
|
||||||
table.setAnchor(Pack.UpperLeft, growX=True)
|
|
||||||
|
|
||||||
split.packRelativeTo(table, Pack.Below)
|
|
||||||
split.fill(Pack.LowerRight, margin=0)
|
|
||||||
split.setAnchor(Pack.UpperLeft, growX=True, growY=True)
|
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
|
|
||||||
def main():
|
|
||||||
return os.system('open "{{app_path}}"')
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "AppDelegateBase.h"
|
|
||||||
#import "PyDupeGuru.h"
|
|
||||||
|
|
||||||
@interface AppDelegate : AppDelegateBase {}
|
|
||||||
@end
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "AppDelegate.h"
|
|
||||||
#import "ProgressController.h"
|
|
||||||
#import "Utils.h"
|
|
||||||
#import "ValueTransformers.h"
|
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "DirectoryPanel.h"
|
|
||||||
#import "ResultWindow.h"
|
|
||||||
#import "Consts.h"
|
|
||||||
|
|
||||||
@implementation AppDelegate
|
|
||||||
+ (NSDictionary *)defaultPreferences
|
|
||||||
{
|
|
||||||
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:[super defaultPreferences]];
|
|
||||||
[d setObject:i2n(1) forKey:@"scanType"];
|
|
||||||
[d setObject:i2n(80) forKey:@"minMatchPercentage"];
|
|
||||||
[d setObject:i2n(30) forKey:@"smallFileThreshold"];
|
|
||||||
[d setObject:b2n(YES) forKey:@"wordWeighting"];
|
|
||||||
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
|
|
||||||
[d setObject:b2n(YES) forKey:@"ignoreSmallFiles"];
|
|
||||||
return d;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (id)init
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
NSMutableIndexSet *contentsIndexes = [NSMutableIndexSet indexSet];
|
|
||||||
[contentsIndexes addIndex:1];
|
|
||||||
[contentsIndexes addIndex:2];
|
|
||||||
VTIsIntIn *vt = [[[VTIsIntIn alloc] initWithValues:contentsIndexes reverse:YES] autorelease];
|
|
||||||
[NSValueTransformer setValueTransformer:vt forName:@"vtScanTypeIsNotContent"];
|
|
||||||
_directoryPanel = nil;
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSString *)homepageURL
|
|
||||||
{
|
|
||||||
return @"http://www.hardcoded.net/dupeguru/";
|
|
||||||
}
|
|
||||||
|
|
||||||
- (ResultWindowBase *)createResultWindow
|
|
||||||
{
|
|
||||||
return [[ResultWindow alloc] initWithParentApp:self];
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "DetailsPanelBase.h"
|
|
||||||
|
|
||||||
@interface DetailsPanel : DetailsPanelBase
|
|
||||||
@end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import "DetailsPanel.h"
|
|
||||||
#import "DetailsPanel_UI.h"
|
|
||||||
|
|
||||||
@implementation DetailsPanel
|
|
||||||
- (NSWindow *)createWindow
|
|
||||||
{
|
|
||||||
return createDetailsPanel_UI(self);
|
|
||||||
}
|
|
||||||
@end
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>English</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>dupeGuru</string>
|
|
||||||
<key>CFBundleHelpBookFolder</key>
|
|
||||||
<string>dupeguru_help</string>
|
|
||||||
<key>CFBundleHelpBookName</key>
|
|
||||||
<string>dupeGuru Help</string>
|
|
||||||
<key>CFBundleIconFile</key>
|
|
||||||
<string>dupeguru</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>com.hardcoded-software.dupeguru</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>dupeGuru</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>hsft</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>{version}</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>{version}</string>
|
|
||||||
<key>NSPrincipalClass</key>
|
|
||||||
<string>NSApplication</string>
|
|
||||||
<key>NSHumanReadableCopyright</key>
|
|
||||||
<string>© Hardcoded Software, 2014</string>
|
|
||||||
<key>SUFeedURL</key>
|
|
||||||
<string>http://www.hardcoded.net/updates/dupeguru.appcast</string>
|
|
||||||
<key>SUPublicDSAKeyFile</key>
|
|
||||||
<string>dsa_pub.pem</string>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
|
||||||
|
|
||||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
|
||||||
which should be included with this package. The terms are also available at
|
|
||||||
http://www.gnu.org/licenses/gpl-3.0.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
#import <Cocoa/Cocoa.h>
|
|
||||||
#import "ResultWindowBase.h"
|
|
||||||
|
|
||||||
@interface ResultWindow : ResultWindowBase {}
|
|
||||||
@end
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user