1
0
mirror of https://github.com/arsenetar/dupeguru.git synced 2026-01-25 08:01:39 +00:00

Compare commits

..

153 Commits
rust ... 4.0.4

Author SHA1 Message Date
11e57b0316 Remove RC from changelog version
Adding RC would require updating other scripts to support it.  For the
purpose of the changelog just using 4.0.4 should suffice.  Might Add
support for this later.
2019-05-13 20:43:47 -05:00
c661905350 Bump version to 4.0.4 RC
- Also update package.py to allow version postfixes without causing
  issues with Windows build.
2019-05-13 20:18:56 -05:00
d819719eca Update changelog for 4.0.4 RC
Marking this as an RC as it will take some time for me to get all
packages out and I might need to fix various packaging related bugs.
Will mark as 4.0.4 after all packages exist, should not have application
changes between these two.
2019-05-13 19:37:54 -05:00
08722a30f2 Merge pull request #565 from arsenetar/542
Switch to pyinstaller from cx_freeze
2019-05-02 21:09:55 -05:00
a1cc0fe946 Merge branch 'master' into 542 2019-05-02 20:47:05 -05:00
2a2c0061f1 Merge pull request #534 from arsenetar/504
Fix Issue #504
2019-04-26 22:02:57 -05:00
15bfe059c7 Update Windows Build
Fix the issues with cx_Freeze builds on newer versions of python and
with newer version of PyQt5
- Update .gitignore to ignore .spec files
- Update package.py to use pyInstaller instead of cx_Freeze
- Update requirements-windows to have pyInstaller instead of cx_Freeze
- Update setup.nsi to work with build and packaging changes
- Add win_version_info.temp to build a version information file for
  pyInstaller as part of the package.py script
2019-03-24 21:35:34 -05:00
a1cacbe72b Merge pull request #559 from arsenetar/444
Fix #444
2019-03-21 18:11:56 -05:00
0a5db4c5c1 Merge pull request #558 from arsenetar/505
Fix #505
2019-03-21 18:11:22 -05:00
1b879259a4 Fix #444
Update default INITIAL_FOLDER_IN_DIALOGS to use '/' as it is most
likely available on most unsuppored platforms.
2019-03-21 18:07:00 -05:00
136342f7d0 Fix #505
Remove offending line in package.py
2019-03-21 17:59:33 -05:00
9eb15509c1 Merge pull request #526 from arsenetar/templateUpdate
Update README.md and ISSUE_TEMPLATE.md
2019-02-19 22:18:31 -06:00
dde2c9bf8f Fix Issue #504
- Update qt/preferences_dialog.py to resize based on layout with fixed
  sizing constraints
- Remove _setupUi from qt/se/preferences_dialog.py and just use the
  parent class's function
- Remove unused imports from qt/se/preferences_dialog.py
2018-12-04 21:10:01 -06:00
f7e20c8aa6 Merge pull request #533 from arsenetar/hscommonUpdate
Update hscommon & .travis.yml for Python 3.7
2018-12-04 20:20:36 -06:00
1a04f6ee86 Update to fix Travis Build & flake8 warning
- Fix Travis build by using dist: xenial so python 3.7 is available
- Correct flake8 warning in core/test/app_test.py
2018-12-04 20:16:15 -06:00
bd3d47bf19 Update hscommon & .travis.yml for Python 3.7
- .travis.yml changes from a82ab3c237
- hscommon update from https://github.com/hsoft/hscommon/pull/5
2018-12-04 19:20:18 -06:00
f953bc4af4 Update README.md and ISSUE_TEMPLATE.md
- Update ISSUE_TEMPLATE.md to help obtain better information for bug
reports.
- Update README.md to reflect more recent changes and highlight areas of
need.
2018-10-10 21:12:01 -05:00
10ac536c3b Merge pull request #525 from arsenetar/521
Update qt/result_window.py to fix off-screen issue.
2018-10-10 20:03:03 -05:00
ab9703b86e Update qt/result_window.py
Now previous changes comply with flake8 rules.
2018-10-10 19:48:32 -05:00
79b97311e9 Update qt/result_window.py
Move the result window if fully or partially off-screen.
Fix #521.
Need to verify if this solves #500.
2018-10-08 21:20:09 -05:00
48936b53a8 Merge pull request #514 from pzrrl/master
Updated wrongly translated wording
2018-07-15 10:36:53 -05:00
pzrrl
9bf1887109 Updated wrongly translated wording
"Font size" was wrongly translated for Simplified Chinese.
2018-07-11 14:26:27 +08:00
30b6e5c68d Merge pull request #492 from arsenetar/452+489
Update submodule for hscommon & qtlib
2018-05-12 12:57:11 -05:00
20202d8dfa Update submodule for hscommon & qtlib
Fix issues #452 and #489
2018-02-27 19:23:05 -06:00
6c6271bc69 Include dbm.dumb for windows build (#490)
cx_Freeze was only including dbm.ndbm which is not available on windows.
This should fix hsoft/dupeguru#474
2018-02-26 09:42:14 -05:00
f349f6a9b9 Fix #456 & #461 (#491)
* Update setup.nsi
- Always display all language choices to fix #461
- Stop recursively removing installation directory as that
  is not a safe operation.  This may leave the installation
  directory after uninstallation but should be empty.  Fix #456.

* Update requirements-windows.txt
- Add pypiwin32 for cx-freeze as it was issuing a warning
- Prevent version 5.1.0 of cx-freeze as it produces non-working
  packages for some reason.  Version 5.0.2 and 6.0b1 work fine.

* Update setup to delete correct files

Installer will now delete the files in the installation folder along
with the folder if it is empty.  Bumped minimum cx-Freeze version to
5.1.1 as files are structured differently in older versions.
2018-02-26 09:41:10 -05:00
Virgil Dupras
afe1d4ed2e Current status: unmaintained 2018-02-14 22:42:40 -05:00
DJCrashdummy
c37037ca4a added some lacking translations (#479)
* added some lacking translations

* added some lacking translations
2018-01-23 20:31:49 -05:00
445f51d595 Update qtlib (#460)
Update qtlib to prevent error on multiple calls to close the progress window.  I have not been able to duplicate the issue again, to track down the root cause.
Ref #449
2017-11-16 10:53:30 -05:00
6132d7c211 Fix syntax error in translation file. (#459)
Fix #451
2017-11-16 10:50:17 -05:00
Virgil Dupras
79adbfd4f2 Fix spurious flake8 failures 2017-11-16 10:49:03 -05:00
Virgil Dupras
45b907a529 Enable Travis CI 2017-10-08 20:37:59 -04:00
Virgil Dupras
d5fef949e9 directories: un-recurse get_files() and get_state()
These methods were previously called recursively and it seemed to cause
problems in some cases. The recursive nature of these functions not
bringing any notable advantage and `os.walk()` being of better style
anyway, I removed that recursive nature.

Hopefully fixes #421
2017-10-08 20:35:58 -04:00
auanasgheps
899a42f6a9 Update ui.po (#448)
More translations (not all yet).
I've also revised some of them.
Changed _Last-Translato_r string to my nickname, hope that is okay.
2017-10-03 11:52:26 -04:00
auanasgheps
93a3978747 Update core.po (#447)
All strings should now be translated in Italia.
I've also changed Last-Translator string to my nickname, hope that is okay.
2017-10-03 11:51:17 -04:00
auanasgheps
5d15cd4c97 Update columns.po (#446)
Too much space on line #122
2017-10-03 11:50:41 -04:00
auanasgheps
7936339909 Update columns.po (#445)
Updated missing strings
2017-09-28 19:49:00 -04:00
Virgil Dupras
2f31dc7aab cache_shelve: wrap deletions in try..except in purge_outdated
Hopefully solves #402 and #439.
2017-09-19 13:22:33 -04:00
Virgil Dupras
a6b1e6e9ab Make tox work with non-venv interpreters
Previously, as soon as an interpreter that wasn't the one having been
used for `env` was used by tox, we would get errors because our C
modules wouldn't be built for this interpreter.

The makefile has been changed to make `make modules` interpreter-aware,
thus fixing this problem.
2017-09-19 13:14:11 -04:00
8cd0ef4c2b Initial Update of Windows Packaging (#438)
* Update run.py & .gitignore for windows

- Update run.py to execute on windows as SIGQUIT is not available.
- Update .gitignore to ignore the generate .pyd files
Ref #300, #393

* Update package.py for windows

Add package_windows back into package.py
- Using cx_freeze for freezing installation
- Will be using nsis for actual installer
Tested with python 3.5 64bit on windows 10
Ref #393

* Update makefile for windows (+2 misc)

- Update the makefile to support windows
- Use different bin path in virtualenv
- Use pyd instead of so files
- Tested with Msys2
- Add *.exe to .gitignore
- Fix minor format error in package.py
Ref #393

* Add requirements-windows

Add the requirements-windows.txt
- contains cx-Freeze for bundling
Ref #393

* Add initial setup.nsi

Initial Version of a NSIS installer script
- Multi-user install (install for just one or all)
- Registers uninstaller (more values need to finish up)
- Tested both single and all install / uninstall and works
- Still need to add parameters instead of hardcoded values in some spots
- Need to clean up vendor folders / keys if empty on uninstall
- Need to add the other dupeGuru languages to the language list
- Minor cleanup of script needed as well
Ref #393

* Update setup.nsi

Updates to setup.nsi including:
- Defines from CLI
- Version information (MAJOR, MINOR, PATCH)
- Bits (64 / 32)
- SourcePath (if we wanted something other than build)
- Added extra defines to move application specifics to one location
- Added extra defines for uninstall information
- Added calculation of install size
- Added switching between 64 and 32 bit contexts (need to verify
functionality)
- Updated output file naming
- Added NSIS supported languages which are also supported by dupeGuru
- Added rest of registry keys for uninstall information
- Added missing registry key for installType
- Added removeing Vendor folder and registry key if empty on uninstall

Should be very close to having this installer script ready to integrate
into the package.py script if desired.
Ref #393

* Update README & requirements-windows

Minor update to README to indicate windows is supported.  Add PyQt5 to
requirements-windows.txt to make installation easier.

* Update packaging for windows

- Update package.py to integrate NSIS for windows
- Update makefile to deal with a few additional windows issues
- Add Windows.md to contain specific windows instructions, if we want
this can be merged with README.md
- Minor formatting update to setup.nsi
Ref #393

* Update README & Windows Instructions

- Update the README to include a reference to the Windows instructions.
-  Add some additional notes into Windows Instructions and remove one
incorrect command.
- Update .gitignore to ignore all permutations of env* to allow for
multiple side by side virtual environments (used to build different
versions for windows)
Ref:  #393

* Update Window.md

Fix broken python link and move nsis link for consistency.

* More Details in Windows.md

Update Windows.md including:
- Information on compilier requirements for windows
- Notes about the windows 10 sdk
- Some clarification around some of the steps
- Addition of msys2 links

Going to review this a bit more to polish it up.

Ref #393.
2017-08-28 19:27:17 -04:00
Virgil Dupras
50e26928f4 Fix env creation in Makefile
The --user isn't a good idea at all to workaround Gentoo's patched pip.
2017-08-14 09:26:44 -04:00
Jocelyn Le Sage
84011fb46d Handle OS termination signals. (#425)
* Handle OS termination signals.

* Added comment about why a timer is required to handle OS signals.
2017-06-20 12:04:38 -04:00
Virgil Dupras
8861f6296e Makefile: add NO_VENV option
This option allows us to avoid venv+pip-install operations. We can use
this in situations where we already know we have all dependencies met
(in a Gentoo ebuild, for example...) and wish to avoid useless work and
potential problems.
2017-06-20 11:59:41 -04:00
Virgil Dupras
35ea499857 Make docs installation optional 2017-06-20 11:49:11 -04:00
Virgil Dupras
a82a19e074 Remove cocoa-related code from build.py 2017-03-12 15:00:57 -04:00
Virgil Dupras
e72cf917f1 Fix broken packaging
I forgot to remove references to the now-gone cocoalib folder
2017-03-11 20:46:57 -05:00
Virgil Dupras
245ed0ddec Remove cocoa
The cocoa UI code now lives in dupeguru-cocoa.
2017-03-11 20:41:47 -05:00
Nick Okasinski
f51f94e03d Fix verb tense in README.md (#406) 2017-01-08 22:34:49 -05:00
Virgil Dupras
6a28017c49 v4.0.3 2016-11-25 01:04:31 +00:00
Virgil Dupras
dc6933c90c Fix crash when cleaning picture cache 2016-11-25 00:59:51 +00:00
Virgil Dupras
e0281dd740 Fix previous commit
I forgot to remove a sparkle reference in the build script.
2016-11-23 20:25:32 -05:00
Virgil Dupras
79e99db1d3 cocoa: remove Sparkle
It's a deployment headache. Old sparkle versions generate runtime warnings about security and up to date version requires me to compile on 10.10, but after many tries, it seems that I absolutely need to build on my minimum requirements version which is 10.8. So screw Sparkle.
2016-11-23 19:51:55 -05:00
Virgil Dupras
76cc2000ab Add UI preference to picture cache type under Qt 2016-11-22 02:41:43 +00:00
Virgil Dupras
e4b6e12d4c Update tox warning exception
E305 somehow popped up as a default warning which I don't care about.
2016-11-22 02:39:51 +00:00
Virgil Dupras
c58a4817ca Add shelve-based picture cache implementation
Hopefully, this will fix #394 for real this time, that is, without the
need for a messy python executable ship in the app.
2016-11-15 19:58:18 -05:00
Virgil Dupras
f7adb5f11e Whitespace normalization 2016-11-15 19:57:30 -05:00
Virgil Dupras
c43044ea4c Remove unused imports 2016-11-15 19:56:19 -05:00
Virgil Dupras
cc01e8eb09 Move pe.cache.Cache into its own unit, cache_sqlite
This prepares us for an upcoming alternative cache implementation.
2016-11-13 17:01:20 -05:00
Virgil Dupras
1c20e5c770 v4.0.2 2016-10-09 12:32:04 -04:00
Virgil Dupras
edcff588e2 Update po from code 2016-08-25 21:43:51 -04:00
Virgil Dupras
26aad6e948 Add DESTDIR variable to makefile
That allows us to install in a sandbox.
2016-08-24 22:22:20 -04:00
Virgil Dupras
c303a490ef Make 'make env' a bit more solid
In some context, we don't end up with a bin/pip executable in our venv.
It's better to call pip as a module.
2016-08-24 22:04:58 -04:00
Virgil Dupras
6ed4499a97 v4.0.1 2016-08-24 20:31:58 -04:00
Virgil Dupras
aa7499aa12 Add make install and make uninstall 2016-08-23 23:02:38 -04:00
Virgil Dupras
63558d647a Add 'make srcpkg' 2016-08-23 19:10:03 -04:00
Virgil Dupras
eb3f7d65de Adjust requirements to pytest 3.0 release
pytest-monkeyplus isn't pytest 3.0 compatible yet, so we have to
explicitly forbid pytest 3.0+ in requirements.
2016-08-22 22:16:45 -04:00
Virgil Dupras
ac8a336c4a Fix picture mode's fuzzy block scanner threshold
It was always wrongly set to it's weirld old default 75 threshold.

fixes #387
2016-08-22 21:35:46 -04:00
Virgil Dupras
0206f2fd15 makefile: compile PE extensions without build.py 2016-08-16 22:03:43 -04:00
Virgil Dupras
b41d3f7efc Improve makefile's i18n target
It now uses proper dependencies and directly calls msgfmt.
2016-08-16 20:59:05 -04:00
Virgil Dupras
c43d37582e Fix syntax error in greek po file 2016-08-16 20:55:59 -04:00
Virgil Dupras
30a278719b Moved credits to the root folder
It was a real pain to edit credits files in all languages.
2016-08-16 20:18:49 -04:00
Virgil Dupras
87ef46ca15 Update hscommon and qtlib subrepos
They contain our new greek translation.
2016-08-16 20:01:25 -04:00
1kakarot
9f3ec065ed Added 'el' locale (#382) 2016-08-16 19:59:04 -04:00
Virgil Dupras
e19056048c Clarify the Windows situation in README 2016-08-16 19:33:39 -04:00
Virgil Dupras
76e5817ff3 Add Makefile
I finally took the time to properly learn how to write makefiles. This
was long overdue, but here we go.

Much of the makefile wraps `build.py`, but gradually, we'll extract
stuff from there until the makefile is the main container for build
logic.
2016-08-15 22:54:22 -04:00
Virgil Dupras
20dc2d63fd qt: save prefs on close more predictably
Ticket #379 reports crashes on quit due to `willSavePrefs` being called
when result and details dialogs are already freed. I can't reproduce the
crash, but it's still a bad idea to rely on the timing of
`aboutToQuit()` to launch this process.

This commits uses a more predictable place to emit `willSavePrefs` and
I'm pretty sure it will fix the crash at #379.
2016-08-14 21:11:24 -04:00
Virgil Dupras
28d2aa8197 cocoa: fix crash on load results
During createResultsWindow(), we would initialize the details panel too late.

fixes #380
2016-08-14 20:31:14 -04:00
Virgil Dupras
5be9d537a5 qt: fix broken load results dialog
`QFileDialog.getOpenFileName`, under pyqt5, returns a tuple, not only a
file path.
2016-08-14 20:01:46 -04:00
Virgil Dupras
b97e89d4d8 package.py: use proper prefix for submodules archive file 2016-08-13 20:37:08 -04:00
Virgil Dupras
0f4992de47 package.py: include submodules src in tar.gz
Otherwise, that results in an incomplete source package!
2016-08-13 20:30:24 -04:00
Virgil Dupras
55ad9ef33a Fix qt.platform.BASE_PATH location
It was wrong since it was moved from qt/base.

fixes #378
2016-07-21 20:39:15 -04:00
Virgil Dupras
e69a1764a0 Fix cocoa build script
It wouldn't properly find python 3.5 dylib for linking.
2016-07-01 19:50:19 -04:00
Virgil Dupras
215307df93 Remove this dependency inclusion thing in src packages
It's pointless and wasteful.
2016-07-01 17:12:31 -04:00
Virgil Dupras
3aa99c396b Bump OS X requirements to 10.8 and update README
Because of Sparkle, it's now required to build dupeguru on 10.10+, but with MACOSX_DEPLOYMENT_TARGET, which we now properly set, the results properly runs on 10.8.

This requires a python that has also been compiled with  MACOSX_DEPLOYMENT_TARGET=10.8
2016-07-01 15:36:15 -04:00
Virgil Dupras
9f2c3e7732 Fix failing test on OS X / py35
A 100 recursion limit was too low in that environment.
2016-07-01 15:29:50 -04:00
Eugene San
d660cef245 Update packaging to conform with package unification and few fixes (#372)
* Rename package (dupeguru-se -> dupeguru)
* Update package name in .desktop files and scripts
* Add Ubuntu package building instructions
* Fix build_pe_modules.py
* Add description to package
* Add conflicts dependencies to replace previous versions
* Update python version
* Unify .json configs
* Few cosmetics changes (mainly missing end-lines and images permissions)
2016-06-28 22:39:23 -04:00
Virgil Dupras
bdd404ce0e Remove appscript from OS X requirements
It's not needed anymore.
2016-06-10 09:48:53 -04:00
Virgil Dupras
df9f72d9bf v4.0.0 2016-06-10 09:16:54 -04:00
Virgil Dupras
53bbc5901c Add xenial to the list of supported ubuntu distros 2016-06-10 09:15:20 -04:00
Virgil Dupras
0959f4581e Update to Sparkle 1.14 2016-06-08 13:28:52 -04:00
Virgil Dupras
b1ef3dc8fe Simplify progress report during scanning
We now get less progress feedback, but in exchange, our progress job is
simpler. Previously, our progress bar would often get wonky towards the
end of the scan and I didn't have the energy to debug that.

Besides, people don't care about that level of progress feedback.
2016-06-08 12:29:28 -04:00
Virgil Dupras
334f4dd2ae Increase md5 reading buffer to 1mb
This makes md5 computing faster without using too much memory.
2016-06-08 12:23:10 -04:00
Virgil Dupras
fbdd1d866e Simplify getmatches_by_contents() signature
partial and sizeattr attributes are not needed anymore.
2016-06-08 12:06:08 -04:00
Virgil Dupras
64e86c9ff9 Add ctags config 2016-06-07 21:36:25 -04:00
Virgil Dupras
80f659858c Fail with excplicit message when unable to load results file
Previously, we would simply show an empty results window. Not very
helpful.
2016-06-07 21:34:04 -04:00
Virgil Dupras
ef8f8f0e44 Fix broken tests 2016-06-07 21:32:30 -04:00
Virgil Dupras
b7a7282c2a Fix results loading
The merge operation broke it. It would try to access a result_table that
didn't exist yet.
2016-06-07 16:56:59 -04:00
Virgil Dupras
668821301c Update documentation 2016-06-06 20:48:26 -04:00
Virgil Dupras
13fb06a693 Remove ContentsAusio scan type
It had few uses and had a confusing name. People though it did fuzzy
audio data matching, which it does not.
2016-06-06 17:08:41 -04:00
Virgil Dupras
61b219ff43 flake8 fix 2016-06-06 17:05:48 -04:00
Virgil Dupras
c4aeda0bd0 Clean up README 2016-06-06 17:00:57 -04:00
Virgil Dupras
76f3332d36 Remove windows leftover file 2016-06-06 17:00:38 -04:00
Virgil Dupras
b47b1e11af cocoa: remove inter.app_se
The last remnant of the pre-merge era.
2016-06-06 11:20:45 -04:00
Virgil Dupras
168d94910b cocoa: fix image loading in picture mode details panel
I had broken it during the big merge.
2016-06-06 11:03:09 -04:00
Virgil Dupras
ca3172044f qt: move scan type and app mode selector to the top of the window 2016-06-06 10:29:02 -04:00
Virgil Dupras
f66849b09d Fix tox tests 2016-06-06 10:21:32 -04:00
Virgil Dupras
8c1078aa71 cocoa: merge se/me/pe into one single app
That merge has already been done in core and qt, we're following.

I broke picture scan details panel image loading. Will fix later.
2016-06-05 21:18:48 -04:00
Virgil Dupras
b780816e3c Merge commit 'a65077f871481ca98ce51810751e66f228cb096a'
# Conflicts:
#	build.py
#	core/pe/iphoto_plist.py
2016-06-05 13:18:33 -04:00
Virgil Dupras
fb8a384a6a cocoa: get rid of edition-specific ResultWindow overrides 2016-06-04 21:18:14 -04:00
Virgil Dupras
2be4ae8f65 cocoa: move scan type selector to directory window
Also, use dynamic scan type labels supplied by core.
2016-06-02 21:31:12 -04:00
Virgil Dupras
f8686ffb55 cocoa: remove iPhoto and Aperture support
These apps don't exist anymore.
2016-06-01 22:34:16 -04:00
Virgil Dupras
3093a42553 cocoa: remove iTunes support
It was an unmaintained feature that wasn't working well with recent OS X releases.
2016-06-01 22:12:27 -04:00
Virgil Dupras
83d934fd4f cocoa: make auto-update URLs HTTPS 2016-06-01 21:57:27 -04:00
Virgil Dupras
f3c09c7a8d cocoa: adjust to latest changes
...that is, scanner on-the-fly instantiation and fileclasses/folderclass config move.

We haven't moved the scan type selector in the UI yet.
2016-06-01 21:56:18 -04:00
Virgil Dupras
a65077f871 Merge core_{se,me,pe} into core.{se,me,pe} 2016-05-31 22:32:37 -04:00
Virgil Dupras
d4919054f9 qt: move qt.base units into qt root package 2016-05-31 21:59:31 -04:00
Virgil Dupras
773f6651e6 Merge core_se.app into core.app 2016-05-31 21:43:24 -04:00
Virgil Dupras
9a25670552 qt: merge se.app into base.app 2016-05-31 21:22:50 -04:00
Virgil Dupras
8c9ef3ea29 Re-add the Clear Picture Cache action 2016-05-31 20:55:32 -04:00
Virgil Dupras
7256adb4d4 qt: remove UI testapp 2016-05-31 20:23:09 -04:00
Virgil Dupras
ad45a6e16e Adapt build/package scripts to single-edition 2016-05-31 20:21:07 -04:00
Virgil Dupras
c865f84c16 Merge PE into SE 2016-05-30 22:27:59 -04:00
Virgil Dupras
7d749779f2 qt: merge ME edition into SE
(breaks PE temporarily)

Adds a Standard/Music Application Mode button to SE and thus adds the
ability to run ME scan types in SE. When in Music mode, the
Music-specific results window, details panel and preferences panel will
show up.

All preferences except scan_type become shared between app modes
(changing the pref in a mode changes it in the other mode).

Results Window and Details Panel are now re-created at each scan
operation because they could change their type between two runs.

Preferences panel is instantiated on the fly and discarded after close.

This is a very big merge operation and I'm trying to touch as little
code as possible, sometimes at the cost of elegance. I try to minimize
the breakage that this change brings.
2016-05-29 22:37:38 -04:00
Virgil Dupras
8b878b7b13 core_me: properly set scanner class
It was wrongly instatiating the scanner on startup (we now do it
on-the-fly).
2016-05-29 17:22:46 -04:00
Virgil Dupras
0056f696df refactoring: move fileclasses and folderclass options in app class
Previously, it was in `Directory`.

This will make our job easier for an upcoming SE/ME/PE merge.
2016-05-29 17:15:55 -04:00
Virgil Dupras
abd2f3a9d6 core_pe: fix missing scanner option refactoring 2016-05-29 17:08:54 -04:00
Virgil Dupras
5c57a2a8fc Instantiate Scanner on-the-fly
Previously, it would be instantiated on startup.

This will make our job easier for an upcoming SE/ME/PE merge.
2016-05-29 16:52:07 -04:00
Virgil Dupras
dc76f9744e Update qtlib subrepo 2016-05-29 16:48:44 -04:00
Virgil Dupras
130581db53 Apply flake8 checks to tests 2016-05-29 15:02:39 -04:00
Virgil Dupras
9ed4b7abf0 refactoring: take ignore_list out of Scanner class
It's now `DupeGuru` that holds it and passes it to `get_dupe_groups()`,
the only place where it's actually used in `Scanner`.

This will make the SE/ME/PE merge easier by allowing us to instantiate
the Scanner on-the-fly since it doesn't hold state anymore.
2016-05-29 14:13:19 -04:00
Virgil Dupras
a0a90e8ef8 Update qtlib subrepo 2016-05-28 22:20:42 -04:00
Virgil Dupras
197acbf5b3 qt: move scan_type preference to main window
It leads to better discoverability of dupeguru's options and will make
more sense after the big merge of all editions.
2016-05-28 21:54:25 -04:00
Virgil Dupras
09d5243648 Bump python requirement to v3.4 2016-05-27 19:28:19 -04:00
Virgil Dupras
10169bee9c Update qtlib
This updates `progress_window`, which fixes a bug where the progress
window would be mistakenly shown on starup.

fixes #357
2016-05-25 21:27:48 -04:00
Virgil Dupras
bb8a41f8c5 Do git submodule init/update in bootstrap script 2016-05-25 21:15:56 -04:00
Virgil Dupras
bb1f0f5be6 Convert hscommon, qtlib and cocoalib to submodules
... rather than subtrees. That also represents a small qtlib updates
which needed a code adjustment.
2016-05-25 21:07:30 -04:00
Virgil Dupras
4b6f8b45e2 Fix tox tests
Add new warning to ignores
2016-05-24 22:54:28 -04:00
Virgil Dupras
2ed1b82ecf Push edition-specific scan option listing down to the core
... rather than have each UI layer repeat them.

Did qt, but not cocoa yet.
2016-05-24 22:53:03 -04:00
Virgil Dupras
de9122c3cb Remove obsolete ABOUT_LICENSE
dupeGuru is GPL now
2016-05-24 22:36:37 -04:00
Yichi Zhang
632650b483 Fix some compiler warnings (Cocoa) 2016-04-04 22:08:58 -04:00
Virgil Dupras
c05f01853d Merge remote-tracking branch 'patrick/feature/tox-py35' 2016-01-05 17:21:44 -05:00
Virgil Dupras
15539eb3c5 flake8 fix 2016-01-05 17:16:39 -05:00
Virgil Dupras
b9874cc7ed Add tox instructions in README
Also, remove py33 from tox envlist
2016-01-05 17:14:05 -05:00
Patrick Atamaniuk
13a2868dd2 add py35 to tox environments 2016-01-05 22:15:42 +01:00
Virgil Dupras
abb1345c49 Update FAQ 2015-12-26 09:20:07 -05:00
Virgil Dupras
9c53b2218c Update waf to v1.8.17
This allows us to call it from Python 3.5 without the failures we previously had.
2015-12-25 10:26:56 -05:00
Virgil Dupras
4b3c1e2828 Add Windows/Mac maintainer notice in the README 2015-12-24 20:50:33 -05:00
Virgil Dupras
b64f9f5ec0 Add support for Python 3.5 and pyenv's pythons on OS X 2015-10-27 21:33:41 -04:00
Virgil Dupras
40d9a486e2 Add Spanish and Dutch localizations
Thanks Josep and Kees Duvekot!

Also, made the language selector sorted alphabetically. It was getting
confusing in there.
2015-07-20 13:18:14 -04:00
Virgil Dupras
6930e092e0 Document branching in the repo 2015-07-20 13:02:14 -04:00
Virgil Dupras
6b41223a22 Add missing pl_PL log in cocoalib/qtlib 2015-07-20 12:54:38 -04:00
Virgil Dupras
d15321a8e9 Update locales from transifex
Also, add missing Korean locales from cocoalib/qtlib, which prevented
proper build on OS X.
2015-07-20 12:50:58 -04:00
Virgil Dupras
d6533cbfa2 Update README 2015-07-03 19:41:10 -04:00
503 changed files with 12261 additions and 52317 deletions

5
.ctags Normal file
View File

@@ -0,0 +1,5 @@
-R
--exclude=build
--exclude=env
--exclude=.tox
--python-kinds=-i

24
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,24 @@
# Instructions
1. Provide a short descriptive title for the issue. A good example is 'Results window appears off screen.', a non-optimal example is 'Problem with App'.
2. Please fill out either the 'Bug / Issue' or the 'Feature Request' section. Replace values in ` `.
3. Delete these instructions and the unused sections.
# Bug / issue Report
System Information:
- DupeGuru Version: `version`
- Operating System: `Windows/Linux/OSX` `distribution` `version`
If using the source distribution and building yourself also provide (otherwise remove):
- Python Version: `version ex. 3.6.6` `32/64bit`
- Complier: `gcc/llvm/msvc` `version`
## Description
`Provide a detailed description of the issue to help reproduce it. If it happens after a specific sequence of events provide them here.`
## Debug Log
```
If reporting an error provide the debug log and/or the error message information. If the debug log is short < 40 lines you can provide it here, otherwise attach the text file to this issue.
```
# Feature Requests
`Provide a detailed description of the feature.`

16
.gitignore vendored
View File

@@ -1,25 +1,25 @@
.DS_Store .DS_Store
*.pyc __pycache__
*.so *.so
*.mo *.mo
*.pyd
*.waf* *.waf*
.lock-waf* .lock-waf*
.idea
.tox .tox
/tags
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
*.pyd
*.exe
*.spec

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "qtlib"]
path = qtlib
url = https://github.com/hsoft/qtlib.git
[submodule "hscommon"]
path = hscommon
url = https://github.com/hsoft/hscommon.git

11
.travis.yml Normal file
View File

@@ -0,0 +1,11 @@
sudo: false
dist: xenial
language: python
python:
- "3.4"
- "3.5"
- "3.6"
- "3.7"
install: pip install tox-travis
script: tox

19
CREDITS Normal file
View File

@@ -0,0 +1,19 @@
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.
* 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!

143
Makefile Normal file
View File

@@ -0,0 +1,143 @@
PYTHON ?= python3
PYTHON_VERSION_MINOR := $(shell ${PYTHON} -c "import sys; print(sys.version_info.minor)")
PYRCC5 ?= pyrcc5
REQ_MINOR_VERSION = 4
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 uname -o), Msys)
BIN = Scripts
SO = *.pyd
VENV_OPTIONS =
else
BIN = bin
SO = cpython-3$(PYTHON_VERSION_MINOR)m*.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.
submodules_target = hscommon/__init__.py
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:
${PYTHON} -m compileall ${packages}
reqs :
ifneq ($(shell test $(PYTHON_VERSION_MINOR) -gt $(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; }
# Ensure that submodules are initialized
$(submodules_target) :
git submodule init
git submodule update
env : | $(submodules_target) 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 $@ $<
core/pe/_block.$(SO) : core/pe/modules/block.c core/pe/modules/common.c
$(PYTHON) hscommon/build_ext.py $^ _block
mv _block.$(SO) core/pe
core/pe/_cache.$(SO) : core/pe/modules/cache.c core/pe/modules/common.c
$(PYTHON) hscommon/build_ext.py $^ _cache
mv _cache.$(SO) core/pe
qt/pe/_block_qt.$(SO) : qt/pe/modules/block.c
$(PYTHON) hscommon/build_ext.py $^ _block_qt
mv _block_qt.$(SO) qt/pe
modules : core/pe/_block.$(SO) core/pe/_cache.$(SO) qt/pe/_block_qt.$(SO)
mergepot :
$(VENV_PYTHON) build.py --mergepot
normpo :
$(VENV_PYTHON) build.py --normpo
srcpkg :
./scripts/srcpkg.sh
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 srcpkg normpo mergepot modules i18n reqs run pyc install uninstall all

115
README.md
View File

@@ -1,40 +1,34 @@
# About the "rust" branch
I'm really excited about Rust and I'm trying out a Rust implementation of dupeGuru's bottlenecks
(PE's image comparison).
For fun.
# 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's written mostly in Python 3 and has the peculiarity of using
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer [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's 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/hsoft/dupeguru-cocoa
source tree. You choose the edition you want to build in a `configure.py` flag.
# Current status: People wanted ## Current status: Additional Maintainers Wanted (/ Note on Things in General)
dupeGuru has currently only one maintainer, me. This is a dangerous situation that needs to be When I started contributing to dupeGuru, it was to help provide an updated Windows build for dupeGuru. I hoped to contribute more over time and help work through some of the general issues as well. Since Virgil Dupras left as the lead maintainer, I have not been able to devote enough time to work through as many issues as I had hoped. Now I am going to be devoting a more consistent amount of time each month to work on dupeGuru, however I will not be able to get to all issues. Additionally there are a few specific areas where additional help would be appreciated:
corrected.
The goal is to eventually have another active maintainer, but before we can get there, the project - OSX maintenance
needs more contributors. It is very much lacking on that side right now. - UI issues (I have no experience with cocoa)
- General issues & releases (I lack OSX environments / hardware to develop and test on, looking into doing builds through Travis CI.)
- Linux maintenance
- Packaging (I have not really done much linux packaging yet, although will be spending some time trying to get at least .deb and potentially ppa's updated.)
Whatever your skills, if you are remotely interestested in being a contributor, I'm interested in I am still working to update the new site & update links within the help and the repository to use the new urls. Additionally, hoping to get a 4.0.4 release out this year for at least Windows and Linux.
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 Thanks,
Andrew Senetar
## 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 organised:
* 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
@@ -42,78 +36,47 @@ This folder contains the source for dupeGuru. Its documentation is in `help`, bu
* locale: .po files for localisation. * locale: .po files for localisation.
There are also other sub-folder that comes from external repositories and are part of this repo as There are also other sub-folder that comes from external repositories and are part of this repo as
git subtrees: git submodules:
* 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
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 ### Prerequisites
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 * [Python 3.4+][python]
* PyQt5
and follow instructions from the script. You can then ignore the rest of the build documentation. ### make
## Prerequisites installation dupeGuru is built with "make":
Prerequisites are installed through `pip`. However, some of them are not "pip installable" and have $ make
to be installed manually. $ make run
* All systems: [Python 3.3+][python] and [setuptools][setuptools] ### Generate Ubuntu packages
* Mac OS X: The last XCode to have the 10.7 SDK included. Python 3.4+.
* Windows: Visual Studio 2010, [PyQt 5.0+][pyqt], [cx_Freeze][cxfreeze] and
[Advanced Installer][advinst] (you only need the last two if you want to create an installer)
On Ubuntu (14.04+), the apt-get command to install all pre-requisites is: $ bash -c "pyvenv --system-site-packages env && source env/bin/activate && pip install -r requirements.txt && python3 build.py --clean && python3 package.py"
$ apt-get install python3-dev python3-pyqt5 pyqt5-dev-tools ### Running tests
On Arch, it's: 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`.
$ pacman -S python-pyqt5 If you don't have Tox system-wide, install it in your virtualenv with `pip install tox` and then
run `tox`.
## Setting up the virtual environment 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`
Use Python's built-in `pyvenv` to create a virtual environment in which we're going to install our. [dupeguru]: https://dupeguru.voltaicideas.net/
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 run.py
You can also package dupeGuru into an installable package with:
$ python package.py
[dupeguru]: http://www.hardcoded.net/dupeguru/
[cross-toolkit]: http://www.hardcoded.net/articles/cross-toolkit-software [cross-toolkit]: http://www.hardcoded.net/articles/cross-toolkit-software
[contrib-issue]: https://github.com/hsoft/dupeguru/issues/300 [documentation]: http://dupeguru.voltaicideas.net/help/en/
[documentation]: http://www.hardcoded.net/dupeguru/help/en/
[python]: http://www.python.org/ [python]: http://www.python.org/
[setuptools]: https://pypi.python.org/pypi/setuptools
[pyqt]: http://www.riverbankcomputing.com [pyqt]: http://www.riverbankcomputing.com
[cxfreeze]: http://cx-freeze.sourceforge.net/ [tox]: https://tox.readthedocs.org/en/latest/
[advinst]: http://www.advancedinstaller.com

59
Windows.md Normal file
View File

@@ -0,0 +1,59 @@
## How to build dupeGuru for Windows
### Prerequisites
- [Python 3.5+][python]
- [Visual Studio 2017][vs] or [Visual Studio Build Tools 2017][vsBuildTools] with the Windows 10 SDK
- [nsis][nsis] (for installer creation)
- [msys2][msys2] (for using makefile method)
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.5):
$ py -3.5 -m pip install --upgrade setuptools
More details on setting up python for compiling packages on windows can be found on the [python wiki][pythonWindowsCompilers]
### With build.py (preferred)
To build with a different python version 3.5 vs 3.6 or 32 bit vs 64 bit specify that version instead of -3.5 to the `py` command below. If you want to build additional versions while keeping all virtual environments setup use a different location for each vritual environment.
$ cd <dupeGuru directory>
$ git submodule init
$ git submodule update
$ py -3.5 -m venv .\env
$ .\env\Scripts\activate
$ pip install -r requirements.txt -r requirements-windows.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.5'
$ make run
NOTE: Install PyQt5 & cx-Freeze with requirements-windows.txt into the venv before runing the packaging scripts in the section below.
### 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. 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.
[python]: http://www.python.org/
[nsis]: http://nsis.sourceforge.net/Main_Page
[vs]: https://www.visualstudio.com/downloads/#visual-studio-community-2017
[vsBuildTools]: https://www.visualstudio.com/downloads/#build-tools-for-visual-studio-2017
[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/

View File

@@ -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"

317
build.py
View File

@@ -1,32 +1,21 @@
# 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
import sys
import os import os
import os.path as op import os.path as op
from optparse import OptionParser from optparse import OptionParser
import shutil import shutil
import json
import importlib
import compileall
from setuptools import setup, Extension from setuptools import setup, Extension
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, print_and_do, move_all, fix_qt_resource_file,
get_module_version, move_all, copy_all, OSXAppStructure,
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
collect_stdlib_dependencies, copy
) )
from hscommon import loc from hscommon 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]"
@@ -43,18 +32,6 @@ def parse_args():
'--loc', action='store_true', dest='loc', '--loc', action='store_true', dest='loc',
help="Build only localization" help="Build only localization"
) )
parser.add_option(
'--cocoa-ext', action='store_true', dest='cocoa_ext',
help="Build only Cocoa extensions"
)
parser.add_option(
'--cocoa-compile', action='store_true', dest='cocoa_compile',
help="Build only Cocoa executable"
)
parser.add_option(
'--xibless', action='store_true', dest='xibless',
help="Build only xibless UIs"
)
parser.add_option( parser.add_option(
'--updatepot', action='store_true', dest='updatepot', '--updatepot', action='store_true', dest='updatepot',
help="Generate .pot files from source code." help="Generate .pot files from source code."
@@ -70,130 +47,14 @@ def parse_args():
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
return options return options
def cocoa_compile_command(edition): def build_help():
return '{0} waf configure --edition {1} && {0} waf'.format(sys.executable, edition)
def cocoa_app(edition):
app_path = {
'se': 'build/dupeGuru.app',
'me': 'build/dupeGuru ME.app',
'pe': 'build/dupeGuru PE.app',
}[edition]
return OSXAppStructure(app_path)
def build_xibless(edition, dest='cocoa/autogen'):
import xibless
ensure_folder(dest)
FNPAIRS = [
('ignore_list_dialog.py', 'IgnoreListDialog_UI'),
('deletion_options.py', 'DeletionOptions_UI'),
('problem_dialog.py', 'ProblemDialog_UI'),
('directory_panel.py', 'DirectoryPanel_UI'),
('prioritize_dialog.py', 'PrioritizeDialog_UI'),
('result_window.py', 'ResultWindow_UI'),
('main_menu.py', 'MainMenu_UI'),
('preferences_panel.py', 'PreferencesPanel_UI'),
]
for srcname, dstname in FNPAIRS:
xibless.generate(
op.join('cocoa', 'base', 'ui', srcname), op.join(dest, dstname),
localizationTable='Localizable', args={'edition': edition}
)
if edition == 'pe':
xibless.generate(
'cocoa/pe/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'),
localizationTable='Localizable'
)
else:
xibless.generate(
'cocoa/base/ui/details_panel.py', op.join(dest, 'DetailsPanel_UI'),
localizationTable='Localizable'
)
def build_cocoa(edition, dev):
print("Creating OS X app structure")
ed = lambda s: s.format(edition)
app = cocoa_app(edition)
app_version = get_module_version(ed('core_{}'))
cocoa_project_path = ed('cocoa/{}')
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version)
app.create(op.join('build', 'Info.plist'))
print("Building localizations")
build_localizations('cocoa', edition)
print("Building xibless UIs")
build_cocoalib_xibless()
build_xibless(edition)
print("Building Python extensions")
build_cocoa_proxy_module()
build_cocoa_bridging_interfaces(edition)
print("Building the cocoa layer")
copy_embeddable_python_dylib('build')
pydep_folder = op.join(app.resources, 'py')
if not op.exists(pydep_folder):
os.mkdir(pydep_folder)
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
appscript_pkgs = ['appscript', 'aem', 'mactypes', 'osax']
specific_packages = {
'se': ['core_se'],
'me': ['core_me'] + appscript_pkgs + ['hsaudiotag'],
'pe': ['core_pe'] + appscript_pkgs,
}[edition]
tocopy = [
'core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'objp', 'send2trash'
] + specific_packages
copy_packages(tocopy, pydep_folder, create_links=dev)
sys.path.insert(0, 'build')
extra_deps = None
if edition == 'pe':
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have
# to manually specify it.
extra_deps = ['multiprocessing']
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder, extra_deps=extra_deps)
del sys.path[0]
# Views are not referenced by python code, so they're not found by the collector.
copy_all('build/inter/*.so', op.join(pydep_folder, 'inter'))
if not dev:
# Important: Don't ever run delete_files_with_pattern('*.py') on dev builds because you'll
# be deleting all py files in symlinked folders.
compileall.compile_dir(pydep_folder, force=True, legacy=True)
delete_files_with_pattern(pydep_folder, '*.py')
delete_files_with_pattern(pydep_folder, '__pycache__')
print("Compiling with WAF")
os.chdir('cocoa')
print_and_do(cocoa_compile_command(edition))
os.chdir('..')
app.copy_executable('cocoa/build/dupeGuru')
build_help(edition)
print("Copying resources and frameworks")
image_path = ed('cocoa/{}/dupeguru.icns')
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
app.copy_resources(*resources, use_symlinks=dev)
app.copy_frameworks('build/Python', 'cocoalib/Sparkle.framework')
print("Creating the run.py file")
tmpl = open('cocoa/run_template.py', 'rt').read()
run_contents = tmpl.replace('{{app_path}}', app.dest)
open('run.py', 'wt').write(run_contents)
def build_qt(edition, dev, conf):
print("Building localizations")
build_localizations('qt', edition)
print("Building Qt stuff")
print_and_do("pyrcc5 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py')))
fix_qt_resource_file(op.join('qt', 'base', 'dg_rc.py'))
build_help(edition)
print("Creating the run.py file")
filereplace(op.join('qt', 'run_template.py'), 'run.py', edition=edition)
def build_help(edition):
print("Generating Help") print("Generating Help")
current_path = op.abspath('.') current_path = op.abspath('.')
help_basepath = op.join(current_path, 'help', 'en') help_basepath = op.join(current_path, 'help', 'en')
help_destpath = op.join(current_path, 'build', 'help'.format(edition)) help_destpath = op.join(current_path, 'build', 'help')
changelog_path = op.join(current_path, 'help', 'changelog_{}'.format(edition)) changelog_path = op.join(current_path, 'help', 'changelog')
tixurl = "https://github.com/hsoft/dupeguru/issues/{}" tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
appname = {'se': 'dupeGuru', 'me': 'dupeGuru Music Edition', 'pe': 'dupeGuru Picture Edition'}[edition] confrepl = {'language': 'en'}
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') changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl')
conftmpl = op.join(current_path, 'help', 'conf.tmpl') conftmpl = op.join(current_path, 'help', 'conf.tmpl')
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl) sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl)
@@ -202,61 +63,27 @@ def build_qt_localizations():
loc.compile_all_po(op.join('qtlib', 'locale')) loc.compile_all_po(op.join('qtlib', 'locale'))
loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale') loc.merge_locale_dir(op.join('qtlib', 'locale'), 'locale')
def build_localizations(ui, edition): def build_localizations():
loc.compile_all_po('locale') loc.compile_all_po('locale')
if ui == 'cocoa':
app = cocoa_app(edition)
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'))
locale_dest = op.join(app.resources, 'locale')
elif ui == 'qt':
build_qt_localizations() build_qt_localizations()
locale_dest = op.join('build', 'locale') locale_dest = op.join('build', 'locale')
if op.exists(locale_dest): if op.exists(locale_dest):
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'], op.join('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'], op.join('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', op.join('cocoa', 'inter')]
loc.generate_pot(ui_packages, op.join('locale', 'ui.pot'), ['tr'], merge=(not ISOSX)) loc.generate_pot(ui_packages, op.join('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'], op.join('qtlib', 'locale', 'qtlib.pot'), ['tr'])
if ISOSX:
print("Building cocoalib.pot")
cocoalib_pot = op.join('cocoalib', 'locale', 'cocoalib.pot')
os.remove(cocoalib_pot)
loc.strings2pot(op.join('cocoalib', 'en.lproj', 'cocoalib.strings'), cocoalib_pot)
print("Enhancing ui.pot with Cocoa's strings files")
loc.strings2pot(
op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'),
op.join('locale', 'ui.pot')
)
def build_mergepot(): def build_mergepot():
print("Updating .po files using .pot files") print("Updating .po files using .pot files")
@@ -269,133 +96,55 @@ def build_normpo():
loc.normalize_all_pos(op.join('qtlib', 'locale')) loc.normalize_all_pos(op.join('qtlib', 'locale'))
loc.normalize_all_pos(op.join('cocoalib', 'locale')) loc.normalize_all_pos(op.join('cocoalib', 'locale'))
def build_cocoa_proxy_module(): def build_pe_modules():
print("Building Cocoa Proxy")
import objp.p2o
objp.p2o.generate_python_proxy_code('cocoalib/cocoa/CocoaProxy.h', 'build/CocoaProxy.m')
build_cocoa_ext(
"CocoaProxy", 'cocoalib/cocoa',
[
'cocoalib/cocoa/CocoaProxy.m', 'build/CocoaProxy.m', 'build/ObjP.m',
'cocoalib/HSErrorReportWindow.m', 'cocoa/autogen/HSErrorReportWindow_UI.m'
],
['AppKit', 'CoreServices'],
['cocoalib', 'cocoa/autogen']
)
def build_cocoa_bridging_interfaces(edition):
print("Building Cocoa Bridging Interfaces")
import objp.o2p
import objp.p2o
add_to_pythonpath('cocoa')
add_to_pythonpath('cocoalib')
from cocoa.inter import (
PyGUIObject, GUIObjectView, PyColumns, ColumnsView, PyOutline,
OutlineView, PySelectableList, SelectableListView, PyTable, TableView, PyBaseApp,
PyTextField, ProgressWindowView, PyProgressWindow
)
from inter.deletion_options import PyDeletionOptions, DeletionOptionsView
from inter.details_panel import PyDetailsPanel, DetailsPanelView
from inter.directory_outline import PyDirectoryOutline, DirectoryOutlineView
from inter.prioritize_dialog import PyPrioritizeDialog, PrioritizeDialogView
from inter.prioritize_list import PyPrioritizeList, PrioritizeListView
from inter.problem_dialog import PyProblemDialog
from inter.ignore_list_dialog import PyIgnoreListDialog, IgnoreListDialogView
from inter.result_table import PyResultTable, ResultTableView
from inter.stats_label import PyStatsLabel, StatsLabelView
from inter.app import PyDupeGuruBase, DupeGuruView
appmod = importlib.import_module('inter.app_{}'.format(edition))
allclasses = [
PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp,
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
PyIgnoreListDialog, PyDeletionOptions, PyResultTable, PyStatsLabel, PyDupeGuruBase,
PyTextField, PyProgressWindow, appmod.PyDupeGuru
]
for class_ in allclasses:
objp.o2p.generate_objc_code(class_, 'cocoa/autogen', inherit=True)
allclasses = [
GUIObjectView, ColumnsView, OutlineView, SelectableListView, TableView,
DetailsPanelView, DirectoryOutlineView, PrioritizeDialogView, PrioritizeListView,
IgnoreListDialogView, DeletionOptionsView, ResultTableView, StatsLabelView,
ProgressWindowView, DupeGuruView
]
clsspecs = [objp.o2p.spec_from_python_class(class_) for class_ in allclasses]
objp.p2o.generate_python_proxy_code_from_clsspec(clsspecs, 'build/CocoaViews.m')
build_cocoa_ext('CocoaViews', 'cocoa/inter', ['build/CocoaViews.m', 'build/ObjP.m'])
def build_pe_modules(ui):
print("Building PE Modules") print("Building PE Modules")
exts = [ exts = [
Extension("_block", [op.join('core_pe', 'modules', 'block.c'), op.join('core_pe', 'modules', 'common.c')]), Extension(
Extension("_cache", [op.join('core_pe', 'modules', 'cache.c'), op.join('core_pe', 'modules', 'common.c')]), "_block",
[op.join('core', 'pe', 'modules', 'block.c'), op.join('core', 'pe', 'modules', 'common.c')]
),
Extension(
"_cache",
[op.join('core', 'pe', 'modules', 'cache.c'), op.join('core', 'pe', 'modules', 'common.c')]
),
] ]
if ui == 'qt':
exts.append(Extension("_block_qt", [op.join('qt', 'pe', 'modules', 'block.c')])) 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( setup(
script_args=['build_ext', '--inplace'], script_args=['build_ext', '--inplace'],
ext_modules=exts, ext_modules=exts,
) )
move_all('_block_qt*', op.join('qt', 'pe')) move_all('_block_qt*', op.join('qt', 'pe'))
move_all('_block*', 'core_pe') move_all('_block*', op.join('core', 'pe'))
move_all('_cache*', 'core_pe') move_all('_cache*', op.join('core', 'pe'))
def build_normal(edition, ui, dev, conf): def build_normal():
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui)) print("Building dupeGuru with UI qt")
add_to_pythonpath('.') 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(op.join('qt', 'dg.qrc'), op.join('qt', 'dg_rc.py')))
build_qt(edition, dev, conf) fix_qt_resource_file(op.join('qt', 'dg_rc.py'))
build_help()
def main(): def main():
options = parse_args() options = parse_args()
conf = json.load(open('conf.json'))
edition = conf['edition']
ui = conf['ui']
dev = conf['dev']
if dev:
print("Building in Dev mode")
if options.clean:
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
if op.exists(path):
shutil.rmtree(path)
if not op.exists('build'): if not op.exists('build'):
os.mkdir('build') os.mkdir('build')
if options.doc: if options.doc:
build_help(edition) 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:
build_cocoa_proxy_module()
build_cocoa_bridging_interfaces(edition)
elif options.cocoa_compile:
os.chdir('cocoa')
print_and_do(cocoa_compile_command(edition))
os.chdir('..')
cocoa_app(edition).copy_executable('cocoa/build/dupeGuru')
elif options.xibless:
build_cocoalib_xibless()
build_xibless(edition)
else: else:
build_normal(edition, ui, dev, conf) build_normal()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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-----

View File

@@ -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";

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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'))

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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 (&amp;, &quot;, 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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)]

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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', [])]

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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)

View File

@@ -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())

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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 "ResultWindow.h"
#import "Utils.h"
#import "Consts.h"
#import "PyDupeGuru.h"
@implementation ResultWindow
/* Override */
- (void)initResultColumns
{
HSColumnDef defs[] = {
{@"marked", 26, 26, 26, YES, [NSButtonCell class]},
{@"name", 195, 16, 0, YES, nil},
{@"folder_path", 183, 16, 0, YES, nil},
{@"size", 63, 16, 0, YES, nil},
{@"extension", 40, 16, 0, YES, nil},
{@"mtime", 120, 16, 0, YES, nil},
{@"percentage", 60, 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];
[[table columns] restoreColumns];
}
- (void)setScanOptions
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[model setScanType:n2i([ud objectForKey:@"scanType"])];
[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"])];
int smallFileThreshold = [ud integerForKey:@"smallFileThreshold"]; // In KB
int sizeThreshold = [ud boolForKey:@"ignoreSmallFiles"] ? smallFileThreshold * 1024 : 0; // The py side wants bytes
[model setSizeThreshold:sizeThreshold];
}
@end

View File

@@ -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_se 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.

BIN
cocoa/waf vendored

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More