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

Compare commits

...

111 Commits

Author SHA1 Message Date
Virgil Dupras
152f5f37ce pe v2.9.0 2013-12-22 10:23:54 -05:00
Virgil Dupras
3e42ad8469 Minimum Python version is now 3.3 2013-12-22 09:52:19 -05:00
Virgil Dupras
c7c7a73384 me v6.7.0 2013-12-08 10:34:04 -05:00
Virgil Dupras
47e636e949 Merge branch 'develop' 2013-12-07 11:15:39 -05:00
Virgil Dupras
0562729d8b Improved build script's --clean. 2013-12-07 11:14:59 -05:00
Virgil Dupras
4a36227a18 v3.8.0 2013-12-07 10:57:30 -05:00
Virgil Dupras
28b8b2e415 Sync locs with Transifex 2013-12-07 10:26:01 -05:00
Virgil Dupras
fd82464564 Removed .tx config in hscommon (useless now) 2013-12-07 10:20:13 -05:00
Virgil Dupras
418acf6e5e Merge branch 'regless' into develop
Conflicts:
	cocoa/inter/app.py
	core/app.py
	hscommon/reg.py
	locale/cs/LC_MESSAGES/ui.po
	locale/de/LC_MESSAGES/ui.po
	locale/fr/LC_MESSAGES/ui.po
	locale/hy/LC_MESSAGES/ui.po
	locale/it/LC_MESSAGES/ui.po
	locale/pt_BR/LC_MESSAGES/ui.po
	locale/ru/LC_MESSAGES/ui.po
	locale/ui.pot
	locale/uk/LC_MESSAGES/ui.po
	locale/vi/LC_MESSAGES/ui.po
	locale/zh_CN/LC_MESSAGES/ui.po
	qt/base/app.py
2013-12-07 10:19:31 -05:00
Virgil Dupras
d14d076989 Disable symlink/hardlink option when not relevant (Cocoa)
Fixes #247.
2013-12-06 16:17:04 -05:00
Virgil Dupras
cb8bb5a70e Disable symlink/hardlink option when not relevant (Qt)
When the "Replace with links" option is not enabled, the choice of
symlink or hardlink is irrelevant and causes confusion. Implemented core
mechanism for controlling the enabled state of that option. Also
implemented the Qt interface for it. Cocoa-part is still to be done.

I used this opportunity to greatly enhance documentation of this part of
the code. I'm beginning to like documenting...

Ref #247.
2013-12-06 15:48:01 -05:00
Virgil Dupras
563c9aeff3 Updated README 2013-12-01 11:26:30 -05:00
Virgil Dupras
a0cc1f2e03 Fixed regless cocoa and updated locs 2013-11-30 18:23:42 -05:00
Virgil Dupras
01403a3f92 Removed fairware 2013-11-30 17:54:40 -05:00
Virgil Dupras
7116674663 Improved hscommon docs 2013-11-30 16:13:12 -05:00
Virgil Dupras
b6bc5de79c Improved hscommon docs
TIL sphinx is rather smart about partial class refrences (starting with
a ".")
2013-11-30 12:29:25 -05:00
Virgil Dupras
5a275db67d Improved hscommon doc
* Completed hscommon.gui.table's doc
* Use sphinx.ext.autosummary.
* Moved attribute docstrings directly into properties.
2013-11-30 12:15:03 -05:00
Virgil Dupras
31395d8794 Fix typos in docs 2013-11-28 22:49:26 -05:00
Virgil Dupras
3734bd6f6c Improved hscommon.gui docs
Added docs for Table and Row in hscommon.gui.table.
2013-11-28 22:38:07 -05:00
Virgil Dupras
da06ef8cad Improved hscommon.gui docs 2013-11-24 13:53:52 -05:00
Virgil Dupras
0b00171655 pygettext: explicitly open files as utf-8
When running it through SSH, I couldn't open files with non-ascii chars.
2013-11-24 10:22:05 -05:00
Virgil Dupras
c1cfa86ad1 Make Cmd+A select all folders in the Folder Selection dialog (Cocoa)
Fixes #228.
2013-11-24 10:12:47 -05:00
Virgil Dupras
c34c9562d3 Make non-numeric delta comparison case insensitive
Fixes #239.
2013-11-23 15:31:20 -05:00
Virgil Dupras
0e542577b0 Merge branch 'master' into develop 2013-11-23 12:39:59 -05:00
Virgil Dupras
42be49da83 Fix surrogate-related UnicodeEncodeError on CSV export
Fixes #210.
2013-11-23 12:38:55 -05:00
Virgil Dupras
398ac9b7c6 Greatly improved docs
Added a new scan.rst page, laying out in much more details than before
the inner workings of the scanning process.

Fixes #208, but does much more than that.
2013-11-17 12:03:48 -05:00
Virgil Dupras
508e9a5d94 Reorganized hscommon documentation
Removed hscommon's "docs" folder and moved all documentation directly
into docstrings. Then, in dupeGuru's developer documentation, added
autodoc references to relevant modules.

The result is a much more usable hscommon documentation.
2013-11-16 14:46:34 -05:00
Virgil Dupras
10dbfa9b38 Refactoring: Path API compatibility with pathlib
Refactored dupeGuru to make hscommon.path's API a bit close to pathlib's
API. It's not 100% compatible yet, but it's much better than before.

This is more of a hscommon refactoring than a dupeguru one, but since
duepGuru is the main user of Path, it was the driver behind the
refactoring.

This refactoring also see the introduction of @pathify, which ensure
Path arguments. Previously, we were often unsure of whether the caller
of a function was passing a Path or a str. This problem is now solved
and this allows us to remove hscommon.io, an ill-conceived attempt to
solve that same ambiguity problem.

Fixes #235.
2013-11-16 12:06:16 -05:00
Virgil Dupras
e8c42740cf Fixed tests which were broken 2013-11-10 12:54:35 -05:00
Virgil Dupras
4b6c4f048d Fixed compilation warnings on OS X 2013-11-10 12:41:10 -05:00
Virgil Dupras
7594cccf8c Fixed build on OS X which was broken 2013-11-10 12:39:02 -05:00
Virgil Dupras
1d9573cf6f On OS X, read Exif tags using Cocoa's built-in functionality
This allows for RAW files Exif reading. Fixes #234.
2013-11-10 12:00:16 -05:00
Virgil Dupras
76f45fb5a6 Fixed appdata logic which was broken on OS X. 2013-11-10 11:05:03 -05:00
Virgil Dupras
12cf9b800b Merge branch 'master' into develop 2013-11-09 16:21:59 -05:00
Virgil Dupras
ba7e6494c6 Fixed crash on Dupe Count sorting with Delta + Dupes Only
Fixes #238
2013-11-09 16:20:33 -05:00
Virgil Dupras
72d8160b28 Fix boken tests 2013-11-08 16:45:14 -05:00
Virgil Dupras
6d53511cee Merge branch 'master' into develop 2013-11-08 16:03:35 -05:00
Virgil Dupras
a563327723 Updated cocoalib 2013-10-20 16:01:59 -04:00
Virgil Dupras
096e2bb78a Updated hscommon 2013-10-20 16:01:27 -04:00
Virgil Dupras
8e65f15e1a Fixed core.engine.Match docstring
The way it was set made dupeGuru crash under Python 3.2
2013-10-20 13:33:27 -04:00
Virgil Dupras
9ea9f60e92 Added packaging support for ubuntu 13.10 2013-10-19 14:37:01 -04:00
Virgil Dupras
8efefaf0bf Improved API docs 2013-10-12 13:55:36 -04:00
Virgil Dupras
33d9569427 Refactoring: Created hscommon.desktop
This unit hosts previously awkward UI view methods which weren't related
to the view itself, but to the current desktop environment. These
functions are now at their appropriate place.
2013-10-12 13:54:13 -04:00
Virgil Dupras
2fdfacb34e Docs: Fix ugly nulljob repr in method signatures 2013-10-11 12:15:02 -04:00
Virgil Dupras
97fcf1ffa8 Fixed debian packaging
.so files were included in the source package, which messed up builds
under archs that weren't the same as the srcpkg creator (namely, i386
builds).
2013-09-22 09:38:52 -04:00
Virgil Dupras
350b2c64e0 Fixed nasty crash during PE's Cocoa block scanning
Using PyUnicode_GET_SIZE was obviously wrong, but I'm guessing that the str changes in py3.3 made that wrongness significant...
2013-08-26 07:17:02 -04:00
Virgil Dupras
dcc57a7afb Ah crap, another Cocoa fatal mistake 2013-08-25 17:10:26 -04:00
Virgil Dupras
8b510994ad pe v2.8.0 2013-08-25 10:53:08 -04:00
Virgil Dupras
4a4d1bbfcd Eased "Clear Picture Cache" triggering under Qt
Added a keybinding and added the action to the directories dialog's menu
(it was previously only in the results window's menu). Fixes #230.
2013-08-25 10:47:10 -04:00
Virgil Dupras
78c3c8ec2d Improved dev docs 2013-08-20 22:52:43 -04:00
Virgil Dupras
e99e2b18e0 Call sphinx-build from withing Python instead of a subprocess 2013-08-19 17:43:32 -04:00
Virgil Dupras
ae1283f2e1 se v3.7.1 2013-08-19 16:48:07 -04:00
Virgil Dupras
cc76f3ca87 Fixed SE folder scanning under Cocoa 2013-08-18 21:07:33 -04:00
Virgil Dupras
be8efea081 Fixed folder scanning in SE, which was completely broken
Oops
2013-08-18 20:50:31 -04:00
Virgil Dupras
7e8f9036d8 Began serious code documentation effort
Enabled the autodoc Sphinx extension and started adding docstrings to
classes, methods, etc.. It's quickly becoming quite interesting...
2013-08-18 18:36:09 -04:00
Virgil Dupras
8a8ac027f5 Fixed ME's cocoa interface file, which was broken (again)
The Remove Dead Tracks didn't use the new job system and appscript wasn't properly packaged.
2013-08-18 11:23:20 -04:00
Virgil Dupras
1d9d09fdf7 Fixed ME's cocoa interface file, which was broken
It tried to update JOBID2TITLE from inter.app, but it has moved to core.app.
2013-08-18 10:48:02 -04:00
Virgil Dupras
5dc956870d me v6.6.0 2013-08-18 10:16:39 -04:00
Virgil Dupras
d8f48cbd42 Fixed 32bit Windows packaging for Python 3.3
Python 3.3 is compiled with VS2010, and the old VS2008 pre-requisite
scheme doesn't work anymore. We now do like with 64bit, include the DLLs
directly in the package.
2013-08-17 14:48:36 -04:00
Virgil Dupras
39d24817f6 Added packaging for Ubuntu 13.04 2013-08-17 11:42:19 -04:00
Virgil Dupras
2364e44707 Tweaked bootstrap script so it works on Ubuntu
Ubuntu 13.04 doesn't have the pyvenv command. Instead, it's pyvenv-3.3.
Replaced pyvenv with python3 -m venv.
2013-08-17 11:32:49 -04:00
Virgil Dupras
3e2249bf89 se v3.7.0 2013-08-17 11:13:16 -04:00
Virgil Dupras
38acb6f91c Updated french doc 2013-08-17 10:41:41 -04:00
Virgil Dupras
9bcb28d5e2 Fixed inaccuracies in docs 2013-08-16 18:06:58 -04:00
Virgil Dupras
d0a3f081da Tweaked bootstrap script to work on OS X 2013-08-04 16:18:31 -04:00
Virgil Dupras
d11ec557e7 Added bootstrap script for easy build setup 2013-08-04 15:57:39 -04:00
Virgil Dupras
b9124a497c Added Phan Anh to the credits for the vietnamese loc 2013-08-04 10:19:05 -04:00
Virgil Dupras
502715cfd6 Updated locs from Transifex 2013-08-04 10:18:38 -04:00
Virgil Dupras
e1f532e2fd Fixed broken tests 2013-08-04 09:26:18 -04:00
Virgil Dupras
a71033d9d6 Added a splitter control to the Re-Prioritize dialog
Fixes #224
2013-08-04 09:20:08 -04:00
Virgil Dupras
a15a62f55c Fixed progress under Cocoa which always cancelled the job
Yeah, it's funny, same problem as with Qt, but for different reasons.
2013-08-04 09:11:19 -04:00
Virgil Dupras
2fe5cdcf02 Fixed progress under Qt which always cancelled the job 2013-08-03 21:28:02 -04:00
Virgil Dupras
21c64545e5 Fixed job UI cancellation
It was broken since the modernization.
2013-08-03 18:33:35 -04:00
Virgil Dupras
c93a88f8b0 Fix startup crash with PE
Fixes #232
2013-08-03 18:01:28 -04:00
Virgil Dupras
86a81eab4e Added the Vietnamese language 2013-08-03 17:36:53 -04:00
Virgil Dupras
1c779cb3ec Removed spurious debug code 2013-08-03 17:36:12 -04:00
Virgil Dupras
04949c853d Pulled all locs from Transifex
Vietnamese was added.

There's also updated to Russian and Brazilian.
2013-08-03 17:34:02 -04:00
Virgil Dupras
ff782a09f5 Added the --normpo build option
This build command normalizes all PO so that I stop getting
spurious diffs whenever I pull from Transifex.
2013-08-03 17:13:24 -04:00
Virgil Dupras
e5ce6680ca Modernized progress window GUI
Following the refactoring that has been initiated in pdfmasher's
"vala" branch, I pushed more progress window logic into the
core.

The UI code is now a bit dumber than it used to be, and the core
now directly decides when the progress window is shown and
hidden. The "job finished" notification is also directly sent by the
core. Job description update logic is handled by a core gui
textfield.

Job description contsants also moved to the core, triggering
a localisation migration from "ui" to "core".
2013-08-03 16:27:36 -04:00
Virgil Dupras
8e15d89a2e Updated cocoalib subtree. 2013-08-03 11:12:31 -04:00
Virgil Dupras
d874f26f06 Updated qtlib subtree 2013-08-03 11:06:58 -04:00
Virgil Dupras
80a99ff29e Updated hscommon subtree 2013-08-03 10:59:44 -04:00
Virgil Dupras
b11b97dd7c Improved delta values to support non-numerical values
Delta values now work for non-numerical values. Any column,
when its value differs from its ref, becomes orange.

A column that was already a "delta column" keeps its previous
behavior (dupe cells for these columns are always displayed in
orange).

Sorting behavior, when Dupes Only and Delta Values are enabled
at the same time, has also been extended to non-numerical
values, making it easy to mass-mark dupe rows with orange
values.

Documentation was updated, unit tests were added.

Fixes #213
2013-07-28 17:45:23 -04:00
Virgil Dupras
386a5f2c64 Fixed results display bug under Mac OS X
Since the latest refactoring, results wouldn't display properly.
2013-07-28 16:41:07 -04:00
Virgil Dupras
c13a2f207c Dropped i386 support under Mac OS X. 2013-07-28 16:39:53 -04:00
Virgil Dupras
d36710ef38 Docs: Changelog issue now point to Github 2013-07-28 15:11:39 -04:00
Virgil Dupras
bbc9b003c6 Docs: Changed theme to haiku 2013-07-28 15:09:35 -04:00
Virgil Dupras
3edba28f0b Docs: Added a Developer Guide page 2013-07-28 15:09:17 -04:00
Virgil Dupras
9304f42f69 Use Send2Trash instead of the newly deprecated Send2Trash3k 2013-07-28 11:58:49 -04:00
Virgil Dupras
375963ebfd Docs: Updated F.A.Q 2013-07-28 11:29:36 -04:00
Virgil Dupras
7891fb5396 Refactoring: Moved some code from app.DupeGuru to fs.File.
Moved DupeGuru._get_display_info() to File.get_display_info().
This method used none of the app's global state or methods
and had nothing to do there.
2013-07-14 17:43:58 -04:00
Virgil Dupras
bdd5f0a515 Updated help about symlinks and hardlinks
It now mentions that Windows is supported and under which
conditions it is. Ref #220.
2013-07-14 14:06:01 -04:00
Virgil Dupras
db0901b1de Handle OSError during symlink support check
Under a windows that supports symlinks (Vista+), we still need
proper privileges. If we don't have it, OSError is raised and we
need to correctly handle this case. Ref #220.
2013-07-14 13:59:03 -04:00
Virgil Dupras
9225697053 Added hardlink/symlink support for Windows Vista+.
Fixes #220.
2013-07-14 11:58:49 -04:00
Virgil Dupras
097b949763 Tweaked README 2013-06-24 16:20:05 -04:00
Virgil Dupras
60701c2a5c Fixed package.py --src-pkg
Make it use "git archive" instead of "hg archive".
2013-06-23 09:36:44 -04:00
Virgil Dupras
3ef1281450 Updated README to include clearer build instructions 2013-06-22 21:43:24 -04:00
Virgil Dupras
af4e74a130 Added qtlib repo as a subtree 2013-06-22 21:34:41 -04:00
Virgil Dupras
422fb2670d Added cocoalib as a subtree. 2013-06-22 21:32:48 -04:00
Virgil Dupras
94a469205a Added hscommon repo as a subtree 2013-06-22 21:32:23 -04:00
Virgil Dupras
95623f9b47 Removed submodules. 2013-06-22 21:22:32 -04:00
Virgil Dupras
a65c246a2e Updated README so it talks about git submodules. 2013-06-21 21:37:20 -04:00
Virgil Dupras
045d496a98 Converted repo to Git. 2013-06-21 21:00:52 -04:00
Virgil Dupras
5ed98b3d92 Fixed Cocoa build for ME. 2013-05-18 13:14:04 -04:00
Virgil Dupras
5799e3548b me v6.5.1 2013-05-18 12:19:03 -04:00
Virgil Dupras
b01ed1e9f3 Added tag pe2.7.1 for changeset 3cfae8ce9120 2013-05-05 12:32:49 -04:00
Virgil Dupras
49839b8b8e Fix cocoa build for PE.
appscript and multiprocessing dependencies weren't properly packaged.
2013-05-05 11:37:28 -04:00
Virgil Dupras
500468ed1c Added the --src-pkg packaging option. 2013-05-05 10:47:43 -04:00
Virgil Dupras
de6f50ab12 pe v2.7.1 2013-05-05 10:17:24 -04:00
Virgil Dupras
2c6339f7a2 In PE's EXIF mode, don't match pictures without EXIF timestamp.
[#219 state:fixed]
2013-05-05 10:11:07 -04:00
Virgil Dupras
96c3d63557 Added tag se3.6.1 for changeset 810ab1e1324e 2013-04-28 17:46:59 -04:00
327 changed files with 20266 additions and 2759 deletions

22
.gitignore vendored Normal file
View File

@@ -0,0 +1,22 @@
.DS_Store
*.pyc
*.so
*.mo
*.pyd
*.waf*
.lock-waf*
build
dist
install
installer_tmp-cache
env
cocoa/autogen
/run.py
/conf.json
/cocoa/*/Info.plist
/cocoa/*/build
/qt/base/*_rc.py
/help/*/conf.py
/help/*/changelog.rst

View File

@@ -1,22 +0,0 @@
syntax: glob
.DS_Store
run.py
*.pyc
*.so
*.mo
*.pyd
*.waf*
.lock-waf*
conf.json
build
dist
install
installer_tmp-cache
env
cocoa/autogen
cocoa/*/Info.plist
cocoa/*/build
qt/base/*_rc.py
help/*/conf.py
help/*/changelog.rst

86
.hgtags
View File

@@ -1,86 +0,0 @@
0ef0ca83b49ad009c896f55824189acc932bcf22 se2.8.2
0ef0ca83b49ad009c896f55824189acc932bcf22 me5.6.6
0ef0ca83b49ad009c896f55824189acc932bcf22 pe1.7.8
a8f232f880b6f9ada565d472996a627ebf69b6e9 before-tiger-drop
321d15e818cf9a3f1fc037543090bb2fca2cccd7 me5.7.0
adc73ccd14b1386cb04dee773c53a2d126800e31 se2.9.0
cbcf9c80fee4c908ef2efbf1c143c9e47676c9b2 pe1.8.0
61c4101851bdea3cb37dfb76f0d404c78c7c594c se2.9.1
0e923897a3389331d4ab3debbc40b8dd616199d9 pe1.8.1
2c454eca9ebe93b6cf34916068f828a6a39e3eaf me5.7.1
19e40bab20521d4256acf325dba9b32e95e135c5 pe1.8.2
7b7c5a66ebee4e4b8125330d24fe9ce1a070ff25 se2.9.2
1cef6d39855f85d4be728646bc78b860e6d4e398 pe1.8.3
90ed56ee602666db2f267f73eac6f824347039b5 me5.7.2
4c3cb1e671a333eabde1151c7c6ffb3609cab025 pe1.8.4
0a71306434bca51bea9a5d5ae54fe1bf0e4900d8 pe1.8.5
556baf4a410779e9bbf43129de133e4c4b26d679 pe1.8.6
9149024283959a50fe9a47a5f175b905d1672c19 se2.10.0
388a7e5aef6385e515189f4a15b4c4fed3ae2fcf me5.8.0
27501167e3b9262ecb60c967941294f36d77eb25 pe1.9.0
cb0a860430bacd712820bce426bcf47a4135efe1 se2.10.1
cb0a860430bacd712820bce426bcf47a4135efe1 se2.10.1
f71d405e62badcfdc1b037facaac043cece40ee5 se2.10.1
3742e83edd9eadf44e1a501859f5e2462b1ef6fd me5.8.1
724ff565dd785fb739774588c6ee652cfc0612d5 pe1.9.1
634b66415c6529f46ae4f837318027cc9d70c3b5 before-py3k
2b67955db2b0580a8b0854dc918b6ab0d1fa3b88 se2.11.0
b56fe4dd8c95bca270b078a09e86848df77e2b2d me5.9.0
618a7365457d56fdc6920c70843a244762e2ea00 pe1.10.0
95b3a4b564c6222b414f2b40182dde2bd6d0e8a4 me5.9.1
9735a5218d2b5b3b1e1dfe17f2f874177cf8f61c se2.11.1
dbfee3ee2fa5cbb9e7ab36570659c17cd5b8561f se2.12.0
d3fe0d0dcda1e0bf1100d02f117503d3bf6baacf me5.10.0
b07ac1398703dd358912c1f3d20bd995633db9fe pe1.11.0
96b6aee668398d663b04eafc8d5dae05e18500ee before-fairware
22239f94589baf2a9fad2123045b8a718dbd68f5 se2.12.2
f9cae82a0752191276b24ffb2cc4e4a8afb5d754 me5.10.2
154c8cb6f018d446d88fa099490c900906e86386 pe1.11.2
ca93352ce35184853ad9fcb881935a43a8b1e249 me5.10.3
44f6ff67066c083f79daa18a9d2f1ab909e0a62e me5.10.4
3f71a8f5bf8f6d0729748a27af9163e013723294 pe1.11.3
0056293b0dade8b8230f68c1fe6f0c2d1e0b74d8 se2.12.3
8d12cab3b12b723e3a86d02cf8002731a0f73f95 se3.0.0
778876a8a9787658aa6adf6944b53aebcb7faeea se3.0.1
f1d40b556c01f32c58f9ef9f9acac5b78e01ba7a pe2.0.0
2fd901a516f8cb6b4438491f63f2ebfd52a57c13 me6.0.0
ff43c6d9feb388f103b7857eaa6f7809185f78ec before-pluginbuilder
d274bcb98f2d02b86470a04cd62e718eff33b74f pe2.1.0
77e169f757195c11e9c1c5febeb2db8eb3589510 se3.0.2
97893f37d7d0767b5aedf1b4b40de57ee36d426b se3.1.0
e44d5127ed605daa7a17a01eee65d0a157de20c0 pe2.2.0
ecf9aaa568340e3d03e8854b7556edd5a3285107 pe2.2.1
db1f325c907ffa9808a49cb7bc2886b9fca7aee2 se3.1.1
e62183e907d6177cf0bac354e31afa9546c199e6 se3.1.2
28ba95706dc54ba32b1c0cf4e1e6350515d19ba3 me6.0.2
925847384dcef62a5c3518fc9e5ce42feab2b093 pe2.2.2
383b14d6e8555ed2c8d5552259bc7ce600dfb1d0 before-leopard-drop
a2f7b7302e178f08725a6404ddc28464409510b1 se3.2.0
5a5134a4fa9bb7ca80ae3e32010030f5bd7ba434 me6.1.0
0fd77be57ff716d5c93232e829dc02acec036d7c se3.2.1
3dd08060135b0b9cef70b6f5a81f191ea339c8d5 me6.1.1
4e6cbef6bcdfcc0e56ff9690fbfe1cac1f4b1b09 pe2.3.0
9ea9af1b886cd1adc4f42fd2276cc2b38376eab0 se3.3.0
6e3379be6821bb36d7f0877a17dd6c07aa037b0a se3.3.1
015ba7e2c10d09afb944f387c2a9c97f7eff7571 me6.2.0
8178bda48324461a17118c98634241952c074f29 pe2.4.0
2a96f2fb3ddb6f1e0ae87951586733fc3e4a28a0 se3.3.2
6a08c1205dfe5e537e5c2dc99d05e05d1d3928f6 me6.2.1
a619f313712e2923160b8f90d8250ee0e184c7b9 pe2.4.1
fad463ae749b7189dce92f1e42a57ac4ee03987d se3.3.3
236cf9b690a144392e7e86e7c9749fc834a8b271 me6.3.0
90318f1303858d9d01065d92d78d98b888b38ea0 se3.4.0
93ed33410df2d2f21229a77ae49c83ece2c50a55 pe2.5.0
c153aef25e5c9911f2197d13899591c50cf38ffc se3.4.1
71b7e18613f3790cea18cb0dd8c9c986ce237267 me6.3.1
c3d9f91dc9c9d60f370c72bc211f09be3e4fc18d se3.5.0
254bce83ad6e56c102d69fd603f6845e2324b470 me6.4.0
e772f1de86744999ffbbe5845554417965b1dfba me6.4.1
c8a9a4d355927e509f514308c82306192bc71f92 pe2.6.0
a618e954f01e4bbdbe9a03e5667a67d62be995a7 me6.4.2
0f18c4498a6c7529bf77207db70aed8a5ec96ee4 se3.6.0
8f478379ec62fd1329d527aafb1ab0f2410f3a79 me6.5.0
d773721e6c3260f8130f40b4ab10442edc9965ec pe2.7.0
6b42e0d5628b937aee8039ee34d4b329149718a5 se3.6.0-arch
df6e045b9e7679f2a1949a57060e5c1863904444 me6.5.0-arch
286ba6959cd0af059f245371a3afb52c1da91dee pe2.7.0-arch

View File

@@ -1,5 +1,5 @@
[main] [main]
host = https://www.transifex.net host = https://www.transifex.com
[dupeguru.core] [dupeguru.core]
file_filter = locale/<lang>/LC_MESSAGES/core.po file_filter = locale/<lang>/LC_MESSAGES/core.po

94
README.md Normal file
View File

@@ -0,0 +1,94 @@
# dupeGuru
[dupeGuru][dupeguru] is a cross-platform (Linux, OS X, Windows) GUI tool to find duplicate files in
a system. It's written mostly in Python 3 and has the peculiarity of using
[multiple GUI toolkits][cross-toolkit], all using the same core Python code. On OS X, the UI layer
is written in Objective-C and uses Cocoa. On Linux and Windows, it's written in Python and uses Qt4.
dupeGuru comes in 3 editions (standard, music and picture) which are all buildable from this same
source tree. You choose the edition you want to build in a ``configure.py`` flag.
# Contents of this folder
This folder contains the source for dupeGuru. Its documentation is in ``help``, but is also
[available online][documentation] in its built form. Here's how this source tree is organised:
* core: Contains the core logic code for dupeGuru. It's Python code.
* core_*: Edition-specific-cross-toolkit code written in Python.
* cocoa: UI code for the Cocoa toolkit. It's Objective-C code.
* qt: UI code for the Qt toolkit. It's written in Python and uses PyQt.
* images: Images used by the different UI codebases.
* debian: Skeleton files required to create a .deb package
* help: Help document, written for Sphinx.
* locale: .po files for localisation.
There are also other sub-folder that comes from external repositories and are part of this repo as
git subtrees:
* hscommon: A collection of helpers used across HS applications.
* cocoalib: A collection of helpers used across Cocoa UI codebases of HS applications.
* qtlib: A collection of helpers used across Qt UI codebases of HS applications.
# How to build dupeGuru from source
## The very, very, very easy way
If you're on Linux or Mac, there's a bootstrap script that will make building very, very easy. There
might be some things that you need to install manually on your system, but the bootstrap script will
tell you when what you need to install. You can run the bootstrap with:
./bootstrap.sh
and follow instructions from the script. You can then ignore the rest of the build documentation.
## Prerequisites installation
Prerequisites are installed through `pip`. However, some of them are not "pip installable" and have
to be installed manually.
* All systems: [Python 3.3+][python] and [setuptools][setuptools]
* Mac OS X: The last XCode to have the 10.6 SDK included.
* Windows: Visual Studio 2008, [PyQt 4.7+][pyqt], [cx_Freeze][cxfreeze] and
[Advanced Installer][advinst] (you only need the last two if you want to create an installer)
On Ubuntu, the apt-get command to install all pre-requisites is:
$ apt-get install python3-dev python3-pyqt4 pyqt4-dev-tools python3-setuptools
## Setting up the virtual environment
Use Python's built-in `pyvenv` to create a virtual environment in which we're going to install our.
Python-related dependencies. `pyvenv` is built-in Python but, unlike its `virtualenv` predecessor,
it doesn't install setuptools and pip, so it has to be installed manually:
$ pyvenv --system-site-packages env
$ source env/bin/activate
$ wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py -O - | python
$ easy_install pip
Then, you can install pip requirements in your virtualenv:
$ pip install -r requirements-[osx|win].txt
([osx|win] depends, of course, on your platform. On other platforms, just use requirements.txt).
## Actual building and running
With your virtualenv activated, you can build and run dupeGuru with these commands:
$ python configure.py
$ python build.py
$ python run.py
You can also package dupeGuru into an installable package with:
$ python package.py
[dupeguru]: http://www.hardcoded.net/dupeguru/
[cross-toolkit]: http://www.hardcoded.net/articles/cross-toolkit-software
[documentation]: http://www.hardcoded.net/dupeguru/help/en/
[python]: http://www.python.org/
[setuptools]: https://pypi.python.org/pypi/setuptools
[pyqt]: http://www.riverbankcomputing.com
[cxfreeze]: http://cx-freeze.sourceforge.net/
[advinst]: http://www.advancedinstaller.com

View File

@@ -1,117 +0,0 @@
Contents
========
This package contains the source for dupeGuru. To learn how to build it, refer to the
"Build dupeGuru" section. Below is the description of the various subfolders:
- core: Contains the core logic code for dupeGuru. It's Python code.
- core_*: Edition-specific-cross-toolkit code written in Python.
- cocoa: UI code for the Cocoa toolkit. It's Objective-C code.
- qt: UI code for the Qt toolkit. It's written in Python and uses PyQt.
- images: Images used by the different UI codebases.
- debian: Skeleton files required to create a .deb package
- help: Help document, written for Sphinx.
There are also other sub-folder that comes from external repositories (automatically checked out
as mercurial subrepos):
- hscommon: A collection of helpers used across HS applications.
- cocoalib: A collection of helpers used across Cocoa UI codebases of HS applications.
- qtlib: A collection of helpers used across Qt UI codebases of HS applications.
dupeGuru Dependencies
=====================
Before being able to build dupeGuru, a few dependencies have to be installed. If you use pip, you
will not have to install them all manually (see "The easy way!" below):
General dependencies
--------------------
- Python 3.2 (http://www.python.org)
- Send2Trash3k (http://hg.hardcoded.net/send2trash)
- hsaudiotag3k 1.1.0 (for ME) (http://hg.hardcoded.net/hsaudiotag)
- jobprogress 1.0.3 (http://hg.hardcoded.net/jobprogress)
- Sphinx 1.1 (http://sphinx.pocoo.org/)
- polib 0.7.0 (http://bitbucket.org/izi/polib)
- pytest 2.0.0, to run unit tests. (http://pytest.org/)
OS X prerequisites
------------------
- XCode's command line tools
- objp 1.1.0 (http://bitbucket.org/hsoft/objp)
- appscript 1.0.0 for ME and PE (http://appscript.sourceforge.net/)
- xibless 0.4.0 (https://bitbucket.org/hsoft/xibless)
Windows prerequisites
---------------------
- Visual Studio 2008 (Express is enough) is needed to build C extensions. (http://www.microsoft.com/Express/)
- PyQt 4.7+ (http://www.riverbankcomputing.co.uk/news)
- cx_Freeze, if you want to build a exe. You don't need it if you just want to run dupeGuru. (http://cx-freeze.sourceforge.net/)
- Advanced Installer, if you want to build the installer file. (http://www.advancedinstaller.com/)
Linux prerequisites
-------------------
- PyQt 4.7+ (http://www.riverbankcomputing.co.uk/news)
The easy way!
-------------
There's an easy way to install the majority of the prerequisites above, and it's `pip <http://www.pip-installer.org/>`_ which has recently started to support Python 3. So install it and then run::
pip install -r requirements-[osx|win].txt
([osx|win] depends, of course, on your platform. On other platforms, just use requirements.txt).
Advanced Installer, having nothing to do with Python, needs to be installed manually.
PyQt isn't in the requirements file either (there's no package uploaded on PyPI) and you also have
to install it manually.
If you use a virtualenv (which you should), you have to enable the "site-packages" option because
dupeGuru will need the PyQt library which you'll have installed on your system.
Prerequisite gotchas
--------------------
Correctly installing the prerequisites is tricky. Make sure you have at least the version number
required for each prerequisite.
If you didn't use mercurial to download this source, you probably have an incomplete source folder!
External projects (hscommon, qtlib, cocoalib) need to be at the root of the dupeGuru project folder.
You'll have to download those separately. Or use mercurial, it's much easier.
Another one on OS X: I wouldn't use macports/fink/whatever. Whenever I tried using those, I always
ended up with problems.
Whenever you have a problem, always double-check that you're running the correct python version.
You'll probably have to tweak your $PATH.
To setup a build machine under Ubuntu 12.04 and up, install those packages: python3-dev, python3-pyqt4,
pyqt4-dev-tools, mercurial and then python3-setuptools. Once you've done that, install pip with
`easy_install`. Once you've done that, you can then perform "The easy way!" installation.
Building dupeGuru
=================
First, make sure you meet the dependencies listed in the section above. Then you need to configure
your build with::
python configure.py
If you want, you can specify a UI to use with the ``--ui`` option. So, if you want to build dupeGuru
with Qt on OS X, then you have to type ``python configure.py --ui=qt``. You can also use the
``--dev`` flag to indicate a dev build (mostly useful in OS X, where the python code in symlinked
so you don't have to repackage whenever you make a change in the python code).
Then, just build the thing and then run it with::
python build.py
python run.py
If you want to create ready-to-upload package, run::
python package.py

26
bootstrap.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
command -v python3 -m venv >/dev/null 2>&1 || { echo >&2 "Python 3.3 required. Install it and try again. Aborting"; exit 1; }
if [ ! -d "env" ]; then
echo "No virtualenv. Creating one"
command -v curl >/dev/null 2>&1 || { echo >&2 "curl required. Install it and try again. Aborting"; exit 1; }
python3 -m venv --system-site-packages env
source env/bin/activate
curl https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py | python
easy_install pip
else
echo "There's already an env. Activating it"
source env/bin/activate
fi
echo "Installing pip requirements"
if [ "$(uname)" == "Darwin" ]; then
pip install -r requirements-osx.txt
else
python3 -c "import PyQt4" >/dev/null 2>&1 || { echo >&2 "PyQt 4.8+ required. Install it and try again. Aborting"; exit 1; }
pip install -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"

View File

@@ -19,7 +19,7 @@ from setuptools import setup, Extension
from hscommon import sphinxgen from hscommon import sphinxgen
from hscommon.build import (add_to_pythonpath, print_and_do, copy_packages, filereplace, from hscommon.build import (add_to_pythonpath, print_and_do, copy_packages, filereplace,
get_module_version, move_all, copy_sysconfig_files_for_embed, copy_all, OSXAppStructure, get_module_version, move_all, copy_all, OSXAppStructure,
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib, build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
collect_stdlib_dependencies, copy) collect_stdlib_dependencies, copy)
from hscommon import loc from hscommon import loc
@@ -45,6 +45,8 @@ def parse_args():
help="Generate .pot files from source code.") help="Generate .pot files from source code.")
parser.add_option('--mergepot', action='store_true', dest='mergepot', parser.add_option('--mergepot', action='store_true', dest='mergepot',
help="Update all .po files based on .pot files.") help="Update all .po files based on .pot files.")
parser.add_option('--normpo', action='store_true', dest='normpo',
help="Normalize all PO files (do this before commit).")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
return options return options
@@ -102,20 +104,25 @@ def build_cocoa(edition, dev):
if not op.exists(pydep_folder): if not op.exists(pydep_folder):
os.mkdir(pydep_folder) os.mkdir(pydep_folder)
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build') shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
appscript_pkgs = ['appscript', 'aem', 'mactypes', 'osax']
specific_packages = { specific_packages = {
'se': ['core_se'], 'se': ['core_se'],
'me': ['core_me'], 'me': ['core_me'] + appscript_pkgs + ['hsaudiotag'],
'pe': ['core_pe'], 'pe': ['core_pe'] + appscript_pkgs,
}[edition] }[edition]
tocopy = ['core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'jobprogress', 'objp', tocopy = ['core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'jobprogress', 'objp',
'send2trash'] + specific_packages 'send2trash'] + specific_packages
copy_packages(tocopy, pydep_folder, create_links=dev) copy_packages(tocopy, pydep_folder, create_links=dev)
sys.path.insert(0, 'build') sys.path.insert(0, 'build')
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder) 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] del sys.path[0]
# Views are not referenced by python code, so they're not found by the collector. # 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')) copy_all('build/inter/*.so', op.join(pydep_folder, 'inter'))
copy_sysconfig_files_for_embed(pydep_folder)
if not dev: if not dev:
# Important: Don't ever run delete_files_with_pattern('*.py') on dev builds because you'll # Important: Don't ever run delete_files_with_pattern('*.py') on dev builds because you'll
# be deleting all py files in symlinked folders. # be deleting all py files in symlinked folders.
@@ -127,6 +134,7 @@ def build_cocoa(edition, dev):
print_and_do(cocoa_compile_command(edition)) print_and_do(cocoa_compile_command(edition))
os.chdir('..') os.chdir('..')
app.copy_executable('cocoa/build/dupeGuru') app.copy_executable('cocoa/build/dupeGuru')
build_help(edition)
print("Copying resources and frameworks") print("Copying resources and frameworks")
image_path = ed('cocoa/{}/dupeguru.icns') image_path = ed('cocoa/{}/dupeguru.icns')
resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help'] resources = [image_path, 'cocoa/base/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
@@ -143,6 +151,7 @@ def build_qt(edition, dev, conf):
print("Building Qt stuff") print("Building Qt stuff")
print_and_do("pyrcc4 -py3 {0} > {1}".format(op.join('qt', 'base', 'dg.qrc'), op.join('qt', 'base', 'dg_rc.py'))) print_and_do("pyrcc4 -py3 {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')) fix_qt_resource_file(op.join('qt', 'base', 'dg_rc.py'))
build_help(edition)
print("Creating the run.py file") print("Creating the run.py file")
filereplace(op.join('qt', 'run_template.py'), 'run.py', edition=edition) filereplace(op.join('qt', 'run_template.py'), 'run.py', edition=edition)
@@ -152,7 +161,7 @@ def build_help(edition):
help_basepath = op.join(current_path, 'help', 'en') help_basepath = op.join(current_path, 'help', 'en')
help_destpath = op.join(current_path, 'build', 'help'.format(edition)) help_destpath = op.join(current_path, 'build', 'help'.format(edition))
changelog_path = op.join(current_path, 'help', 'changelog_{}'.format(edition)) changelog_path = op.join(current_path, 'help', 'changelog_{}'.format(edition))
tixurl = "https://hardcoded.lighthouseapp.com/projects/31699-dupeguru/tickets/{0}" tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
appname = {'se': 'dupeGuru', 'me': 'dupeGuru Music Edition', 'pe': 'dupeGuru Picture Edition'}[edition] appname = {'se': 'dupeGuru', 'me': 'dupeGuru Music Edition', 'pe': 'dupeGuru Picture Edition'}[edition]
homepage = 'http://www.hardcoded.net/dupeguru{}/'.format('_' + edition if edition != 'se' else '') homepage = 'http://www.hardcoded.net/dupeguru{}/'.format('_' + edition if edition != 'se' else '')
confrepl = {'edition': edition, 'appname': appname, 'homepage': homepage, 'language': 'en'} confrepl = {'edition': edition, 'appname': appname, 'homepage': homepage, 'language': 'en'}
@@ -160,17 +169,12 @@ def build_help(edition):
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)
def build_base_localizations():
loc.compile_all_po('locale')
loc.compile_all_po(op.join('hscommon', 'locale'))
loc.merge_locale_dir(op.join('hscommon', 'locale'), 'locale')
def build_qt_localizations(): 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(ui, edition):
build_base_localizations() loc.compile_all_po('locale')
if ui == 'cocoa': if ui == 'cocoa':
app = cocoa_app(edition) app = cocoa_app(edition)
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings')) loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'base', 'en.lproj', 'Localizable.strings'))
@@ -212,8 +216,6 @@ def build_updatepot():
# 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=(not ISOSX))
print("Building hscommon.pot")
loc.generate_pot(['hscommon'], op.join('hscommon', 'locale', 'hscommon.pot'), ['tr'])
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: if ISOSX:
@@ -228,10 +230,14 @@ def build_updatepot():
def build_mergepot(): def build_mergepot():
print("Updating .po files using .pot files") print("Updating .po files using .pot files")
loc.merge_pots_into_pos('locale') loc.merge_pots_into_pos('locale')
loc.merge_pots_into_pos(op.join('hscommon', 'locale'))
loc.merge_pots_into_pos(op.join('qtlib', 'locale')) loc.merge_pots_into_pos(op.join('qtlib', 'locale'))
loc.merge_pots_into_pos(op.join('cocoalib', 'locale')) loc.merge_pots_into_pos(op.join('cocoalib', 'locale'))
def build_normpo():
loc.normalize_all_pos('locale')
loc.normalize_all_pos(op.join('qtlib', 'locale'))
loc.normalize_all_pos(op.join('cocoalib', 'locale'))
def build_cocoa_proxy_module(): def build_cocoa_proxy_module():
print("Building Cocoa Proxy") print("Building Cocoa Proxy")
import objp.p2o import objp.p2o
@@ -249,7 +255,8 @@ def build_cocoa_bridging_interfaces(edition):
add_to_pythonpath('cocoa') add_to_pythonpath('cocoa')
add_to_pythonpath('cocoalib') add_to_pythonpath('cocoalib')
from cocoa.inter import (PyGUIObject, GUIObjectView, PyColumns, ColumnsView, PyOutline, from cocoa.inter import (PyGUIObject, GUIObjectView, PyColumns, ColumnsView, PyOutline,
OutlineView, PySelectableList, SelectableListView, PyTable, TableView, PyBaseApp, PyFairware) OutlineView, PySelectableList, SelectableListView, PyTable, TableView, PyBaseApp,
PyTextField, ProgressWindowView, PyProgressWindow)
from inter.deletion_options import PyDeletionOptions, DeletionOptionsView from inter.deletion_options import PyDeletionOptions, DeletionOptionsView
from inter.details_panel import PyDetailsPanel, DetailsPanelView from inter.details_panel import PyDetailsPanel, DetailsPanelView
from inter.directory_outline import PyDirectoryOutline, DirectoryOutlineView from inter.directory_outline import PyDirectoryOutline, DirectoryOutlineView
@@ -261,15 +268,16 @@ def build_cocoa_bridging_interfaces(edition):
from inter.stats_label import PyStatsLabel, StatsLabelView from inter.stats_label import PyStatsLabel, StatsLabelView
from inter.app import PyDupeGuruBase, DupeGuruView from inter.app import PyDupeGuruBase, DupeGuruView
appmod = importlib.import_module('inter.app_{}'.format(edition)) appmod = importlib.import_module('inter.app_{}'.format(edition))
allclasses = [PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp, PyFairware, allclasses = [PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp,
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog, PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
PyIgnoreListDialog, PyDeletionOptions, PyResultTable, PyStatsLabel, PyDupeGuruBase, PyIgnoreListDialog, PyDeletionOptions, PyResultTable, PyStatsLabel, PyDupeGuruBase,
appmod.PyDupeGuru] PyTextField, PyProgressWindow, appmod.PyDupeGuru]
for class_ in allclasses: for class_ in allclasses:
objp.o2p.generate_objc_code(class_, 'cocoa/autogen', inherit=True) objp.o2p.generate_objc_code(class_, 'cocoa/autogen', inherit=True)
allclasses = [GUIObjectView, ColumnsView, OutlineView, SelectableListView, TableView, allclasses = [GUIObjectView, ColumnsView, OutlineView, SelectableListView, TableView,
DetailsPanelView, DirectoryOutlineView, PrioritizeDialogView, PrioritizeListView, DetailsPanelView, DirectoryOutlineView, PrioritizeDialogView, PrioritizeListView,
IgnoreListDialogView, DeletionOptionsView, ResultTableView, StatsLabelView, DupeGuruView] IgnoreListDialogView, DeletionOptionsView, ResultTableView, StatsLabelView,
ProgressWindowView, DupeGuruView]
clsspecs = [objp.o2p.spec_from_python_class(class_) for class_ in allclasses] clsspecs = [objp.o2p.spec_from_python_class(class_) for class_ in allclasses]
objp.p2o.generate_python_proxy_code_from_clsspec(clsspecs, 'build/CocoaViews.m') objp.p2o.generate_python_proxy_code_from_clsspec(clsspecs, 'build/CocoaViews.m')
build_cocoa_ext('CocoaViews', 'cocoa/inter', ['build/CocoaViews.m', 'build/ObjP.m']) build_cocoa_ext('CocoaViews', 'cocoa/inter', ['build/CocoaViews.m', 'build/ObjP.m'])
@@ -301,7 +309,6 @@ def build_pe_modules(ui):
def build_normal(edition, ui, dev, conf): def build_normal(edition, ui, dev, conf):
print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui)) print("Building dupeGuru {0} with UI {1}".format(edition.upper(), ui))
add_to_pythonpath('.') add_to_pythonpath('.')
build_help(edition)
print("Building dupeGuru") print("Building dupeGuru")
if edition == 'pe': if edition == 'pe':
build_pe_modules(ui) build_pe_modules(ui)
@@ -319,8 +326,9 @@ def main():
if dev: if dev:
print("Building in Dev mode") print("Building in Dev mode")
if options.clean: if options.clean:
if op.exists('build'): for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
shutil.rmtree('build') if op.exists(path):
shutil.rmtree(path)
if not op.exists('build'): if not op.exists('build'):
os.mkdir('build') os.mkdir('build')
if options.doc: if options.doc:
@@ -331,6 +339,8 @@ def main():
build_updatepot() build_updatepot()
elif options.mergepot: elif options.mergepot:
build_mergepot() build_mergepot()
elif options.normpo:
build_normpo()
elif options.cocoa_ext: elif options.cocoa_ext:
build_cocoa_proxy_module() build_cocoa_proxy_module()
build_cocoa_bridging_interfaces(edition) build_cocoa_bridging_interfaces(edition)

View File

@@ -13,8 +13,9 @@ http://www.hardcoded.net/licenses/bsd_license
#import "DetailsPanel.h" #import "DetailsPanel.h"
#import "DirectoryPanel.h" #import "DirectoryPanel.h"
#import "IgnoreListDialog.h" #import "IgnoreListDialog.h"
#import "HSFairwareAboutBox.h" #import "HSAboutBox.h"
#import "HSRecentFiles.h" #import "HSRecentFiles.h"
#import "HSProgressWindow.h"
@interface AppDelegateBase : NSObject @interface AppDelegateBase : NSObject
{ {
@@ -27,8 +28,9 @@ http://www.hardcoded.net/licenses/bsd_license
DirectoryPanel *_directoryPanel; DirectoryPanel *_directoryPanel;
DetailsPanel *_detailsPanel; DetailsPanel *_detailsPanel;
IgnoreListDialog *_ignoreListDialog; IgnoreListDialog *_ignoreListDialog;
HSProgressWindow *_progressWindow;
NSWindowController *_preferencesPanel; NSWindowController *_preferencesPanel;
HSFairwareAboutBox *_aboutBox; HSAboutBox *_aboutBox;
HSRecentFiles *_recentResults; HSRecentFiles *_recentResults;
} }
@@ -71,6 +73,4 @@ http://www.hardcoded.net/licenses/bsd_license
/* model --> view */ /* model --> view */
- (void)showMessage:(NSString *)msg; - (void)showMessage:(NSString *)msg;
- (void)setupAsRegistered;
- (void)showDemoNagWithPrompt:(NSString *)prompt;
@end @end

View File

@@ -8,7 +8,6 @@ http://www.hardcoded.net/licenses/bsd_license
#import "AppDelegateBase.h" #import "AppDelegateBase.h"
#import "ProgressController.h" #import "ProgressController.h"
#import "HSFairwareReminder.h"
#import "HSPyUtil.h" #import "HSPyUtil.h"
#import "Consts.h" #import "Consts.h"
#import "Dialogs.h" #import "Dialogs.h"
@@ -74,6 +73,8 @@ http://www.hardcoded.net/licenses/bsd_license
_directoryPanel = [self createDirectoryPanel]; _directoryPanel = [self createDirectoryPanel];
_detailsPanel = [self createDetailsPanel]; _detailsPanel = [self createDetailsPanel];
_ignoreListDialog = [[IgnoreListDialog alloc] initWithPyRef:[model ignoreListDialog]]; _ignoreListDialog = [[IgnoreListDialog alloc] initWithPyRef:[model ignoreListDialog]];
_progressWindow = [[HSProgressWindow alloc] initWithPyRef:[[self model] progressWindow] view:nil];
[_progressWindow setParentWindow:[_resultWindow window]];
_aboutBox = nil; // Lazily loaded _aboutBox = nil; // Lazily loaded
_preferencesPanel = nil; // Lazily loaded _preferencesPanel = nil; // Lazily loaded
[[[self directoryPanel] window] makeKeyAndOrderFront:self]; [[[self directoryPanel] window] makeKeyAndOrderFront:self];
@@ -138,7 +139,7 @@ http://www.hardcoded.net/licenses/bsd_license
[op setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]]; [op setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
[op setTitle:NSLocalizedString(@"Select a results file to load", @"")]; [op setTitle:NSLocalizedString(@"Select a results file to load", @"")];
if ([op runModal] == NSOKButton) { if ([op runModal] == NSOKButton) {
NSString *filename = [[op filenames] objectAtIndex:0]; NSString *filename = [[[op URLs] objectAtIndex:0] path];
[model loadResultsFrom:filename]; [model loadResultsFrom:filename];
[[self recentResults] addFile:filename]; [[self recentResults] addFile:filename];
} }
@@ -160,7 +161,7 @@ http://www.hardcoded.net/licenses/bsd_license
- (void)showAboutBox - (void)showAboutBox
{ {
if (_aboutBox == nil) { if (_aboutBox == nil) {
_aboutBox = [[HSFairwareAboutBox alloc] initWithApp:model]; _aboutBox = [[HSAboutBox alloc] initWithApp:model];
} }
[[_aboutBox window] makeKeyAndOrderFront:nil]; [[_aboutBox window] makeKeyAndOrderFront:nil];
} }
@@ -197,8 +198,6 @@ http://www.hardcoded.net/licenses/bsd_license
/* Delegate */ /* Delegate */
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{ {
[[ProgressController mainProgressController] setWorker:model];
[model initialRegistrationSetup];
[model loadSession]; [model loadSession];
} }
@@ -260,16 +259,6 @@ http://www.hardcoded.net/licenses/bsd_license
[[self resultWindow] showProblemDialog]; [[self resultWindow] showProblemDialog];
} }
- (void)setupAsRegistered
{
// Nothing to do.
}
- (void)showDemoNagWithPrompt:(NSString *)prompt
{
[HSFairwareReminder showDemoNagWithApp:[self model] prompt:prompt];
}
- (NSString *)selectDestFolderWithPrompt:(NSString *)prompt - (NSString *)selectDestFolderWithPrompt:(NSString *)prompt
{ {
NSOpenPanel *op = [NSOpenPanel openPanel]; NSOpenPanel *op = [NSOpenPanel openPanel];
@@ -279,7 +268,7 @@ http://www.hardcoded.net/licenses/bsd_license
[op setAllowsMultipleSelection:NO]; [op setAllowsMultipleSelection:NO];
[op setTitle:prompt]; [op setTitle:prompt];
if ([op runModal] == NSOKButton) { if ([op runModal] == NSOKButton) {
return [[op filenames] objectAtIndex:0]; return [[[op URLs] objectAtIndex:0] path];
} }
else { else {
return nil; return nil;
@@ -293,7 +282,7 @@ http://www.hardcoded.net/licenses/bsd_license
[sp setAllowedFileTypes:[NSArray arrayWithObject:extension]]; [sp setAllowedFileTypes:[NSArray arrayWithObject:extension]];
[sp setTitle:prompt]; [sp setTitle:prompt];
if ([sp runModal] == NSOKButton) { if ([sp runModal] == NSOKButton) {
return [sp filename]; return [[sp URL] path];
} }
else { else {
return nil; return nil;

View File

@@ -64,4 +64,9 @@ http://www.hardcoded.net/licenses/bsd_license
[[self window] close]; [[self window] close];
return r == NSOKButton; return r == NSOKButton;
} }
- (void)setHardlinkOptionEnabled:(BOOL)enabled
{
[linkTypeRadio setEnabled:enabled];
}
@end @end

View File

@@ -16,4 +16,6 @@ http://www.hardcoded.net/licenses/bsd_license
@interface DirectoryOutline : HSOutline {} @interface DirectoryOutline : HSOutline {}
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView; - (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView;
- (PyDirectoryOutline *)model; - (PyDirectoryOutline *)model;
- (void)selectAll;
@end; @end;

View File

@@ -22,6 +22,12 @@ http://www.hardcoded.net/licenses/bsd_license
return (PyDirectoryOutline *)model; return (PyDirectoryOutline *)model;
} }
/* Public */
- (void)selectAll
{
[[self model] selectAll];
}
/* Delegate */ /* Delegate */
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index - (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{ {

View File

@@ -46,4 +46,6 @@ http://www.hardcoded.net/licenses/bsd_license
- (void)addDirectory:(NSString *)directory; - (void)addDirectory:(NSString *)directory;
- (void)refreshRemoveButtonText; - (void)refreshRemoveButtonText;
- (void)markAll;
@end @end

View File

@@ -91,8 +91,8 @@ http://www.hardcoded.net/licenses/bsd_license
[op setTitle:NSLocalizedString(@"Select a folder to add to the scanning list", @"")]; [op setTitle:NSLocalizedString(@"Select a folder to add to the scanning list", @"")];
[op setDelegate:self]; [op setDelegate:self];
if ([op runModal] == NSOKButton) { if ([op runModal] == NSOKButton) {
for (NSString *directory in [op filenames]) { for (NSURL *directoryURL in [op URLs]) {
[self addDirectory:directory]; [self addDirectory:[directoryURL path]];
} }
} }
} }
@@ -158,6 +158,14 @@ http://www.hardcoded.net/licenses/bsd_license
} }
} }
- (void)markAll
{
/* markAll isn't very descriptive of what we do, but since we re-use the Mark All button from
the result window, we don't have much choice.
*/
[outline selectAll];
}
/* Delegate */ /* Delegate */
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path - (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
{ {
@@ -171,6 +179,14 @@ http://www.hardcoded.net/licenses/bsd_license
[self addDirectory:path]; [self addDirectory:path];
} }
- (BOOL)validateMenuItem:(NSMenuItem *)item
{
if ([item action] == @selector(markAll)) {
[item setTitle:NSLocalizedString(@"Select All", @"")];
}
return YES;
}
/* Notifications */ /* Notifications */
- (void)directorySelectionChanged:(NSNotification *)aNotification - (void)directorySelectionChanged:(NSNotification *)aNotification

View File

@@ -13,7 +13,6 @@ http://www.hardcoded.net/licenses/bsd_license
@interface ResultTable : HSTable <QLPreviewPanelDataSource, QLPreviewPanelDelegate> @interface ResultTable : HSTable <QLPreviewPanelDataSource, QLPreviewPanelDelegate>
{ {
NSSet *_deltaColumns;
} }
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView; - (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView;
- (PyResultTable *)model; - (PyResultTable *)model;

View File

@@ -20,16 +20,9 @@ http://www.hardcoded.net/licenses/bsd_license
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView - (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView
{ {
self = [super initWithPyRef:aPyRef wrapperClass:[PyResultTable class] callbackClassName:@"ResultTableView" view:aTableView]; self = [super initWithPyRef:aPyRef wrapperClass:[PyResultTable class] callbackClassName:@"ResultTableView" view:aTableView];
_deltaColumns = [[NSSet setWithArray:[[self model] deltaColumns]] retain];
return self; return self;
} }
- (void)dealloc
{
[_deltaColumns release];
[super dealloc];
}
- (PyResultTable *)model - (PyResultTable *)model
{ {
return (PyResultTable *)model; return (PyResultTable *)model;
@@ -132,10 +125,8 @@ http://www.hardcoded.net/licenses/bsd_license
color = [NSColor selectedTextColor]; color = [NSColor selectedTextColor];
} }
else if (isMarkable) { else if (isMarkable) {
if ([self deltaValuesMode]) { if ([[self model] isDeltaAtRow:row column:[column identifier]]) {
if ([_deltaColumns containsObject:[column identifier]]) { color = [NSColor orangeColor];
color = [NSColor orangeColor];
}
} }
} }
else { else {

View File

@@ -42,9 +42,6 @@ http://www.hardcoded.net/licenses/bsd_license
[matches setTarget:self]; [matches setTarget:self];
[matches setDoubleAction:@selector(openClicked)]; [matches setDoubleAction:@selector(openClicked)];
[self adjustUIToLocalization]; [self adjustUIToLocalization];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobStarted:) name:JobStarted object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobInProgress:) name:JobInProgress object:nil];
return self; return self;
} }
@@ -261,8 +258,8 @@ http://www.hardcoded.net/licenses/bsd_license
[sp setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]]; [sp setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
[sp setTitle:NSLocalizedString(@"Select a file to save your results to", @"")]; [sp setTitle:NSLocalizedString(@"Select a file to save your results to", @"")];
if ([sp runModal] == NSOKButton) { if ([sp runModal] == NSOKButton) {
[model saveResultsAs:[sp filename]]; [model saveResultsAs:[[sp URL] path]];
[[app recentResults] addFile:[sp filename]]; [[app recentResults] addFile:[[sp URL] path]];
} }
} }
@@ -340,22 +337,6 @@ http://www.hardcoded.net/licenses/bsd_license
previewPanel = nil; previewPanel = nil;
} }
- (void)jobInProgress:(NSNotification *)aNotification
{
[Dialogs showMessage:NSLocalizedString(@"A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again.", @"")];
}
- (void)jobStarted:(NSNotification *)aNotification
{
[[self window] makeKeyAndOrderFront:nil];
NSDictionary *ui = [aNotification userInfo];
NSString *desc = [ui valueForKey:@"desc"];
[[ProgressController mainProgressController] setJobDesc:desc];
NSString *jobid = [ui valueForKey:@"jobid"];
[[ProgressController mainProgressController] setJobId:jobid];
[[ProgressController mainProgressController] showSheetForParent:[self window]];
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem - (BOOL)validateToolbarItem:(NSToolbarItem *)theItem
{ {
return ![[ProgressController mainProgressController] isShown]; return ![[ProgressController mainProgressController] isShown];
@@ -363,6 +344,9 @@ http://www.hardcoded.net/licenses/bsd_license
- (BOOL)validateMenuItem:(NSMenuItem *)item - (BOOL)validateMenuItem:(NSMenuItem *)item
{ {
if ([item action] == @selector(markAll)) {
[item setTitle:NSLocalizedString(@"Mark All", @"")];
}
return ![[ProgressController mainProgressController] isShown]; return ![[ProgressController mainProgressController] isShown];
} }
@end @end

View File

@@ -1,6 +1,5 @@
"%@ Results" = "%@ Results"; "%@ Results" = "%@ Results";
"A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again." = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again.";
"About dupeGuru" = "About dupeGuru"; "About dupeGuru" = "About dupeGuru";
"Action" = "Action"; "Action" = "Action";
"Actions" = "Actions"; "Actions" = "Actions";

View File

@@ -5,11 +5,12 @@ result = Window(610, 400, "Re-Prioritize duplicates")
promptLabel = Label(result, "Add criteria to the right box and click OK to send the dupes that " 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 " "correspond the best to these criteria to their respective group's reference position. Read "
"the help file for more information.") "the help file for more information.")
categoryPopup = Popup(result) split = SplitView(result, 2, vertical=True)
criteriaTable = ListView(result) categoryPopup = Popup(split.subviews[0])
prioritizationTable = ListView(result) criteriaTable = ListView(split.subviews[0])
addButton = Button(result, NLSTR("-->")) prioritizationTable = ListView(split.subviews[1])
removeButton = Button(result, NLSTR("<--")) addButton = Button(split.subviews[1], NLSTR("-->"))
removeButton = Button(split.subviews[1], NLSTR("<--"))
okButton = Button(result, "Ok") okButton = Button(result, "Ok")
cancelButton = Button(result, "Cancel") cancelButton = Button(result, "Cancel")
@@ -27,24 +28,38 @@ cancelButton.action = Action(owner, 'cancel')
okButton.keyEquivalent = '\\r' okButton.keyEquivalent = '\\r'
cancelButton.keyEquivalent = '\\e' 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 promptLabel.height *= 3 # 3 lines
leftLayout = VLayout([categoryPopup, criteriaTable], width=262, filler=criteriaTable) leftLayout = VLayout([categoryPopup, criteriaTable], filler=criteriaTable)
middleLayout = VLayout([addButton, removeButton], width=41) middleLayout = VLayout([addButton, removeButton], width=41)
buttonLayout = HLayout([None, cancelButton, okButton]) 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.packToCorner(Pack.UpperLeft)
promptLabel.fill(Pack.Right) promptLabel.fill(Pack.Right)
leftLayout.packRelativeTo(promptLabel, Pack.Below) split.moveNextTo(promptLabel, Pack.Below)
middleLayout.packRelativeTo(leftLayout, Pack.Right, align=Pack.Above) buttonLayout.moveNextTo(split, Pack.Below)
prioritizationTable.packRelativeTo(middleLayout, Pack.Right, align=Pack.Above)
buttonLayout.packRelativeTo(leftLayout, Pack.Below)
buttonLayout.fill(Pack.Right) buttonLayout.fill(Pack.Right)
leftLayout.fill(Pack.Below) split.fill(Pack.LowerRight)
middleLayout.packRelativeTo(leftLayout, Pack.Right, align=Pack.Middle)
prioritizationTable.fill(Pack.Below, goal=leftLayout.y)
prioritizationTable.fill(Pack.Right)
promptLabel.setAnchor(Pack.UpperLeft, growX=True) promptLabel.setAnchor(Pack.UpperLeft, growX=True)
prioritizationTable.setAnchor(Pack.UpperLeft, growX=True, growY=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) buttonLayout.setAnchor(Pack.Below)

View File

@@ -1,3 +1,4 @@
from cocoa.inter import PyTextField, PyProgressWindow
from .deletion_options import PyDeletionOptions from .deletion_options import PyDeletionOptions
from .details_panel import PyDetailsPanel from .details_panel import PyDetailsPanel
from .directory_outline import PyDirectoryOutline from .directory_outline import PyDirectoryOutline

View File

@@ -1,41 +1,23 @@
import logging import logging
from objp.util import pyref, dontwrap from objp.util import pyref, dontwrap
from jobprogress import job from cocoa import install_exception_hook, install_cocoa_logger, patch_threaded_job_performer
import cocoa from cocoa.inter import PyBaseApp, BaseAppView
from cocoa import install_exception_hook, install_cocoa_logger, proxy
from cocoa.inter import PyFairware, FairwareView
from hscommon.trans import trget
from core.app import JobType class DupeGuruView(BaseAppView):
tr = trget('ui')
JOBID2TITLE = {
JobType.Scan: tr("Scanning for duplicates"),
JobType.Load: tr("Loading"),
JobType.Move: tr("Moving"),
JobType.Copy: tr("Copying"),
JobType.Delete: tr("Sending to Trash"),
}
class DupeGuruView(FairwareView):
def askYesNoWithPrompt_(self, prompt: str) -> bool: pass def askYesNoWithPrompt_(self, prompt: str) -> bool: pass
def showProblemDialog(self): pass def showProblemDialog(self): pass
def selectDestFolderWithPrompt_(self, prompt: str) -> str: pass def selectDestFolderWithPrompt_(self, prompt: str) -> str: pass
def selectDestFileWithPrompt_extension_(self, prompt: str, extension: str) -> str: pass def selectDestFileWithPrompt_extension_(self, prompt: str, extension: str) -> str: pass
class PyDupeGuruBase(PyFairware): class PyDupeGuruBase(PyBaseApp):
FOLLOW_PROTOCOLS = ['Worker']
@dontwrap @dontwrap
def _init(self, modelclass): def _init(self, modelclass):
logging.basicConfig(level=logging.WARNING, format='%(levelname)s %(message)s') logging.basicConfig(level=logging.WARNING, format='%(levelname)s %(message)s')
install_exception_hook() install_exception_hook()
install_cocoa_logger() install_cocoa_logger()
appdata = proxy.getAppdataPath() patch_threaded_job_performer()
self.model = modelclass(self, appdata) self.model = modelclass(self)
self.progress = cocoa.ThreadedJobPerformer()
#---Sub-proxies #---Sub-proxies
def detailsPanel(self) -> pyref: def detailsPanel(self) -> pyref:
@@ -56,6 +38,9 @@ class PyDupeGuruBase(PyFairware):
def ignoreListDialog(self) -> pyref: def ignoreListDialog(self) -> pyref:
return self.model.ignore_list_dialog return self.model.ignore_list_dialog
def progressWindow(self) -> pyref:
return self.model.progress_window
def deletionOptions(self) -> pyref: def deletionOptions(self) -> pyref:
return self.model.deletion_options return self.model.deletion_options
@@ -157,52 +142,7 @@ class PyDupeGuruBase(PyFairware):
def setCopyMoveDestType_(self, copymove_dest_type: int): def setCopyMoveDestType_(self, copymove_dest_type: int):
self.model.options['copymove_dest_type'] = copymove_dest_type self.model.options['copymove_dest_type'] = copymove_dest_type
#---Worker
def getJobProgress(self) -> object: # NSNumber
try:
return self.progress.last_progress
except AttributeError:
# I have *no idea* why this can possible happen (last_progress is always set by
# create_job() *before* any threaded job notification, which shows the progress panel,
# is sent), but it happens anyway, so there we go. ref: #106
return -1
def getJobDesc(self) -> str:
try:
return self.progress.last_desc
except AttributeError:
# see getJobProgress
return ''
def cancelJob(self):
self.progress.job_cancelled = True
def jobCompleted_(self, jobid: str):
result = self.model._job_completed(jobid, self.progress.last_error)
if not result:
self.progress.reraise_if_error()
#--- model --> view #--- model --> view
@dontwrap
def open_path(self, path):
proxy.openPath_(str(path))
@dontwrap
def reveal_path(self, path):
proxy.revealPath_(str(path))
@dontwrap
def start_job(self, jobid, func, args=()):
try:
j = self.progress.create_job()
args = tuple([j] + list(args))
self.progress.run_threaded(func, args=args)
except job.JobInProgressError:
proxy.postNotification_userInfo_('JobInProgress', None)
else:
ud = {'desc': JOBID2TITLE[jobid], 'jobid':jobid}
proxy.postNotification_userInfo_('JobStarted', ud)
@dontwrap @dontwrap
def ask_yes_no(self, prompt): def ask_yes_no(self, prompt):
return self.callback.askYesNoWithPrompt_(prompt) return self.callback.askYesNoWithPrompt_(prompt)

View File

@@ -19,11 +19,11 @@ from hscommon.path import Path
from hscommon.util import remove_invalid_xml from hscommon.util import remove_invalid_xml
from core import directories from core import directories
from core.app import JobType from core.app import JobType, JOBID2TITLE
from core.scanner import ScanType from core.scanner import ScanType
from core_me.app import DupeGuru as DupeGuruBase from core_me.app import DupeGuru as DupeGuruBase
from core_me import fs from core_me import fs
from .app import JOBID2TITLE, PyDupeGuruBase from .app import PyDupeGuruBase
tr = trget('ui') tr = trget('ui')
@@ -143,16 +143,13 @@ class Directories(directories.Directories):
class DupeGuruME(DupeGuruBase): class DupeGuruME(DupeGuruBase):
def __init__(self, view, appdata): def __init__(self, view):
appdata = op.join(appdata, 'dupeGuru Music Edition') DupeGuruBase.__init__(self, view)
DupeGuruBase.__init__(self, view, appdata)
# Use fileclasses set in DupeGuruBase.__init__() # Use fileclasses set in DupeGuruBase.__init__()
self.directories = Directories(fileclasses=self.directories.fileclasses) self.directories = Directories(fileclasses=self.directories.fileclasses)
self.dead_tracks = [] self.dead_tracks = []
def _do_delete(self, j, *args): def _do_delete(self, j, *args):
# XXX If I read correctly, Python 3.3 will allow us to go fetch inner function easily, so
# we'll be able to replace "op" below with DupeGuruBase._do_delete.op.
def op(dupe): def op(dupe):
j.add_progress() j.add_progress()
return self._do_delete_dupe(dupe, *args) return self._do_delete_dupe(dupe, *args)
@@ -174,7 +171,7 @@ class DupeGuruME(DupeGuruBase):
DupeGuruBase._do_delete_dupe(self, dupe, *args) DupeGuruBase._do_delete_dupe(self, dupe, *args)
def _create_file(self, path): def _create_file(self, path):
if (self.directories.itunes_libpath is not None) and (path in self.directories.itunes_libpath[:-1]): if (self.directories.itunes_libpath is not None) and (path in self.directories.itunes_libpath.parent()):
if not hasattr(self, 'itunes_songs'): if not hasattr(self, 'itunes_songs'):
songs = get_itunes_songs(self.directories.itunes_libpath) songs = get_itunes_songs(self.directories.itunes_libpath)
self.itunes_songs = {song.path: song for song in songs} self.itunes_songs = {song.path: song for song in songs}
@@ -184,11 +181,14 @@ class DupeGuruME(DupeGuruBase):
pass # We'll return the default file type, as per the last line of this method pass # We'll return the default file type, as per the last line of this method
return DupeGuruBase._create_file(self, path) return DupeGuruBase._create_file(self, path)
def _job_completed(self, jobid, exc): def _job_completed(self, jobid):
if (jobid in {JobType.RemoveDeadTracks, JobType.ScanDeadTracks}) and (exc is not None): # XXX Just before release, I'm realizing that this piece of code below is why I was passing
msg = tr("There were communication problems with iTunes. The operation couldn't be completed.") # job exception as an argument to _job_completed(). I have to comment it for now. It's not
self.view.show_message(msg) # the end of the world, but I should find an elegant solution to this at some point.
return True # 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: if jobid == JobType.ScanDeadTracks:
dead_tracks_count = len(self.dead_tracks) dead_tracks_count = len(self.dead_tracks)
if dead_tracks_count > 0: if dead_tracks_count > 0:
@@ -202,7 +202,7 @@ class DupeGuruME(DupeGuruBase):
if hasattr(self, 'itunes_songs'): if hasattr(self, 'itunes_songs'):
# If we load another file, we want a refresh song list # If we load another file, we want a refresh song list
del self.itunes_songs del self.itunes_songs
DupeGuruBase._job_completed(self, jobid, exc) DupeGuruBase._job_completed(self, jobid)
def copy_or_move(self, dupe, copy, destination, dest_type): def copy_or_move(self, dupe, copy, destination, dest_type):
if isinstance(dupe, ITunesSong): if isinstance(dupe, ITunesSong):
@@ -230,7 +230,7 @@ class DupeGuruME(DupeGuruBase):
except CommandError as e: except CommandError as e:
logging.warning('Error while trying to remove a track from iTunes: %s' % str(e)) logging.warning('Error while trying to remove a track from iTunes: %s' % str(e))
self.view.start_job(JobType.RemoveDeadTracks, do) self._start_job(JobType.RemoveDeadTracks, do)
def scan_dead_tracks(self): def scan_dead_tracks(self):
def do(j): def do(j):
@@ -248,7 +248,7 @@ class DupeGuruME(DupeGuruBase):
self.dead_tracks.append(track) self.dead_tracks.append(track)
logging.info('Found %d dead tracks' % len(self.dead_tracks)) logging.info('Found %d dead tracks' % len(self.dead_tracks))
self.view.start_job(JobType.ScanDeadTracks, do) self._start_job(JobType.ScanDeadTracks, do)
class PyDupeGuru(PyDupeGuruBase): class PyDupeGuru(PyDupeGuruBase):
def __init__(self): def __init__(self):

View File

@@ -6,16 +6,14 @@
# which should be included with this package. The terms are also available at # which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license # http://www.hardcoded.net/licenses/bsd_license
import os.path as op
import plistlib import plistlib
import logging import logging
import re import re
from appscript import app, its, k, CommandError, ApplicationNotFoundError from appscript import app, its, k, CommandError, ApplicationNotFoundError
from hscommon import io
from hscommon.util import remove_invalid_xml, first from hscommon.util import remove_invalid_xml, first
from hscommon.path import Path from hscommon.path import Path, pathify
from hscommon.trans import trget from hscommon.trans import trget
from cocoa import proxy from cocoa import proxy
@@ -48,6 +46,16 @@ class Photo(PhotoBase):
raise IOError('The picture %s could not be read' % str(self.path)) raise IOError('The picture %s could not be read' % str(self.path))
return blocks 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): class IPhoto(Photo):
def __init__(self, path, db_id): def __init__(self, path, db_id):
@@ -67,11 +75,12 @@ class AperturePhoto(Photo):
def display_folder_path(self): def display_folder_path(self):
return APERTURE_PATH return APERTURE_PATH
def get_iphoto_or_aperture_pictures(plistpath, photo_class): @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. # The structure of iPhoto and Aperture libraries for the base photo list are excactly the same.
if not io.exists(plistpath): if not plistpath.exists():
return [] return []
s = io.open(plistpath, 'rt', encoding='utf-8').read() 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 # 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='') s = remove_invalid_xml(s, replace_with='')
# It seems that iPhoto sometimes doesn't properly escape & chars. The regexp below is to find # It seems that iPhoto sometimes doesn't properly escape & chars. The regexp below is to find
@@ -114,12 +123,12 @@ class Directories(directories.Directories):
directories.Directories.__init__(self, fileclasses=[Photo]) directories.Directories.__init__(self, fileclasses=[Photo])
try: try:
self.iphoto_libpath = get_iphoto_database_path() self.iphoto_libpath = get_iphoto_database_path()
self.set_state(self.iphoto_libpath[:-1], directories.DirectoryState.Excluded) self.set_state(self.iphoto_libpath.parent(), directories.DirectoryState.Excluded)
except directories.InvalidPathError: except directories.InvalidPathError:
self.iphoto_libpath = None self.iphoto_libpath = None
try: try:
self.aperture_libpath = get_aperture_database_path() self.aperture_libpath = get_aperture_database_path()
self.set_state(self.aperture_libpath[:-1], directories.DirectoryState.Excluded) self.set_state(self.aperture_libpath.parent(), directories.DirectoryState.Excluded)
except directories.InvalidPathError: except directories.InvalidPathError:
self.aperture_libpath = None self.aperture_libpath = None
@@ -171,9 +180,8 @@ class Directories(directories.Directories):
class DupeGuruPE(DupeGuruBase): class DupeGuruPE(DupeGuruBase):
def __init__(self, view, appdata): def __init__(self, view):
appdata = op.join(appdata, 'dupeGuru Picture Edition') DupeGuruBase.__init__(self, view)
DupeGuruBase.__init__(self, view, appdata)
self.directories = Directories() self.directories = Directories()
def _do_delete(self, j, *args): def _do_delete(self, j, *args):
@@ -247,20 +255,20 @@ class DupeGuruPE(DupeGuruBase):
DupeGuruBase._do_delete_dupe(self, dupe, *args) DupeGuruBase._do_delete_dupe(self, dupe, *args)
def _create_file(self, path): def _create_file(self, path):
if (self.directories.iphoto_libpath is not None) and (path in self.directories.iphoto_libpath[:-1]): if (self.directories.iphoto_libpath is not None) and (path in self.directories.iphoto_libpath.parent()):
if not hasattr(self, 'path2iphoto'): if not hasattr(self, 'path2iphoto'):
photos = get_iphoto_pictures(self.directories.iphoto_libpath) photos = get_iphoto_pictures(self.directories.iphoto_libpath)
self.path2iphoto = {p.path: p for p in photos} self.path2iphoto = {p.path: p for p in photos}
return self.path2iphoto.get(path) return self.path2iphoto.get(path)
if (self.directories.aperture_libpath is not None) and (path in self.directories.aperture_libpath[:-1]): if (self.directories.aperture_libpath is not None) and (path in self.directories.aperture_libpath.parent()):
if not hasattr(self, 'path2aperture'): if not hasattr(self, 'path2aperture'):
photos = get_aperture_pictures(self.directories.aperture_libpath) photos = get_aperture_pictures(self.directories.aperture_libpath)
self.path2aperture = {p.path: p for p in photos} self.path2aperture = {p.path: p for p in photos}
return self.path2aperture.get(path) return self.path2aperture.get(path)
return DupeGuruBase._create_file(self, path) return DupeGuruBase._create_file(self, path)
def _job_completed(self, jobid, exc): def _job_completed(self, jobid):
DupeGuruBase._job_completed(self, jobid, exc) DupeGuruBase._job_completed(self, jobid)
if jobid == JobType.Load: if jobid == JobType.Load:
if hasattr(self, 'path2iphoto'): if hasattr(self, 'path2iphoto'):
del self.path2iphoto del self.path2iphoto

View File

@@ -9,14 +9,13 @@
import logging import logging
import os.path as op import os.path as op
from hscommon import io from hscommon.path import Path, pathify
from hscommon.path import Path
from cocoa import proxy from cocoa import proxy
from core.scanner import ScanType from core.scanner import ScanType
from core import fs
from core.directories import Directories as DirectoriesBase, DirectoryState from core.directories import Directories as DirectoriesBase, DirectoryState
from core_se.app import DupeGuru as DupeGuruBase from core_se.app import DupeGuru as DupeGuruBase
from core_se import fs
from .app import PyDupeGuruBase from .app import PyDupeGuruBase
def is_bundle(str_path): def is_bundle(str_path):
@@ -27,8 +26,9 @@ def is_bundle(str_path):
class Bundle(fs.Folder): class Bundle(fs.Folder):
@classmethod @classmethod
def can_handle(cls, path): @pathify
return not io.islink(path) and io.isdir(path) and is_bundle(str(path)) def can_handle(cls, path: Path):
return not path.islink() and path.isdir() and is_bundle(str(path))
class Directories(DirectoriesBase): class Directories(DirectoriesBase):
@@ -36,6 +36,7 @@ class Directories(DirectoriesBase):
HOME_PATH_TO_EXCLUDE = [Path('Library')] HOME_PATH_TO_EXCLUDE = [Path('Library')]
def __init__(self): def __init__(self):
DirectoriesBase.__init__(self, fileclasses=[Bundle, fs.File]) DirectoriesBase.__init__(self, fileclasses=[Bundle, fs.File])
self.folderclass = fs.Folder
def _default_state_for_path(self, path): def _default_state_for_path(self, path):
result = DirectoriesBase._default_state_for_path(self, path) result = DirectoriesBase._default_state_for_path(self, path)
@@ -67,9 +68,10 @@ class Directories(DirectoriesBase):
class DupeGuru(DupeGuruBase): class DupeGuru(DupeGuruBase):
def __init__(self, view, appdata): def __init__(self, view):
appdata = op.join(appdata, 'dupeGuru') # appdata = op.join(appdata, 'dupeGuru')
DupeGuruBase.__init__(self, view, appdata) # print(repr(appdata))
DupeGuruBase.__init__(self, view)
self.directories = Directories() self.directories = Directories()

View File

@@ -11,6 +11,7 @@ from cocoa.inter import PyGUIObject, GUIObjectView
class DeletionOptionsView(GUIObjectView): class DeletionOptionsView(GUIObjectView):
def updateMsg_(self, msg: str): pass def updateMsg_(self, msg: str): pass
def show(self) -> bool: pass def show(self) -> bool: pass
def setHardlinkOptionEnabled_(self, enabled: bool): pass
class PyDeletionOptions(PyGUIObject): class PyDeletionOptions(PyGUIObject):
def setLinkDeleted_(self, link_deleted: bool): def setLinkDeleted_(self, link_deleted: bool):
@@ -31,3 +32,6 @@ class PyDeletionOptions(PyGUIObject):
def show(self): def show(self):
return self.callback.show() return self.callback.show()
@dontwrap
def set_hardlink_option_enabled(self, enabled):
self.callback.setHardlinkOptionEnabled_(enabled)

View File

@@ -11,6 +11,9 @@ class PyDirectoryOutline(PyOutline):
def removeSelectedDirectory(self): def removeSelectedDirectory(self):
self.model.remove_selected() self.model.remove_selected()
def selectAll(self):
self.model.select_all()
# python --> cocoa # python --> cocoa
@dontwrap @dontwrap
def refresh_states(self): def refresh_states(self):

View File

@@ -17,12 +17,13 @@ class PyResultTable(PyTable):
def setDeltaValuesMode_(self, value: bool): def setDeltaValuesMode_(self, value: bool):
self.model.delta_values = value self.model.delta_values = value
def deltaColumns(self) -> list:
return list(self.model.DELTA_COLUMNS)
def valueForRow_column_(self, row_index: int, column: str) -> object: def valueForRow_column_(self, row_index: int, column: str) -> object:
return self.model.get_row_value(row_index, column) 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: def renameSelected_(self, newname: str) -> bool:
return self.model.rename_selected(newname) return self.model.rename_selected(newname)

View File

@@ -31,10 +31,10 @@ def configure(conf):
os.symlink('../build/Python', versioned_dylib_path) os.symlink('../build/Python', versioned_dylib_path)
# The rest is standard WAF code that you can find the the python and macapp demos. # The rest is standard WAF code that you can find the the python and macapp demos.
conf.load('compiler_c python') conf.load('compiler_c python')
conf.check_python_version((3,2,0)) conf.check_python_version((3,3,0))
conf.check_python_headers() conf.check_python_headers()
conf.env.FRAMEWORK_COCOA = 'Cocoa' conf.env.FRAMEWORK_COCOA = 'Cocoa'
conf.env.ARCH_COCOA = ['i386', 'x86_64'] conf.env.ARCH_COCOA = ['x86_64']
# Add cocoalib dir to the framework search path so we can find Sparkle. # Add cocoalib dir to the framework search path so we can find Sparkle.
conf.env.CFLAGS = ['-F'+op.abspath('../cocoalib')] conf.env.CFLAGS = ['-F'+op.abspath('../cocoalib')]
conf.env.LINKFLAGS = ['-F'+op.abspath('../cocoalib')] conf.env.LINKFLAGS = ['-F'+op.abspath('../cocoalib')]
@@ -44,13 +44,14 @@ def build(ctx):
cocoalib_node = ctx.srcnode.find_dir('..').find_dir('cocoalib') cocoalib_node = ctx.srcnode.find_dir('..').find_dir('cocoalib')
cocoalib_folders = ['controllers', 'views'] cocoalib_folders = ['controllers', 'views']
cocoalib_includes = [cocoalib_node] + [cocoalib_node.find_dir(folder) for folder in cocoalib_folders] cocoalib_includes = [cocoalib_node] + [cocoalib_node.find_dir(folder) for folder in cocoalib_folders]
cocoalib_uses = ['NSEventAdditions', 'Dialogs', 'HSFairwareAboutBox', 'HSFairwareReminder', 'Utils', cocoalib_uses = ['NSEventAdditions', 'Dialogs', 'HSAboutBox', 'Utils',
'HSPyUtil', 'ProgressController', 'HSRecentFiles', 'HSQuicklook', 'ValueTransformers', 'HSPyUtil', 'ProgressController', 'HSRecentFiles', 'HSQuicklook', 'ValueTransformers',
'NSImageAdditions', 'NSNotificationAdditions', 'NSImageAdditions', 'NSNotificationAdditions',
'views/HSTableView', 'views/HSOutlineView', 'views/NSIndexPathAdditions', 'views/HSTableView', 'views/HSOutlineView', 'views/NSIndexPathAdditions',
'views/NSTableViewAdditions', 'views/NSTableViewAdditions',
'controllers/HSColumns', 'controllers/HSGUIController', 'controllers/HSTable', 'controllers/HSColumns', 'controllers/HSGUIController', 'controllers/HSTable',
'controllers/HSOutline', 'controllers/HSPopUpList', 'controllers/HSSelectableList'] 'controllers/HSOutline', 'controllers/HSPopUpList', 'controllers/HSSelectableList',
'controllers/HSTextField', 'controllers/HSProgressWindow']
cocoalib_src = [cocoalib_node.find_node(usename + '.m') for usename in cocoalib_uses] + cocoalib_node.ant_glob('autogen/*.m') cocoalib_src = [cocoalib_node.find_node(usename + '.m') for usename in cocoalib_uses] + cocoalib_node.ant_glob('autogen/*.m')
project_folders = ['autogen', 'base', ctx.env.DGEDITION] project_folders = ['autogen', 'base', ctx.env.DGEDITION]
project_src = sum([ctx.srcnode.ant_glob('%s/*.m' % folder) for folder in project_folders], []) project_src = sum([ctx.srcnode.ant_glob('%s/*.m' % folder) for folder in project_folders], [])

15
cocoalib/.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
.DS_Store
__pycache__
autogen
*.pyc
*.so
nl.lproj
cs.lproj
de.lproj
fr.lproj
it.lproj
hy.lproj
ru.lproj
uk.lproj
zh_CN.lproj
pt_BR.lproj

8
cocoalib/.tx/config Normal file
View File

@@ -0,0 +1,8 @@
[main]
host = https://www.transifex.com
[hscommon.cocoalib]
file_filter = locale/<lang>/LC_MESSAGES/cocoalib.po
source_file = locale/cocoalib.pot
source_lang = en
type = PO

14
cocoalib/Dialogs.h Normal file
View File

@@ -0,0 +1,14 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
@interface Dialogs : NSObject
+ (void)showMessage:(NSString *)message;
+ (NSInteger)askYesNo:(NSString *)message;
@end

31
cocoalib/Dialogs.m Normal file
View File

@@ -0,0 +1,31 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "Dialogs.h"
@implementation Dialogs
+ (void)showMessage:(NSString *)message
{
NSAlert *a = [[NSAlert alloc] init];
[a addButtonWithTitle:NSLocalizedStringFromTable(@"OK", @"cocoalib", @"")];
[a setMessageText:message];
[a runModal];
[a release];
}
+ (NSInteger)askYesNo:(NSString *)message
{
NSAlert *a = [[NSAlert alloc] init];
[a addButtonWithTitle:NSLocalizedStringFromTable(@"Yes", @"cocoalib", @"")];
[[a addButtonWithTitle:NSLocalizedStringFromTable(@"No", @"cocoalib", @"")] setKeyEquivalent:@"\E"];
[a setMessageText:message];
NSInteger r = [a runModal];
[a release];
return r;
}
@end

27
cocoalib/HSAboutBox.h Normal file
View File

@@ -0,0 +1,27 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import "PyBaseApp.h"
@interface HSAboutBox : NSWindowController
{
NSTextField *titleTextField;
NSTextField *versionTextField;
NSTextField *copyrightTextField;
PyBaseApp *app;
}
@property (readwrite, retain) NSTextField *titleTextField;
@property (readwrite, retain) NSTextField *versionTextField;
@property (readwrite, retain) NSTextField *copyrightTextField;
- (id)initWithApp:(PyBaseApp *)app;
- (void)updateFields;
@end

42
cocoalib/HSAboutBox.m Normal file
View File

@@ -0,0 +1,42 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSAboutBox.h"
#import "HSAboutBox_UI.h"
@implementation HSAboutBox
@synthesize titleTextField;
@synthesize versionTextField;
@synthesize copyrightTextField;
- (id)initWithApp:(PyBaseApp *)aApp
{
self = [super initWithWindow:nil];
[self setWindow:createHSAboutBox_UI(self)];
app = [aApp retain];
[self updateFields];
return self;
}
- (void)dealloc
{
[app release];
[super dealloc];
}
- (void)updateFields
{
[titleTextField setStringValue:[app appLongName]];
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
[versionTextField setStringValue:[NSString stringWithFormat:@"Version: %@",version]];
NSString *copyright = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSHumanReadableCopyright"];
[copyrightTextField setStringValue:copyright];
}
@end

View File

@@ -0,0 +1,23 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
@interface HSErrorReportWindow : NSWindowController
{
NSTextView *contentTextView;
}
@property (readwrite, retain) NSTextView *contentTextView;
+ (void)showErrorReportWithContent:(NSString *)content;
- (id)initWithContent:(NSString *)content;
- (void)send;
- (void)dontSend;
@end

View File

@@ -0,0 +1,48 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSErrorReportWindow.h"
#import "HSErrorReportWindow_UI.h"
@implementation HSErrorReportWindow
@synthesize contentTextView;
+ (void)showErrorReportWithContent:(NSString *)content
{
HSErrorReportWindow *report = [[HSErrorReportWindow alloc] initWithContent:content];
[NSApp runModalForWindow:[report window]];
[report release];
}
- (id)initWithContent:(NSString *)content
{
self = [super initWithWindow:nil];
[self setWindow:createHSErrorReportWindow_UI(self)];
[contentTextView alignLeft:nil];
[[[contentTextView textStorage] mutableString] setString:content];
return self;
}
- (void)send
{
NSString *text = [[contentTextView textStorage] string];
NSString *URL = [NSString stringWithFormat:@"mailto:support@hardcoded.net?SUBJECT=Error Report&BODY=%@",text];
NSString *encodedURL = [URL stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:encodedURL]];
[[self window] orderOut:self];
[NSApp stopModalWithCode:NSOKButton];
}
- (void)dontSend
{
[[self window] orderOut:self];
[NSApp stopModalWithCode:NSCancelButton];
}
@end

15
cocoalib/HSGeometry.h Normal file
View File

@@ -0,0 +1,15 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import <math.h>
CGFloat deg2rad(CGFloat deg);
CGFloat distance(NSPoint p1, NSPoint p2);
NSPoint pointInCircle(NSPoint center, CGFloat radius, CGFloat angle);
CGFloat angleFromPoints(NSPoint pt1, NSPoint pt2);

71
cocoalib/HSGeometry.m Normal file
View File

@@ -0,0 +1,71 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSGeometry.h"
CGFloat deg2rad(CGFloat deg)
{
return deg * M_PI / 180;
}
CGFloat distance(NSPoint p1, NSPoint p2)
{
CGFloat dX = p1.x - p2.x;
CGFloat dY = p1.y - p2.y;
return sqrt(dX * dX + dY * dY);
}
NSPoint pointInCircle(NSPoint center, CGFloat radius, CGFloat angle)
{
// a/sin(A) = b/sin(B) = c/sin(C) = 2R
// the start point it (center.x + radius, center.y) and goes counterclockwise
angle = fmod(angle, M_PI*2);
CGFloat C = M_PI/2;
CGFloat A = fmod(angle, M_PI/2);
CGFloat B = C - A;
CGFloat c = radius;
CGFloat ratio = c / sin(C);
CGFloat b = ratio * sin(B);
CGFloat a = ratio * sin(A);
if (angle >= M_PI * 1.5)
return NSMakePoint(center.x + a, center.y - b);
else if (angle >= M_PI)
return NSMakePoint(center.x - b, center.y - a);
else if (angle >= M_PI/2)
return NSMakePoint(center.x - a, center.y + b);
else
return NSMakePoint(center.x + b, center.y + a);
}
CGFloat angleFromPoints(NSPoint pt1, NSPoint pt2)
{
// Returns the angle (radian) formed by the line pt1-pt2. The angle follows the same logic
// as in pointInCircle.
// What we do here is that we take the line and reduce it to fit a "unit circle" (circle with
// a radius of 1). Then, either asin(adjusted_dy) or acos(adjusted_dx) will give us our angle.
// We'll use asin(adjusted_dy).
CGFloat length = distance(pt1, pt2);
CGFloat dx = pt2.x - pt1.x;
CGFloat dy = pt2.y - pt1.y;
CGFloat ajdusted_dy = ABS(dy) / length;
CGFloat angle = asin(ajdusted_dy);
if ((dx < 0) && (dy >= 0)) {
// top-left quadrant
angle = M_PI - angle;
}
else if ((dx < 0) && (dy < 0)) {
// bottom-left quadrant
angle = M_PI + angle;
}
else if ((dx >= 0) && (dy < 0)) {
// bottom-right quadrant
angle = (2 * M_PI) - angle;
}
return angle;
}

13
cocoalib/HSPyUtil.h Normal file
View File

@@ -0,0 +1,13 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import <Python.h>
void setCocoaViewsModuleName(NSString *moduleName);
PyObject* createCallback(NSString *aViewClassName, id aViewRef);

34
cocoalib/HSPyUtil.m Normal file
View File

@@ -0,0 +1,34 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSPyUtil.h"
#import "ObjP.h"
static NSString *gCocoaViewsModuleName;
void setCocoaViewsModuleName(NSString *moduleName)
{
if (gCocoaViewsModuleName != nil) {
[gCocoaViewsModuleName release];
}
gCocoaViewsModuleName = [moduleName retain];
}
PyObject* createCallback(NSString *aViewClassName, id aViewRef)
{
NSString *moduleName;
if (gCocoaViewsModuleName != nil) {
moduleName = gCocoaViewsModuleName;
}
else {
moduleName = @"inter.CocoaViews";
}
PyGILState_STATE gilState = PyGILState_Ensure();
PyObject *pCallback = ObjP_classInstanceWithRef(aViewClassName, moduleName, aViewRef);
PyGILState_Release(gilState);
return pCallback;
}

18
cocoalib/HSQuicklook.h Normal file
View File

@@ -0,0 +1,18 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
@interface HSQLPreviewItem : NSObject <QLPreviewItem>
{
NSURL *url;
NSString *title;
}
- (id)initWithUrl:(NSURL *)aUrl title:(NSString *)aTitle;
@end

36
cocoalib/HSQuicklook.m Normal file
View File

@@ -0,0 +1,36 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSQuicklook.h"
@implementation HSQLPreviewItem
- (id)initWithUrl:(NSURL *)aUrl title:(NSString *)aTitle
{
self = [super init];
url = [aUrl retain];
title = [aTitle retain];
return self;
}
- (void)dealloc
{
[url release];
[title release];
[super dealloc];
}
- (NSURL *)previewItemURL
{
return url;
}
- (NSString *)previewItemTitle
{
return title;
}
@end

35
cocoalib/HSRecentFiles.h Normal file
View File

@@ -0,0 +1,35 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
@interface HSRecentFiles : NSObject
{
id delegate;
NSMenu *menu;
NSString *name;
NSMutableArray *filepaths;
NSInteger numberOfMenuItemsToPreserve;
}
- (id)initWithName:(NSString *)aName menu:(NSMenu *)aMenu;
- (void)addFile:(NSString *)path;
- (void)rebuildMenu;
- (void)fillMenu:(NSMenu *)menu;
- (void)clearMenu:(id)sender;
- (void)menuClick:(id)sender;
- (NSMenu *)menu;
- (id)delegate;
- (void)setDelegate:(id)aDelegate;
- (NSArray *)filepaths;
@end
@protocol HSRecentFilesDelegate
- (void)recentFileClicked:(NSString *)path;
@end

89
cocoalib/HSRecentFiles.m Normal file
View File

@@ -0,0 +1,89 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSRecentFiles.h"
@implementation HSRecentFiles
- (id)initWithName:(NSString *)aName menu:(NSMenu *)aMenu
{
self = [super init];
name = aName;
menu = [aMenu retain];
numberOfMenuItemsToPreserve = [menu numberOfItems];
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
filepaths = [[NSMutableArray alloc] initWithArray:[ud arrayForKey:name]];
NSFileManager *fm = [NSFileManager defaultManager];
for (NSInteger i=[filepaths count]-1;i>=0;i--) {
NSString *path = [filepaths objectAtIndex:i];
// We check for path class because we might be fed with garbage from the prefs.
if ((![path isKindOfClass:[NSString class]]) || (![fm fileExistsAtPath:path])) {
[filepaths removeObjectAtIndex:i];
}
}
[self rebuildMenu];
return self;
}
- (void)dealloc
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
[ud setObject:filepaths forKey:name];
[ud synchronize];
[filepaths release];
[menu release];
[super dealloc];
}
- (void)addFile:(NSString *)path
{
[filepaths removeObject:path];
[filepaths insertObject:path atIndex:0];
[self rebuildMenu];
}
- (void)rebuildMenu
{
while ([menu numberOfItems] > numberOfMenuItemsToPreserve)
[menu removeItemAtIndex:[menu numberOfItems]-1];
[self fillMenu:menu];
if ([filepaths count] > 0) {
[menu addItem:[NSMenuItem separatorItem]];
NSMenuItem *mi = [menu addItemWithTitle:NSLocalizedStringFromTable(@"Clear List", @"cocoalib", @"") action:@selector(clearMenu:) keyEquivalent:@""];
[mi setTarget:self];
}
}
- (void)fillMenu:(NSMenu *)menuToFill
{
for (int i=0;i<[filepaths count];i++) {
NSMenuItem *mi = [menuToFill addItemWithTitle:[filepaths objectAtIndex:i] action:@selector(menuClick:) keyEquivalent:@""];
[mi setTag:i];
[mi setTarget:self];
}
}
- (void)clearMenu:(id)sender
{
[filepaths removeAllObjects];
[self rebuildMenu];
}
- (void)menuClick:(id)sender
{
if (delegate == nil)
return;
if ([delegate respondsToSelector:@selector(recentFileClicked:)])
[delegate recentFileClicked:[filepaths objectAtIndex:[sender tag]]];
}
/* Properties */
- (NSMenu *)menu {return menu;}
- (id)delegate { return delegate; }
- (void)setDelegate:(id)aDelegate { delegate = aDelegate; }
- (NSArray *)filepaths {return filepaths;}
@end

10
cocoalib/LICENSE Normal file
View File

@@ -0,0 +1,10 @@
Copyright 2013, Hardcoded Software Inc., http://www.hardcoded.net
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Hardcoded Software Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,24 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
@interface NSEvent(NSEventAdditions)
- (unichar)firstCharacter;
- (NSUInteger)flags;
- (NSUInteger)modifierKeysFlags;
- (BOOL)isDeleteOrBackspace;
- (BOOL)isReturnOrEnter;
- (BOOL)isTab;
- (BOOL)isBackTab;
- (BOOL)isSpace;
- (BOOL)isUp;
- (BOOL)isDown;
- (BOOL)isLeft;
- (BOOL)isRight;
@end

View File

@@ -0,0 +1,85 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "NSEventAdditions.h"
@implementation NSEvent(NSEventAdditions)
- (unichar)firstCharacter
{
NSString *characters = [self characters];
if ([characters length] == 0)
{
return '\0';
}
return [characters characterAtIndex:0];
}
- (NSUInteger)flags
{
// get flags and strip the lower 16 (device dependant) bits
// See modifierFlags's doc for details
return [self modifierFlags] & NSDeviceIndependentModifierFlagsMask;
}
- (NSUInteger)modifierKeysFlags
{
// This is modifierFlags with only Command, Opt, Ctrl and Shift, without the rest of the flags
// to pollute.
return [self flags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask);
}
- (BOOL)isDeleteOrBackspace
{
unichar firstChar = [self firstCharacter];
return firstChar == NSDeleteFunctionKey || firstChar == NSDeleteCharFunctionKey ||
firstChar == NSDeleteCharacter || firstChar == NSBackspaceCharacter;
}
- (BOOL)isReturnOrEnter
{
unichar firstChar = [self firstCharacter];
return firstChar == NSCarriageReturnCharacter || firstChar == NSEnterCharacter;
}
- (BOOL)isTab
{
return [self firstCharacter] == NSTabCharacter;
}
- (BOOL)isBackTab
{
return [self firstCharacter] == NSBackTabCharacter;
}
- (BOOL)isSpace
{
return ([self firstCharacter] == 0x20) && (![self flags]);
}
- (BOOL)isUp
{
return [self firstCharacter] == NSUpArrowFunctionKey;
}
- (BOOL)isDown
{
return [self firstCharacter] == NSDownArrowFunctionKey;
}
- (BOOL)isLeft
{
return [self firstCharacter] == NSLeftArrowFunctionKey;
}
- (BOOL)isRight
{
return [self firstCharacter] == NSRightArrowFunctionKey;
}
@end

View File

@@ -0,0 +1,21 @@
// Created by Scott Stevenson on 9/28/07.
//
// Personal site: http://theocacao.com/
// Post for this sample: http://theocacao.com/document.page/497
//
// The code in this project is intended to be used as a learning
// tool for Cocoa programmers. You may freely use the code in
// your own programs, but please do not use the code as-is in
// other tutorials.
#import <Cocoa/Cocoa.h>
@interface NSImage (Extras)
// creates a copy of the current image while maintaining
// proportions. also centers image, if necessary
- (NSImage*)imageByScalingProportionallyToSize:(NSSize)aSize;
@end

114
cocoalib/NSImageAdditions.m Normal file
View File

@@ -0,0 +1,114 @@
// Created by Scott Stevenson on 9/28/07.
//
// Personal site: http://theocacao.com/
// Post for this sample: http://theocacao.com/document.page/497
//
// The code in this project is intended to be used as a learning
// tool for Cocoa programmers. You may freely use the code in
// your own programs, but please do not use the code as-is in
// other tutorials.
#import "NSImageAdditions.h"
@implementation NSImage (Extras)
- (NSImage*)imageByScalingProportionallyToSize:(NSSize)targetSize
{
NSImage* sourceImage = self;
NSImage* newImage = nil;
if ([sourceImage isValid])
{
NSSize imageSize = [sourceImage size];
CGFloat width = imageSize.width;
CGFloat height = imageSize.height;
CGFloat targetWidth = targetSize.width;
CGFloat targetHeight = targetSize.height;
// scaleFactor will be the fraction that we'll
// use to adjust the size. For example, if we shrink
// an image by half, scaleFactor will be 0.5. the
// scaledWidth and scaledHeight will be the original,
// multiplied by the scaleFactor.
//
// IMPORTANT: the "targetHeight" is the size of the space
// we're drawing into. The "scaledHeight" is the height that
// the image actually is drawn at, once we take into
// account the ideal of maintaining proportions
CGFloat scaleFactor = 0.0;
CGFloat scaledWidth = targetWidth;
CGFloat scaledHeight = targetHeight;
NSPoint thumbnailPoint = NSMakePoint(0,0);
// since not all images are square, we want to scale
// proportionately. To do this, we find the longest
// edge and use that as a guide.
if ( NSEqualSizes( imageSize, targetSize ) == NO )
{
// use the longeset edge as a guide. if the
// image is wider than tall, we'll figure out
// the scale factor by dividing it by the
// intended width. Otherwise, we'll use the
// height.
CGFloat widthFactor = targetWidth / width;
CGFloat heightFactor = targetHeight / height;
if ( widthFactor < heightFactor )
scaleFactor = widthFactor;
else
scaleFactor = heightFactor;
// ex: 500 * 0.5 = 250 (newWidth)
scaledWidth = width * scaleFactor;
scaledHeight = height * scaleFactor;
// center the thumbnail in the frame. if
// wider than tall, we need to adjust the
// vertical drawing point (y axis)
if ( widthFactor < heightFactor )
thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5;
else if ( widthFactor > heightFactor )
thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5;
}
// create a new image to draw into
newImage = [[NSImage alloc] initWithSize:targetSize];
// once focus is locked, all drawing goes into this NSImage instance
// directly, not to the screen. It also receives its own graphics
// context.
//
// Also, keep in mind that we're doing this in a background thread.
// You only want to draw to the screen in the main thread, but
// drawing to an offscreen image is (apparently) okay.
[newImage lockFocus];
NSRect thumbnailRect;
thumbnailRect.origin = thumbnailPoint;
thumbnailRect.size.width = scaledWidth;
thumbnailRect.size.height = scaledHeight;
[sourceImage drawInRect: thumbnailRect
fromRect: NSZeroRect
operation: NSCompositeSourceOver
fraction: 1.0];
[newImage unlockFocus];
}
return [newImage autorelease];
}
@end

View File

@@ -0,0 +1,10 @@
// from http://www.cocoadev.com/index.pl?NotificationsAcrossThreads
#import <Cocoa/Cocoa.h>
@interface NSNotificationCenter (NSNotificationCenterAdditions)
- (void) postNotificationOnMainThread:(NSNotification *) notification;
- (void) postNotificationOnMainThread:(NSNotification *) notification waitUntilDone:(BOOL) wait;
- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object;
- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo;
- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo waitUntilDone:(BOOL) wait;
@end

View File

@@ -0,0 +1,48 @@
#import "NSNotificationAdditions.h"
#import <pthread.h>
@implementation NSNotificationCenter (NSNotificationCenterAdditions)
- (void) postNotificationOnMainThread:(NSNotification *) notification {
if( pthread_main_np() ) return [self postNotification:notification];
[self postNotificationOnMainThread:notification waitUntilDone:NO];
}
- (void) postNotificationOnMainThread:(NSNotification *) notification waitUntilDone:(BOOL) wait {
if( pthread_main_np() ) return [self postNotification:notification];
[[self class] performSelectorOnMainThread:@selector( _postNotification: ) withObject:notification waitUntilDone:wait];
}
+ (void) _postNotification:(NSNotification *) notification {
[[self defaultCenter] postNotification:notification];
}
- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object {
if( pthread_main_np() ) return [self postNotificationName:name object:object userInfo:nil];
[self postNotificationOnMainThreadWithName:name object:object userInfo:nil waitUntilDone:NO];
}
- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo {
if( pthread_main_np() ) return [self postNotificationName:name object:object userInfo:userInfo];
[self postNotificationOnMainThreadWithName:name object:object userInfo:nil waitUntilDone:NO];
}
- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo waitUntilDone:(BOOL) wait {
if( pthread_main_np() ) return [self postNotificationName:name object:object userInfo:userInfo];
NSMutableDictionary *info = [[NSMutableDictionary allocWithZone:nil] init];
[info setObject:name forKey:@"name"];
if( object ) [info setObject:object forKey:@"object"];
if( userInfo ) [info setObject:userInfo forKey:@"userInfo"];
[[self class] performSelectorOnMainThread:@selector( _postNotificationName: ) withObject:info waitUntilDone:wait];
[info release];
}
+ (void) _postNotificationName:(NSDictionary *) info {
NSString *name = [info objectForKey:@"name"];
id object = [info objectForKey:@"object"];
NSDictionary *userInfo = [info objectForKey:@"userInfo"];
[[self defaultCenter] postNotificationName:name object:object userInfo:userInfo];
}
@end

View File

@@ -0,0 +1,50 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import "Worker.h"
extern NSString *JobCompletedNotification;
extern NSString *JobCancelledNotification;
@interface ProgressController : NSWindowController <NSWindowDelegate>
{
NSButton *cancelButton;
NSProgressIndicator *progressBar;
NSTextField *statusText;
NSTextField *descText;
id _jobId;
BOOL _running;
NSObject<Worker> *_worker;
}
@property (readwrite, retain) NSButton *cancelButton;
@property (readwrite, retain) NSProgressIndicator *progressBar;
@property (readwrite, retain) NSTextField *statusText;
@property (readwrite, retain) NSTextField *descText;
+ (ProgressController *)mainProgressController;
- (id)init;
- (void)cancel;
- (void)hide;
- (void)show;
- (void)showWithCancelButton:(BOOL)cancelEnabled;
- (void)showSheetForParent:(NSWindow *) parentWindow;
- (void)showSheetForParent:(NSWindow *) parentWindow withCancelButton:(BOOL)cancelEnabled;
/* Properties */
- (BOOL)isShown;
- (id)jobId;
- (void)setJobId:(id)jobId;
- (void)setJobDesc:(NSString *)desc;
- (void)setWorker:(NSObject<Worker> *)worker;
@end

View File

@@ -0,0 +1,160 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "ProgressController.h"
#import "Utils.h"
#import "ProgressController_UI.h"
NSString *JobCompletedNotification = @"JobCompletedNotification";
NSString *JobCancelledNotification = @"JobCancelledNotification";
static ProgressController *_mainPC = nil;
@implementation ProgressController
@synthesize cancelButton;
@synthesize progressBar;
@synthesize statusText;
@synthesize descText;
+ (ProgressController *)mainProgressController
{
if (_mainPC == nil)
_mainPC = [[ProgressController alloc] init];
return _mainPC;
}
- (id)init
{
self = [super initWithWindow:nil];
[self setWindow:createProgressController_UI(self)];
[progressBar setUsesThreadedAnimation:YES];
_worker = nil;
_running = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:NSApplicationDidBecomeActiveNotification object:nil];
return self;
}
- (void)cancel
{
[self hide];
}
- (void)hide
{
if (_worker != nil)
[_worker cancelJob];
[[NSNotificationCenter defaultCenter] postNotificationName:JobCancelledNotification object:self];
_running = NO;
[NSApp endSheet:[self window] returnCode:NSRunAbortedResponse];
/* There's this really strange thing where when the app is inactive at the point we want to hide
the progress dialog, it becomes impossible to close it. I guess it's due to some strange
thread-related crap. Anyway, *DO NOT HIDE THE SHEET WHILE THE APP IS INACTIVE*. Do it later,
when the app becomes active again.
*/
if ([NSApp isActive]) {
[[self window] orderOut:nil];
}
}
- (void)show
{
[self showWithCancelButton:YES];
}
- (void)showWithCancelButton:(BOOL)cancelEnabled
{
[progressBar setIndeterminate:YES];
[[self window] makeKeyAndOrderFront:nil];
[progressBar setUsesThreadedAnimation:YES];
[progressBar startAnimation:nil];
[cancelButton setEnabled:cancelEnabled];
_running = YES;
[NSThread detachNewThreadSelector:@selector(threadedWorkerProbe) toTarget:self withObject:nil];
}
- (void)showSheetForParent:(NSWindow *) parentWindow
{
[self showSheetForParent:parentWindow withCancelButton:YES];
}
- (void)showSheetForParent:(NSWindow *) parentWindow withCancelButton:(BOOL)cancelEnabled
{
[progressBar setIndeterminate:YES];
[progressBar startAnimation:nil];
[cancelButton setEnabled:cancelEnabled];
_running = YES;
[NSThread detachNewThreadSelector:@selector(threadedWorkerProbe) toTarget:self withObject:nil];
[NSApp beginSheet:[self window] modalForWindow:parentWindow modalDelegate:nil didEndSelector:nil contextInfo:nil];
}
- (void)updateProgress
{
if (!_running)
return;
NSNumber *progress = [_worker getJobProgress];
NSString *status = [_worker getJobDesc];
if ((status != nil) && ([status length] > 0))
{
[statusText setStringValue:status];
}
if (progress != nil)
{
[progressBar setDoubleValue:n2i(progress)];
[progressBar setIndeterminate: n2i(progress) < 0];
}
else
{
[self hide];
[_worker jobCompleted:_jobId];
[[NSNotificationCenter defaultCenter] postNotificationName:JobCompletedNotification object:self];
}
}
- (void)threadedWorkerProbe
{
while (_running && (_worker != nil))
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
[self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:YES];
[pool release];
}
}
/* Properties */
- (BOOL)isShown
{
return _running;
}
- (id)jobId {return _jobId;}
- (void)setJobId:(id)jobId
{
[_jobId autorelease];
_jobId = [jobId retain];
}
- (void)setJobDesc:(NSString *)desc
{
[descText setStringValue:desc];
[statusText setStringValue:NSLocalizedStringFromTable(@"Please wait...", @"cocoalib", @"")];
}
- (void)setWorker:(NSObject<Worker> *)worker
{
_worker = worker;
}
/* Delegate and Notifs */
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
if (!_running) {
[[self window] orderOut:nil];
}
}
@end

View File

@@ -0,0 +1 @@
Versions/Current/Headers

View File

@@ -0,0 +1 @@
Versions/Current/Resources

View File

@@ -0,0 +1 @@
Versions/Current/Sparkle

View File

@@ -0,0 +1,33 @@
//
// SUAppcast.h
// Sparkle
//
// Created by Andy Matuschak on 3/12/06.
// Copyright 2006 Andy Matuschak. All rights reserved.
//
#ifndef SUAPPCAST_H
#define SUAPPCAST_H
@class SUAppcastItem;
@interface SUAppcast : NSObject {
NSArray *items;
NSString *userAgentString;
id delegate;
NSMutableData *incrementalData;
}
- (void)fetchAppcastFromURL:(NSURL *)url;
- (void)setDelegate:delegate;
- (void)setUserAgentString:(NSString *)userAgentString;
- (NSArray *)items;
@end
@interface NSObject (SUAppcastDelegate)
- (void)appcastDidFinishLoading:(SUAppcast *)appcast;
- (void)appcast:(SUAppcast *)appcast failedToLoadWithError:(NSError *)error;
@end
#endif

View File

@@ -0,0 +1,47 @@
//
// SUAppcastItem.h
// Sparkle
//
// Created by Andy Matuschak on 3/12/06.
// Copyright 2006 Andy Matuschak. All rights reserved.
//
#ifndef SUAPPCASTITEM_H
#define SUAPPCASTITEM_H
@interface SUAppcastItem : NSObject {
NSString *title;
NSDate *date;
NSString *itemDescription;
NSURL *releaseNotesURL;
NSString *DSASignature;
NSString *minimumSystemVersion;
NSURL *fileURL;
NSString *versionString;
NSString *displayVersionString;
NSDictionary *propertiesDictionary;
}
// Initializes with data from a dictionary provided by the RSS class.
- initWithDictionary:(NSDictionary *)dict;
- (NSString *)title;
- (NSString *)versionString;
- (NSString *)displayVersionString;
- (NSDate *)date;
- (NSString *)itemDescription;
- (NSURL *)releaseNotesURL;
- (NSURL *)fileURL;
- (NSString *)DSASignature;
- (NSString *)minimumSystemVersion;
// Returns the dictionary provided in initWithDictionary; this might be useful later for extensions.
- (NSDictionary *)propertiesDictionary;
@end
#endif

View File

@@ -0,0 +1,118 @@
//
// SUUpdater.h
// Sparkle
//
// Created by Andy Matuschak on 1/4/06.
// Copyright 2006 Andy Matuschak. All rights reserved.
//
#ifndef SUUPDATER_H
#define SUUPDATER_H
#import <Sparkle/SUVersionComparisonProtocol.h>
@class SUUpdateDriver, SUAppcastItem, SUHost, SUAppcast;
@interface SUUpdater : NSObject {
NSTimer *checkTimer;
SUUpdateDriver *driver;
SUHost *host;
IBOutlet id delegate;
}
+ (SUUpdater *)sharedUpdater;
+ (SUUpdater *)updaterForBundle:(NSBundle *)bundle;
- (NSBundle *)hostBundle;
- (void)setDelegate:(id)delegate;
- delegate;
- (void)setAutomaticallyChecksForUpdates:(BOOL)automaticallyChecks;
- (BOOL)automaticallyChecksForUpdates;
- (void)setUpdateCheckInterval:(NSTimeInterval)interval;
- (NSTimeInterval)updateCheckInterval;
- (void)setFeedURL:(NSURL *)feedURL;
- (NSURL *)feedURL;
- (void)setSendsSystemProfile:(BOOL)sendsSystemProfile;
- (BOOL)sendsSystemProfile;
- (void)setAutomaticallyDownloadsUpdates:(BOOL)automaticallyDownloadsUpdates;
- (BOOL)automaticallyDownloadsUpdates;
// This IBAction is meant for a main menu item. Hook up any menu item to this action,
// and Sparkle will check for updates and report back its findings verbosely.
- (IBAction)checkForUpdates:sender;
// This kicks off an update meant to be programmatically initiated. That is, it will display no UI unless it actually finds an update,
// in which case it proceeds as usual. If the fully automated updating is turned on, however, this will invoke that behavior, and if an
// update is found, it will be downloaded and prepped for installation.
- (void)checkForUpdatesInBackground;
// Date of last update check. Returns null if no check has been performed.
- (NSDate*)lastUpdateCheckDate;
// This begins a "probing" check for updates which will not actually offer to update to that version. The delegate methods, though,
// (up to updater:didFindValidUpdate: and updaterDidNotFindUpdate:), are called, so you can use that information in your UI.
- (void)checkForUpdateInformation;
// Call this to appropriately schedule or cancel the update checking timer according to the preferences for time interval and automatic checks. This call does not change the date of the next check, but only the internal NSTimer.
- (void)resetUpdateCycle;
- (BOOL)updateInProgress;
@end
@interface NSObject (SUUpdaterDelegateInformalProtocol)
// This method allows you to add extra parameters to the appcast URL, potentially based on whether or not Sparkle will also be sending along the system profile. This method should return an array of dictionaries with keys: "key", "value", "displayKey", "displayValue", the latter two being specifically for display to the user.
- (NSArray *)feedParametersForUpdater:(SUUpdater *)updater sendingSystemProfile:(BOOL)sendingProfile;
// Use this to override the default behavior for Sparkle prompting the user about automatic update checks.
- (BOOL)updaterShouldPromptForPermissionToCheckForUpdates:(SUUpdater *)bundle;
// Implement this if you want to do some special handling with the appcast once it finishes loading.
- (void)updater:(SUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcast;
// If you're using special logic or extensions in your appcast, implement this to use your own logic for finding
// a valid update, if any, in the given appcast.
- (SUAppcastItem *)bestValidUpdateInAppcast:(SUAppcast *)appcast forUpdater:(SUUpdater *)bundle;
// Sent when a valid update is found by the update driver.
- (void)updater:(SUUpdater *)updater didFindValidUpdate:(SUAppcastItem *)update;
// Sent when a valid update is not found.
- (void)updaterDidNotFindUpdate:(SUUpdater *)update;
// Sent immediately before installing the specified update.
- (void)updater:(SUUpdater *)updater willInstallUpdate:(SUAppcastItem *)update;
// Return YES to delay the relaunch until you do some processing; invoke the given NSInvocation to continue.
- (BOOL)updater:(SUUpdater *)updater shouldPostponeRelaunchForUpdate:(SUAppcastItem *)update untilInvoking:(NSInvocation *)invocation;
// Called immediately before relaunching.
- (void)updaterWillRelaunchApplication:(SUUpdater *)updater;
// This method allows you to provide a custom version comparator.
// If you don't implement this method or return nil, the standard version comparator will be used.
- (id <SUVersionComparison>)versionComparatorForUpdater:(SUUpdater *)updater;
// Returns the path which is used to relaunch the client after the update is installed. By default, the path of the host bundle.
- (NSString *)pathToRelaunchForUpdater:(SUUpdater *)updater;
@end
// Define some minimum intervals to avoid DOS-like checking attacks. These are in seconds.
#ifdef DEBUG
#define SU_MIN_CHECK_INTERVAL 60
#else
#define SU_MIN_CHECK_INTERVAL 60*60
#endif
#ifdef DEBUG
#define SU_DEFAULT_CHECK_INTERVAL 60
#else
#define SU_DEFAULT_CHECK_INTERVAL 60*60*24
#endif
#endif

View File

@@ -0,0 +1,27 @@
//
// SUVersionComparisonProtocol.h
// Sparkle
//
// Created by Andy Matuschak on 12/21/07.
// Copyright 2007 Andy Matuschak. All rights reserved.
//
#ifndef SUVERSIONCOMPARISONPROTOCOL_H
#define SUVERSIONCOMPARISONPROTOCOL_H
/*!
@protocol
@abstract Implement this protocol to provide version comparison facilities for Sparkle.
*/
@protocol SUVersionComparison
/*!
@method
@abstract An abstract method to compare two version strings.
@discussion Should return NSOrderedAscending if b > a, NSOrderedDescending if b < a, and NSOrderedSame if they are equivalent.
*/
- (NSComparisonResult)compareVersion:(NSString *)versionA toVersion:(NSString *)versionB;
@end
#endif

View File

@@ -0,0 +1,21 @@
//
// Sparkle.h
// Sparkle
//
// Created by Andy Matuschak on 3/16/06. (Modified by CDHW on 23/12/07)
// Copyright 2006 Andy Matuschak. All rights reserved.
//
#ifndef SPARKLE_H
#define SPARKLE_H
// This list should include the shared headers. It doesn't matter if some of them aren't shared (unless
// there are name-space collisions) so we can list all of them to start with:
#import <Sparkle/SUUpdater.h>
#import <Sparkle/SUAppcast.h>
#import <Sparkle/SUAppcastItem.h>
#import <Sparkle/SUVersionComparisonProtocol.h>
#endif

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>Sparkle</string>
<key>CFBundleIdentifier</key>
<string>org.andymatuschak.Sparkle</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Sparkle</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.5 Beta 6</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>313</string>
</dict>
</plist>

View File

@@ -0,0 +1,7 @@
Copyright (c) 2006 Andy Matuschak
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ADP2,1</key>
<string>Developer Transition Kit</string>
<key>MacBook1,1</key>
<string>MacBook (Core Duo)</string>
<key>MacBook2,1</key>
<string>MacBook (Core 2 Duo)</string>
<key>MacBook4,1</key>
<string>MacBook (Core 2 Duo Feb 2008)</string>
<key>MacBookAir1,1</key>
<string>MacBook Air (January 2008)</string>
<key>MacBookPro1,1</key>
<string>MacBook Pro Core Duo (15-inch)</string>
<key>MacBookPro1,2</key>
<string>MacBook Pro Core Duo (17-inch)</string>
<key>MacBookPro2,1</key>
<string>MacBook Pro Core 2 Duo (17-inch)</string>
<key>MacBookPro2,2</key>
<string>MacBook Pro Core 2 Duo (15-inch)</string>
<key>MacBookPro3,1</key>
<string>MacBook Pro Core 2 Duo (15-inch LED, Core 2 Duo)</string>
<key>MacBookPro3,2</key>
<string>MacBook Pro Core 2 Duo (17-inch HD, Core 2 Duo)</string>
<key>MacBookPro4,1</key>
<string>MacBook Pro (Core 2 Duo Feb 2008)</string>
<key>MacPro1,1</key>
<string>Mac Pro (four-core)</string>
<key>MacPro2,1</key>
<string>Mac Pro (eight-core)</string>
<key>MacPro3,1</key>
<string>Mac Pro (January 2008 4- or 8- core "Harpertown")</string>
<key>Macmini1,1</key>
<string>Mac Mini (Core Solo/Duo)</string>
<key>PowerBook1,1</key>
<string>PowerBook G3</string>
<key>PowerBook2,1</key>
<string>iBook G3</string>
<key>PowerBook2,2</key>
<string>iBook G3 (FireWire)</string>
<key>PowerBook2,3</key>
<string>iBook G3</string>
<key>PowerBook2,4</key>
<string>iBook G3</string>
<key>PowerBook3,1</key>
<string>PowerBook G3 (FireWire)</string>
<key>PowerBook3,2</key>
<string>PowerBook G4</string>
<key>PowerBook3,3</key>
<string>PowerBook G4 (Gigabit Ethernet)</string>
<key>PowerBook3,4</key>
<string>PowerBook G4 (DVI)</string>
<key>PowerBook3,5</key>
<string>PowerBook G4 (1GHz / 867MHz)</string>
<key>PowerBook4,1</key>
<string>iBook G3 (Dual USB, Late 2001)</string>
<key>PowerBook4,2</key>
<string>iBook G3 (16MB VRAM)</string>
<key>PowerBook4,3</key>
<string>iBook G3 Opaque 16MB VRAM, 32MB VRAM, Early 2003)</string>
<key>PowerBook5,1</key>
<string>PowerBook G4 (17 inch)</string>
<key>PowerBook5,2</key>
<string>PowerBook G4 (15 inch FW 800)</string>
<key>PowerBook5,3</key>
<string>PowerBook G4 (17-inch 1.33GHz)</string>
<key>PowerBook5,4</key>
<string>PowerBook G4 (15 inch 1.5/1.33GHz)</string>
<key>PowerBook5,5</key>
<string>PowerBook G4 (17-inch 1.5GHz)</string>
<key>PowerBook5,6</key>
<string>PowerBook G4 (15 inch 1.67GHz/1.5GHz)</string>
<key>PowerBook5,7</key>
<string>PowerBook G4 (17-inch 1.67GHz)</string>
<key>PowerBook5,8</key>
<string>PowerBook G4 (Double layer SD, 15 inch)</string>
<key>PowerBook5,9</key>
<string>PowerBook G4 (Double layer SD, 17 inch)</string>
<key>PowerBook6,1</key>
<string>PowerBook G4 (12 inch)</string>
<key>PowerBook6,2</key>
<string>PowerBook G4 (12 inch, DVI)</string>
<key>PowerBook6,3</key>
<string>iBook G4</string>
<key>PowerBook6,4</key>
<string>PowerBook G4 (12 inch 1.33GHz)</string>
<key>PowerBook6,5</key>
<string>iBook G4 (Early-Late 2004)</string>
<key>PowerBook6,7</key>
<string>iBook G4 (Mid 2005)</string>
<key>PowerBook6,8</key>
<string>PowerBook G4 (12 inch 1.5GHz)</string>
<key>PowerMac1,1</key>
<string>Power Macintosh G3 (Blue &amp; White)</string>
<key>PowerMac1,2</key>
<string>Power Macintosh G4 (PCI Graphics)</string>
<key>PowerMac10,1</key>
<string>Mac Mini G4</string>
<key>PowerMac10,2</key>
<string>Mac Mini (Late 2005)</string>
<key>PowerMac11,2</key>
<string>Power Macintosh G5 (Late 2005)</string>
<key>PowerMac12,1</key>
<string>iMac G5 (iSight)</string>
<key>PowerMac2,1</key>
<string>iMac G3 (Slot-loading CD-ROM)</string>
<key>PowerMac2,2</key>
<string>iMac G3 (Summer 2000)</string>
<key>PowerMac3,1</key>
<string>Power Macintosh G4 (AGP Graphics)</string>
<key>PowerMac3,2</key>
<string>Power Macintosh G4 (AGP Graphics)</string>
<key>PowerMac3,3</key>
<string>Power Macintosh G4 (Gigabit Ethernet)</string>
<key>PowerMac3,4</key>
<string>Power Macintosh G4 (Digital Audio)</string>
<key>PowerMac3,5</key>
<string>Power Macintosh G4 (Quick Silver)</string>
<key>PowerMac3,6</key>
<string>Power Macintosh G4 (Mirrored Drive Door)</string>
<key>PowerMac4,1</key>
<string>iMac G3 (Early/Summer 2001)</string>
<key>PowerMac4,2</key>
<string>iMac G4 (Flat Panel)</string>
<key>PowerMac4,4</key>
<string>eMac</string>
<key>PowerMac4,5</key>
<string>iMac G4 (17-inch Flat Panel)</string>
<key>PowerMac5,1</key>
<string>Power Macintosh G4 Cube</string>
<key>PowerMac6,1</key>
<string>iMac G4 (USB 2.0)</string>
<key>PowerMac6,3</key>
<string>iMac G4 (20-inch Flat Panel)</string>
<key>PowerMac6,4</key>
<string>eMac (USB 2.0, 2005)</string>
<key>PowerMac7,2</key>
<string>Power Macintosh G5</string>
<key>PowerMac7,3</key>
<string>Power Macintosh G5</string>
<key>PowerMac8,1</key>
<string>iMac G5</string>
<key>PowerMac8,2</key>
<string>iMac G5 (Ambient Light Sensor)</string>
<key>PowerMac9,1</key>
<string>Power Macintosh G5 (Late 2005)</string>
<key>RackMac1,1</key>
<string>Xserve G4</string>
<key>RackMac1,2</key>
<string>Xserve G4 (slot-loading, cluster node)</string>
<key>RackMac3,1</key>
<string>Xserve G5</string>
<key>Xserve1,1</key>
<string>Xserve (Intel Xeon)</string>
<key>Xserve2,1</key>
<string>Xserve (January 2008 quad-core)</string>
<key>iMac1,1</key>
<string>iMac G3 (Rev A-D)</string>
<key>iMac4,1</key>
<string>iMac (Core Duo)</string>
<key>iMac4,2</key>
<string>iMac for Education (17-inch, Core Duo)</string>
<key>iMac5,1</key>
<string>iMac (Core 2 Duo, 17 or 20 inch, SuperDrive)</string>
<key>iMac5,2</key>
<string>iMac (Core 2 Duo, 17 inch, Combo Drive)</string>
<key>iMac6,1</key>
<string>iMac (Core 2 Duo, 24 inch, SuperDrive)</string>
<key>iMac8,1</key>
<string>iMac (April 2008)</string>
</dict>
</plist>

View File

@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBClasses</key>
<array>
<dict>
<key>CLASS</key>
<string>SUWindowController</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSWindowController</string>
</dict>
<dict>
<key>CLASS</key>
<string>NSApplication</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSResponder</string>
</dict>
<dict>
<key>CLASS</key>
<string>FirstResponder</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>CLASS</key>
<string>NSObject</string>
<key>LANGUAGE</key>
<string>ObjC</string>
</dict>
<dict>
<key>CLASS</key>
<string>SUStatusController</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>actionButton</key>
<string>NSButton</string>
<key>progressBar</key>
<string>NSProgressIndicator</string>
</dict>
<key>SUPERCLASS</key>
<string>SUWindowController</string>
</dict>
</array>
<key>IBVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBFramework Version</key>
<string>670</string>
<key>IBLastKnownRelativeProjectPath</key>
<string>Sparkle.xcodeproj</string>
<key>IBOldestOS</key>
<integer>5</integer>
<key>IBOpenObjects</key>
<array>
<integer>6</integer>
</array>
<key>IBSystem Version</key>
<string>10A96</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
</plist>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBClasses</key>
<array>
<dict>
<key>CLASS</key>
<string>SUWindowController</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSWindowController</string>
</dict>
<dict>
<key>ACTIONS</key>
<dict>
<key>doNotInstall</key>
<string>id</string>
<key>installLater</key>
<string>id</string>
<key>installNow</key>
<string>id</string>
</dict>
<key>CLASS</key>
<string>SUAutomaticUpdateAlert</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>SUWindowController</string>
</dict>
<dict>
<key>CLASS</key>
<string>FirstResponder</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>CLASS</key>
<string>NSObject</string>
<key>LANGUAGE</key>
<string>ObjC</string>
</dict>
</array>
<key>IBVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBFramework Version</key>
<string>658</string>
<key>IBLastKnownRelativeProjectPath</key>
<string>../Sparkle.xcodeproj</string>
<key>IBOldestOS</key>
<integer>5</integer>
<key>IBOpenObjects</key>
<array>
<integer>6</integer>
</array>
<key>IBSystem Version</key>
<string>9C7010</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
</plist>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBClasses</key>
<array>
<dict>
<key>CLASS</key>
<string>SUWindowController</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSWindowController</string>
</dict>
<dict>
<key>CLASS</key>
<string>NSApplication</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSResponder</string>
</dict>
<dict>
<key>ACTIONS</key>
<dict>
<key>installUpdate</key>
<string>id</string>
<key>remindMeLater</key>
<string>id</string>
<key>skipThisVersion</key>
<string>id</string>
</dict>
<key>CLASS</key>
<string>SUUpdateAlert</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>delegate</key>
<string>id</string>
<key>description</key>
<string>NSTextField</string>
<key>releaseNotesView</key>
<string>WebView</string>
</dict>
<key>SUPERCLASS</key>
<string>SUWindowController</string>
</dict>
<dict>
<key>CLASS</key>
<string>FirstResponder</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>CLASS</key>
<string>NSObject</string>
<key>LANGUAGE</key>
<string>ObjC</string>
</dict>
</array>
<key>IBVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBFramework Version</key>
<string>670</string>
<key>IBLastKnownRelativeProjectPath</key>
<string>../Sparkle.xcodeproj</string>
<key>IBOldestOS</key>
<integer>5</integer>
<key>IBOpenObjects</key>
<array>
<integer>18</integer>
</array>
<key>IBSystem Version</key>
<string>10A96</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
</plist>

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBClasses</key>
<array>
<dict>
<key>CLASS</key>
<string>SUWindowController</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSWindowController</string>
</dict>
<dict>
<key>ACTIONS</key>
<dict>
<key>finishPrompt</key>
<string>id</string>
<key>toggleMoreInfo</key>
<string>id</string>
</dict>
<key>CLASS</key>
<string>SUUpdatePermissionPrompt</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>OUTLETS</key>
<dict>
<key>delegate</key>
<string>id</string>
<key>descriptionTextField</key>
<string>NSTextField</string>
<key>moreInfoButton</key>
<string>NSButton</string>
<key>moreInfoView</key>
<string>NSView</string>
</dict>
<key>SUPERCLASS</key>
<string>SUWindowController</string>
</dict>
<dict>
<key>CLASS</key>
<string>FirstResponder</string>
<key>LANGUAGE</key>
<string>ObjC</string>
<key>SUPERCLASS</key>
<string>NSObject</string>
</dict>
<dict>
<key>CLASS</key>
<string>NSObject</string>
<key>LANGUAGE</key>
<string>ObjC</string>
</dict>
</array>
<key>IBVersion</key>
<string>1</string>
</dict>
</plist>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBFramework Version</key>
<string>670</string>
<key>IBLastKnownRelativeProjectPath</key>
<string>../Sparkle.xcodeproj</string>
<key>IBOldestOS</key>
<integer>5</integer>
<key>IBOpenObjects</key>
<array>
<integer>6</integer>
<integer>41</integer>
</array>
<key>IBSystem Version</key>
<string>10A96</string>
<key>targetFramework</key>
<string>IBCocoaFramework</string>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
A

36
cocoalib/Utils.h Normal file
View File

@@ -0,0 +1,36 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
//Useful shortcuts
#define i2n(i) [NSNumber numberWithInteger:i]
#define n2i(n) [n integerValue]
#define b2n(b) [NSNumber numberWithBool:b]
#define n2b(n) [n boolValue]
#if __LP64__
#define f2n(d) [NSNumber numberWithDouble:d]
#define n2f(n) [n doubleValue]
#else
#define f2n(f) [NSNumber numberWithFloat:f]
#define n2f(n) [n floatValue]
#endif
#define p2a(p) [Utils indexPath2Array:p]
#define a2p(a) [Utils array2IndexPath:a]
#define fmt(x,...) [NSString stringWithFormat:x,__VA_ARGS__]
@interface Utils : NSObject
+ (NSArray *)indexSet2Array:(NSIndexSet *)aIndexSet;
+ (NSIndexSet *)array2IndexSet:(NSArray *)numberArray;
+ (NSArray *)indexPath2Array:(NSIndexPath *)aIndexPath;
+ (NSIndexPath *)array2IndexPath:(NSArray *)indexArray;
+ (NSString *)indexPath2String:(NSIndexPath *)aIndexPath;
+ (NSIndexPath *)string2IndexPath:(NSString *)aString;
@end
void replacePlaceholderInView(NSView *placeholder, NSView *replaceWith);

92
cocoalib/Utils.m Normal file
View File

@@ -0,0 +1,92 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "Utils.h"
#import <CoreServices/CoreServices.h>
@implementation Utils
//This is to pass index sets to python as arrays (so it can be converted to native lists)
+ (NSArray *)indexSet2Array:(NSIndexSet *)aIndexSet
{
NSMutableArray *r = [NSMutableArray array];
NSInteger i = [aIndexSet firstIndex];
while (i != NSNotFound)
{
[r addObject:[NSNumber numberWithInteger:i]];
i = [aIndexSet indexGreaterThanIndex:i];
}
return r;
}
// numberArray is an array of NSNumber
+ (NSIndexSet *)array2IndexSet:(NSArray *)numberArray
{
NSMutableIndexSet *set = [NSMutableIndexSet indexSet];
NSEnumerator *e = [numberArray objectEnumerator];
NSNumber *n;
while (n = [e nextObject])
[set addIndex:n2i(n)];
return set;
}
//Changes an NSIndexPath into an NSArray
+ (NSArray *)indexPath2Array:(NSIndexPath *)aIndexPath
{
NSMutableArray *r = [NSMutableArray array];
if (!aIndexPath)
return r;
for (int i=0;i<[aIndexPath length];i++)
[r addObject:i2n([aIndexPath indexAtPosition:i])];
return r;
}
// Changes a NSArray of numbers into a NSIndexPath
// indexArray must have at least one item
+ (NSIndexPath *)array2IndexPath:(NSArray *)indexArray
{
if (![indexArray count])
{
return nil;
}
NSEnumerator *e = [indexArray objectEnumerator];
NSNumber *n = [e nextObject];
NSIndexPath *ip = [NSIndexPath indexPathWithIndex:n2i(n)];
while (n = [e nextObject])
ip = [ip indexPathByAddingIndex:n2i(n)];
return ip;
}
+ (NSString *)indexPath2String:(NSIndexPath *)aIndexPath
{
NSMutableArray *components = [NSMutableArray array];
for (int i=0; i<[aIndexPath length]; i++)
[components addObject:i2n([aIndexPath indexAtPosition:i])];
return [components componentsJoinedByString:@"_"];
}
+ (NSIndexPath *)string2IndexPath:(NSString *)aString
{
if (aString == nil)
{
return nil;
}
NSArray *components = [aString componentsSeparatedByString:@"_"];
NSMutableArray *indexes = [NSMutableArray array];
for (int i=0; i<[components count]; i++)
[indexes addObject:i2n([[components objectAtIndex:i] intValue])];
return [Utils array2IndexPath:indexes];
}
@end
void replacePlaceholderInView(NSView *placeholder, NSView *replaceWith)
{
NSView *parent = [placeholder superview];
[replaceWith setFrame:[placeholder frame]];
[replaceWith setAutoresizingMask:[placeholder autoresizingMask]];
[parent replaceSubview:placeholder with:replaceWith];
}

View File

@@ -0,0 +1,26 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
@interface VTIsIntIn : NSValueTransformer
{
NSIndexSet *ints;
BOOL reverse;
}
- (id)initWithValues:(NSIndexSet *)values;
- (id)initWithValues:(NSIndexSet *)values reverse:(BOOL)doReverse;
@end
@interface HSVTAdd : NSValueTransformer
{
int toAdd;
}
- (id)initWithValue:(int)value;
@end

View File

@@ -0,0 +1,79 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "ValueTransformers.h"
#import "Utils.h"
@implementation VTIsIntIn
- (id)initWithValues:(NSIndexSet *)values
{
return [self initWithValues:values reverse:NO];
}
- (id)initWithValues:(NSIndexSet *)values reverse:(BOOL)doReverse
{
self = [super init];
ints = values;
[ints retain];
reverse = doReverse;
return self;
}
- (void)dealloc
{
[ints release];
[super dealloc];
}
+ (Class)transformedValueClass
{
return [NSNumber class]; //Boolean
}
+ (BOOL)allowsReverseTransformation
{
return NO;
}
- (id)transformedValue:(id)value
{
if (value == nil)
return nil;
NSNumber *i = value;
BOOL r = [ints containsIndex:[i intValue]];
if (reverse)
r = !r;
return [NSNumber numberWithBool:r];
}
@end
@implementation HSVTAdd
- (id)initWithValue:(int)value
{
self = [super init];
toAdd = value;
return self;
}
+ (Class)transformedValueClass
{
return [NSNumber class];
}
+ (BOOL)allowsReverseTransformation
{
return NO;
}
- (id)transformedValue:(id)value
{
if (value == nil)
return nil;
return i2n(n2i(value) + toAdd);
}
@end

14
cocoalib/Worker.h Normal file
View File

@@ -0,0 +1,14 @@
#import <Cocoa/Cocoa.h>
//The worker should work in a separate thread or have it's own mechanism to keep the GUI updated as ProgressController
//provides none.
@protocol Worker
// -1: Indeterminate. nil: Not working. 0-100: Progressing
- (NSNumber *)getJobProgress;
- (NSString *)getJobDesc;
- (void)cancelJob;
/* This might seem a little stupid, but it's the simplest way to get a **sync** call to the python
side after a job. Because the python-side app is not an NSObject subclass, it can't listen to
notifications. */
- (void)jobCompleted:(NSString *)jobid;
@end

View File

@@ -0,0 +1,34 @@
#import <Cocoa/Cocoa.h>
@interface CocoaProxy : NSObject
{
NSAutoreleasePool *currentPool;
}
- (void)openPath:(NSString *)path;
- (void)openURL:(NSString *)url;
- (void)revealPath:(NSString *)path;
- (NSString *)getUTI:(NSString *)path;
- (BOOL)type:(NSString *)type conformsToType:(NSString *)refType;
- (NSString *)getAppdataPath;
- (NSString *)getCachePath;
- (NSString *)getResourcePath;
- (NSString *)systemLang;
- (NSString *)systemShortDateFormat;
- (NSString *)systemNumberDecimalSeparator;
- (NSString *)systemNumberGroupingSeparator;
- (NSString *)systemCurrency;
- (NSString *)bundleIdentifier;
- (NSString *)appVersion;
- (NSString *)osxVersion;
- (NSString *)bundleInfo:(NSString *)key;
- (void)postNotification:(NSString *)name userInfo:(NSDictionary *)userInfo;
- (id)prefValue:(NSString *)prefname;
- (void)setPrefValue:(NSString *)prefname value:(id)value;
- (id)prefValue:(NSString *)prefname inDomain:(NSString *)domain;
- (NSString *)url2path:(NSString *)url;
- (void)createPool;
- (void)destroyPool;
- (void)reportCrash:(NSString *)crashReport;
- (void)log:(NSString *)s;
- (NSDictionary *)readExifData:(NSString *)imagePath;
@end

171
cocoalib/cocoa/CocoaProxy.m Normal file
View File

@@ -0,0 +1,171 @@
#import "CocoaProxy.h"
#import "HSErrorReportWindow.h"
@implementation CocoaProxy
- (void)openPath:(NSString *)path
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:path isDirectory:NO]];
}
- (void)openURL:(NSString *)url
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
}
- (void)revealPath:(NSString *)path
{
[[NSWorkspace sharedWorkspace] selectFile:path inFileViewerRootedAtPath:@""];
}
- (NSString *)getUTI:(NSString *)path
{
NSError *error;
return [[NSWorkspace sharedWorkspace] typeOfFile:path error:&error];
}
- (BOOL)type:(NSString *)type conformsToType:(NSString *)refType
{
return [[NSWorkspace sharedWorkspace] type:type conformsToType:refType];
}
- (NSString *)getAppdataPath
{
return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
- (NSString *)getCachePath
{
return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
- (NSString *)getResourcePath
{
return [[[NSBundle mainBundle] resourceURL] path];
}
- (NSString *)systemLang
{
return [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
}
- (NSString *)systemShortDateFormat
{
[NSDateFormatter setDefaultFormatterBehavior:NSDateFormatterBehavior10_4];
NSDateFormatter *f = [[NSDateFormatter alloc] init];
[f setDateStyle:NSDateFormatterShortStyle];
[f setTimeStyle:NSDateFormatterNoStyle];
NSString *result = [[f dateFormat] retain];
[f release];
return [result autorelease];
}
- (NSString *)systemNumberDecimalSeparator
{
[NSNumberFormatter setDefaultFormatterBehavior:NSNumberFormatterBehavior10_4];
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSString *result = [[f decimalSeparator] retain];
[f release];
return [result autorelease];
}
- (NSString *)systemNumberGroupingSeparator
{
[NSNumberFormatter setDefaultFormatterBehavior:NSNumberFormatterBehavior10_4];
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSString *result = [[f groupingSeparator] retain];
[f release];
return [result autorelease];
}
- (NSString *)systemCurrency
{
return [[NSLocale currentLocale] objectForKey:NSLocaleCurrencyCode];
}
- (NSString *)bundleIdentifier
{
return [[NSBundle mainBundle] bundleIdentifier];
}
- (NSString *)appVersion
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
}
- (NSString *)bundleInfo:(NSString *)key
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:key];
}
- (NSString *)osxVersion
{
return [[NSProcessInfo processInfo] operatingSystemVersionString];
}
- (void)postNotification:(NSString *)name userInfo:(NSDictionary *)userInfo
{
[[NSNotificationCenter defaultCenter] postNotificationName:name object:nil userInfo:userInfo];
}
- (id)prefValue:(NSString *)prefname
{
return [[NSUserDefaults standardUserDefaults] objectForKey:prefname];
}
- (void)setPrefValue:(NSString *)prefname value:(id)value
{
[[NSUserDefaults standardUserDefaults] setObject:value forKey:prefname];
}
- (id)prefValue:(NSString *)prefname inDomain:(NSString *)domain
{
NSDictionary *dict = [[NSUserDefaults standardUserDefaults] persistentDomainForName:domain];
return [dict objectForKey:prefname];
}
// Changes a file:/// path into a normal path
- (NSString *)url2path:(NSString *)url
{
NSURL *u = [NSURL URLWithString:url];
return [u path];
}
// Create a pool for use into a separate thread.
- (void)createPool
{
[self destroyPool];
currentPool = [[NSAutoreleasePool alloc] init];
}
- (void)destroyPool
{
if (currentPool != nil) {
[currentPool release];
currentPool = nil;
}
}
- (void)reportCrash:(NSString *)crashReport
{
[HSErrorReportWindow showErrorReportWithContent:crashReport];
}
- (void)log:(NSString *)s
{
NSLog(@"%@", s);
}
- (NSDictionary *)readExifData:(NSString *)imagePath
{
NSDictionary *result = nil;
NSURL* url = [NSURL fileURLWithPath:imagePath];
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, nil);
if (source != nil) {
CFDictionaryRef metadataRef = CGImageSourceCopyPropertiesAtIndex (source, 0, nil);
if (metadataRef != nil) {
result = [NSDictionary dictionaryWithDictionary:(NSDictionary *)metadataRef];
CFRelease(metadataRef);
}
CFRelease(source);
}
return result;
}
@end

117
cocoalib/cocoa/__init__.py Normal file
View File

@@ -0,0 +1,117 @@
# Created By: Virgil Dupras
# Created On: 2007-10-06
# Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
# This software is licensed under the "BSD" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.hardcoded.net/licenses/bsd_license
import logging
import time
import traceback
import subprocess
import sys
from .CocoaProxy import CocoaProxy
proxy = CocoaProxy()
def autoreleasepool(func):
def wrapper(*args, **kwargs):
proxy.createPool()
try:
func(*args, **kwargs)
finally:
proxy.destroyPool()
return wrapper
def as_fetch(as_list, as_type, step_size=1000):
"""When fetching items from a very big list through applescript, the connection with the app
will timeout. This function is to circumvent that. 'as_type' is the type of the items in the
list (found in appscript.k). If we don't pass it to the 'each' arg of 'count()', it doesn't work.
applescript is rather stupid..."""
result = []
# no timeout. default timeout is 60 secs, and it is reached for libs > 30k songs
item_count = as_list.count(each=as_type, timeout=0)
steps = item_count // step_size
if item_count % step_size:
steps += 1
logging.info('Fetching %d items in %d steps' % (item_count, steps))
# Don't forget that the indexes are 1-based and that the upper limit is included
for step in range(steps):
begin = step * step_size + 1
end = min(item_count, begin + step_size - 1)
if end > begin:
result += as_list[begin:end](timeout=0)
else: # When there is only one item, the stupid fuck gives it directly instead of putting it in a list.
result.append(as_list[begin:end](timeout=0))
time.sleep(.1)
logging.info('%d items fetched' % len(result))
return result
def extract_tb_noline(tb):
# Same as traceback.extract_tb(), but without line fetching
limit = 100
list = []
n = 0
while tb is not None and (limit is None or n < limit):
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
list.append((filename, lineno, name, None))
tb = tb.tb_next
n = n+1
return list
def safe_format_exception(type, value, tb):
"""Format exception from type, value and tb and fallback if there's a problem.
In some cases in threaded exceptions under Cocoa, I get tracebacks targeting pyc files instead
of py files, which results in traceback.format_exception() trying to print lines from pyc files
and then crashing when trying to interpret that binary data as utf-8. We want a fallback in
these cases.
"""
try:
return traceback.format_exception(type, value, tb)
except Exception:
result = ['Traceback (most recent call last):\n']
result.extend(traceback.format_list(extract_tb_noline(tb)))
result.extend(traceback.format_exception_only(type, value))
return result
def report_crash(type, value, tb):
app_identifier = proxy.bundleIdentifier()
app_version = proxy.appVersion()
osx_version = proxy.osxVersion()
s = "Application Identifier: {}\n".format(app_identifier)
s += "Application Version: {}\n".format(app_version)
s += "Mac OS X Version: {}\n\n".format(osx_version)
s += ''.join(safe_format_exception(type, value, tb))
if app_identifier:
s += '\nRelevant Console logs:\n\n'
p = subprocess.Popen(['grep', app_identifier, '/var/log/system.log'], stdout=subprocess.PIPE)
try:
s += str(p.communicate()[0], encoding='utf-8')
except IndexError:
# This can happen if something went wrong with the grep (permission errors?)
pass
proxy.reportCrash_(s)
def install_exception_hook():
sys.excepthook = report_crash
class CocoaHandler(logging.Handler):
def emit(self, record):
proxy.log_(record.getMessage())
def install_cocoa_logger():
logging.getLogger().addHandler(CocoaHandler())
def patch_threaded_job_performer():
# _async_run, under cocoa, has to be run within an autorelease pool to prevent leaks.
# You only need this patch is you use one of CocoaProxy's function (which allocate objc
# structures) inside a threaded job.
from jobprogress.performer import ThreadedJobPerformer
ThreadedJobPerformer._async_run = autoreleasepool(ThreadedJobPerformer._async_run)

300
cocoalib/cocoa/inter.py Normal file
View File

@@ -0,0 +1,300 @@
import logging
from objp.util import pyref, dontwrap
from . import proxy
class GUIObjectView:
def refresh(self): pass
class PyGUIObject:
def __init__(self, model: pyref):
self.model = model
self.callback = None
# This *has* to be called right after initialization.
def bindCallback_(self, callback: pyref):
self.callback = callback
self.model.view = self
# Call this before the ObjC callback is deallocated to avoid calls to that deallocated instance.
def free(self):
self.model.view = None
self.callback = None
def modelRef(self) -> pyref:
return self.model
#--- Python -> Cocoa
@dontwrap
def refresh(self):
self.callback.refresh()
class PyTextField(PyGUIObject):
def text(self) -> str:
return self.model.text
def setText_(self, newtext: str):
self.model.text = newtext
class SelectableListView(GUIObjectView):
def updateSelection(self): pass
class PySelectableList(PyGUIObject):
def items(self) -> list:
# Should normally always return strings
return self.model[:]
def selectIndex_(self, index: int):
self.model.select(index)
def selectedIndex(self) -> int:
result = self.model.selected_index
if result is None:
result = -1
return result
def selectedIndexes(self) -> list:
return self.model.selected_indexes
def selectIndexes_(self, indexes: list):
self.model.select(indexes)
def searchByPrefix_(self, prefix: str) -> int:
return self.model.search_by_prefix(prefix)
#--- model --> view
@dontwrap
def update_selection(self):
self.callback.updateSelection()
class ColumnsView:
def restoreColumns(self): pass
def setColumn_visible_(self, colname: str, visible: bool): pass
class PyColumns(PyGUIObject):
def columnNamesInOrder(self) -> list:
return self.model.colnames
def columnDisplay_(self, colname: str) -> str:
return self.model.column_display(colname)
def columnIsVisible_(self, colname: str) -> bool:
return self.model.column_is_visible(colname)
def columnWidth_(self, colname: str) -> int:
return self.model.column_width(colname)
def moveColumn_toIndex_(self, colname: str, index: int):
self.model.move_column(colname, index)
def resizeColumn_toWidth_(self, colname: str, newwidth: int):
self.model.resize_column(colname, newwidth)
def setColumn_defaultWidth_(self, colname: str, width: int):
self.model.set_default_width(colname, width)
def menuItems(self) -> list:
return self.model.menu_items()
def toggleMenuItem_(self, index: int) -> bool:
return self.model.toggle_menu_item(index)
def resetToDefaults(self):
self.model.reset_to_defaults()
#--- Python --> Cocoa
@dontwrap
def restore_columns(self):
self.callback.restoreColumns()
@dontwrap
def set_column_visible(self, colname: str, visible):
self.callback.setColumn_visible_(colname, visible)
class OutlineView(GUIObjectView):
def startEditing(self): pass
def stopEditing(self): pass
def updateSelection(self): pass
class PyOutline(PyGUIObject):
def cancelEdits(self):
self.model.cancel_edits()
def canEditProperty_atPath_(self, property: str, path: list) -> bool:
node = self.model.get_node(path)
assert node is self.model.selected_node
return getattr(node, 'can_edit_' + property, False)
def saveEdits(self):
self.model.save_edits()
def selectedPath(self) -> list:
return self.model.selected_path
def setSelectedPath_(self, path: list):
self.model.selected_path = path
def selectedPaths(self) -> list:
return self.model.selected_paths
def setSelectedPaths_(self, paths: list):
self.model.selected_paths = paths
def property_valueAtPath_(self, property: str, path: list) -> object:
try:
return getattr(self.model.get_node(path), property)
except IndexError:
logging.warning("%r doesn't have a node at path %r", self.model, path)
return ''
def setProperty_value_atPath_(self, property: str, value: object, path: list):
setattr(self.model.get_node(path), property, value)
#--- Python -> Cocoa
@dontwrap
def start_editing(self):
self.callback.startEditing()
@dontwrap
def stop_editing(self):
self.callback.stopEditing()
@dontwrap
def update_selection(self):
self.callback.updateSelection()
class TableView(GUIObjectView):
def showSelectedRow(self): pass
def startEditing(self): pass
def stopEditing(self): pass
def updateSelection(self): pass
class PyTable(PyGUIObject):
#--- Helpers
@dontwrap
def _getrow(self, row):
try:
return self.model[row]
except IndexError:
msg = "Trying to get an out of bounds row ({} / {}) on table {}"
logging.warning(msg.format(row, len(self.model), self.model.__class__.__name__))
#--- Cocoa --> Python
def columns(self) -> pyref:
return self.model.columns
def add(self):
self.model.add()
def cancelEdits(self):
self.model.cancel_edits()
def canEditColumn_atRow_(self, column: str, row: int) -> object:
return self.model.can_edit_cell(column, row)
def deleteSelectedRows(self):
self.model.delete()
def numberOfRows(self) -> int:
return len(self.model)
def saveEdits(self):
self.model.save_edits()
def selectRows_(self, rows: list):
self.model.select(list(rows))
def selectedRows(self) -> list:
return self.model.selected_indexes
def selectionAsCSV(self) -> str:
return self.model.selection_as_csv()
def setValue_forColumn_row_(self, value: object, column: str, row: int):
# this try except is important for the case while a row is in edition mode and the delete
# button is clicked.
try:
self._getrow(row).set_cell_value(column, value)
except AttributeError:
msg = "Trying to set an attribute that can't: {} with value {} at row {} on table {}"
logging.warning(msg.format(column, value, row, self.model.__class__.__name__))
raise
def sortByColumn_desc_(self, column: str, desc: bool):
self.model.sort_by(column, desc=desc)
def valueForColumn_row_(self, column: str, row: int) -> object:
return self._getrow(row).get_cell_value(column)
#--- Python -> Cocoa
@dontwrap
def show_selected_row(self):
self.callback.showSelectedRow()
@dontwrap
def start_editing(self):
self.callback.startEditing()
@dontwrap
def stop_editing(self):
self.callback.stopEditing()
@dontwrap
def update_selection(self):
self.callback.updateSelection()
class ProgressWindowView(GUIObjectView):
def setProgress_(self, progress: int): pass
def showWindow(self): pass
def closeWindow(self): pass
class PyProgressWindow(PyGUIObject):
def jobdescTextField(self) -> pyref:
return self.model.jobdesc_textfield
def progressdescTextField(self) -> pyref:
return self.model.progressdesc_textfield
def pulse(self):
self.model.pulse()
def cancel(self):
self.model.cancel()
#--- Python -> Cocoa
@dontwrap
def set_progress(self, last_progress):
self.callback.setProgress_(last_progress)
@dontwrap
def show(self):
self.callback.showWindow()
@dontwrap
def close(self):
self.callback.closeWindow()
class BaseAppView:
def showMessage_(self, msg: str): pass
class PyBaseApp(PyGUIObject):
def appName(self) -> str:
return self.model.PROMPT_NAME
def appLongName(self) -> str:
return self.model.NAME
#--- Python --> Cocoa
@dontwrap
def get_default(self, key_name):
return proxy.prefValue_(key_name)
@dontwrap
def set_default(self, key_name, value):
proxy.setPrefValue_value_(key_name, value)
@dontwrap
def show_message(self, msg):
self.callback.showMessage_(msg)

View File

@@ -0,0 +1,38 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import <Python.h>
#import "HSGUIController.h"
#import "PyColumns.h"
/*
This structure is to define constants describing table columns (it's easier to maintain in code
than in XIB files).
*/
typedef struct {
NSString *attrname;
NSUInteger defaultWidth;
NSUInteger minWidth;
NSUInteger maxWidth;
BOOL sortable;
Class cellClass;
} HSColumnDef;
@interface HSColumns : HSGUIController {}
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView;
- (PyColumns *)model;
- (NSTableView *)view;
- (void)connectNotifications;
- (void)disconnectNotifications;
- (void)initializeColumns:(HSColumnDef *)columns;
- (void)initializeColumnsFromModel:(HSColumnDef)columnModel;
- (void)setColumnsAsReadOnly;
- (void)restoreColumns;
- (void)setColumn:(NSString *)colname visible:(BOOL)visible;
@end

View File

@@ -0,0 +1,198 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSColumns.h"
#import "Utils.h"
#import "HSTableView.h" // To prevent warning on stopEditing
@implementation HSColumns
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView
{
self = [super initWithPyRef:aPyRef wrapperClass:[PyColumns class]
callbackClassName:@"ColumnsView" view:aTableView];
[self connectNotifications];
return self;
}
- (void)dealloc
{
[self disconnectNotifications];
[super dealloc];
}
- (PyColumns *)model
{
return (PyColumns *)model;
}
- (NSTableView *)view
{
return (NSTableView *)view;
}
- (void)connectNotifications
{
if ([self view] == nil) {
/* This can happen if there something broken somewhere, and even though when that happens,
it means that something serious is going on, the fact that we connect to all columnMoved:
events messes thigs up even MORE. Don't connect when tableView is nil!
*/
return;
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(columnMoved:)
name:NSTableViewColumnDidMoveNotification object:[self view]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(columnMoved:)
name:NSOutlineViewColumnDidMoveNotification object:[self view]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(columnResized:)
name:NSTableViewColumnDidResizeNotification object:[self view]];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(columnResized:)
name:NSOutlineViewColumnDidResizeNotification object:[self view]];
}
- (void)disconnectNotifications
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
/*
It is assumed, when this method is used, that the table/outline is empty *OR* that it is not
defined in the column list.
Special note about NSOutlineView. You can use HSColumns on outline views, but you be aware that
the "main" column (the one having the tree disclosure buttons) cannot be removed. Therefore,
it has to be defined in the XIB and it must *not* be in column defs.
*/
- (void)initializeColumns:(HSColumnDef *)columns
{
/* We don't want default widths to overwrite stored with in the core code */
[self disconnectNotifications];
/* Translate the title of columns (needed for outlines) present already */
for (NSTableColumn *c in [[self view] tableColumns]) {
NSString *title = NSLocalizedStringFromTable([[c headerCell] stringValue], @"columns", @"");
[[c headerCell] setStringValue:title];
}
NSUserDefaults *udc = [NSUserDefaultsController sharedUserDefaultsController];
HSColumnDef *cdef = columns;
while (cdef->attrname != nil) {
if ([[self view] tableColumnWithIdentifier:cdef->attrname] != nil) {
cdef++;
continue;
}
NSTableColumn *c = [[[NSTableColumn alloc] initWithIdentifier:cdef->attrname] autorelease];
[c setResizingMask:NSTableColumnUserResizingMask];
/* If the column is not added right away, it causes glitches under 10.5 (minwidths instead of default widths) */
[[self view] addTableColumn:c];
NSString *title = [[self model] columnDisplay:cdef->attrname];
[[c headerCell] setStringValue:title];
if (cdef->sortable) {
NSSortDescriptor *d = [[[NSSortDescriptor alloc] initWithKey:cdef->attrname ascending:YES] autorelease];
[c setSortDescriptorPrototype:d];
}
[c setWidth:cdef->defaultWidth];
[[self model] setColumn:cdef->attrname defaultWidth:cdef->defaultWidth];
[c setMinWidth:cdef->minWidth];
NSUInteger maxWidth = cdef->maxWidth;
if (maxWidth == 0) {
maxWidth = 0xffffff;
}
[c setMaxWidth:maxWidth];
if (cdef->cellClass != nil) {
id cell = [[[cdef->cellClass alloc] initTextCell:@""] autorelease];
[cell setEditable:YES];
[c setDataCell:cell];
}
[c bind:@"fontSize" toObject:udc withKeyPath:@"values.TableFontSize" options:nil];
cdef++;
}
[self connectNotifications];
}
/*
Here, instead of having all our column defs, we have one column model, which we use to create
our column defs using column names in [[self model] columnNamesInOrder].
*/
- (void)initializeColumnsFromModel:(HSColumnDef)columnModel
{
NSArray *colnames = [[self model] columnNamesInOrder];
HSColumnDef *defs = (HSColumnDef *)malloc(([colnames count]+1)*sizeof(HSColumnDef));
HSColumnDef *def = defs;
for (NSString *colname in colnames) {
def->attrname = colname;
def->defaultWidth = columnModel.defaultWidth;
def->minWidth = columnModel.minWidth;
def->maxWidth = columnModel.maxWidth;
def->sortable = columnModel.sortable;
def->cellClass = columnModel.cellClass;
def++;
}
def->attrname = nil; // Sentinel
[self initializeColumns:defs];
free(defs);
}
- (void)setColumnsAsReadOnly
{
for (NSTableColumn *col in [[self view] tableColumns]) {
[col setEditable:NO];
}
}
/* Notifications */
- (void)columnMoved:(NSNotification *)notification
{
/* We only get this call after the move. Although there's "NSOldColumn" and "NSNewColumn",
the old index is irrelevant since we have to find the moved column's name.
*/
NSInteger index = n2i([[notification userInfo] objectForKey:@"NSNewColumn"]);
NSTableColumn *c = [[[self view] tableColumns] objectAtIndex:index];
NSString *colName = [c identifier];
[[self model] moveColumn:colName toIndex:index];
}
- (void)columnResized:(NSNotification *)notification
{
NSTableColumn *c = [[notification userInfo] objectForKey:@"NSTableColumn"];
[[self model] resizeColumn:[c identifier] toWidth:[c width]];
}
/* Python --> Cocoa */
- (void)restoreColumns
{
[self disconnectNotifications];
NSArray *columnOrder = [[self model] columnNamesInOrder];
for (NSInteger i=0; i<[columnOrder count]; i++) {
NSString *colName = [columnOrder objectAtIndex:i];
NSInteger index = [[self view] columnWithIdentifier:colName];
if ((index != -1) && (index != i)) {
[[self view] moveColumn:index toColumn:i];
}
}
for (NSTableColumn *c in [[self view] tableColumns]) {
NSInteger width = [[self model] columnWidth:[c identifier]];
if (width > 0) {
[c setWidth:width];
}
BOOL isVisible = [[self model] columnIsVisible:[c identifier]];
[c setHidden:!isVisible];
}
[self connectNotifications];
}
- (void)setColumn:(NSString *)colname visible:(BOOL)visible
{
NSTableColumn *col = [[self view] tableColumnWithIdentifier:colname];
if (col == nil)
return;
if ([col isHidden] == !visible)
return;
if ([[self view] respondsToSelector:@selector(stopEditing)]) {
[(id)[self view] stopEditing];
}
[col setHidden:!visible];
}
@end

View File

@@ -0,0 +1,25 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import "HSGUIController.h"
#import "PySelectableList.h"
@interface HSComboBox : HSGUIController <NSComboBoxDataSource>
{
NSArray *items;
}
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSComboBox *)aView;
- (NSComboBox *)view;
- (void)setView:(NSComboBox *)aComboboxView;
- (PySelectableList *)model;
- (void)comboboxViewSelectionChanged;
- (void)refresh;
- (void)updateSelection;
@end

View File

@@ -0,0 +1,119 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSComboBox.h"
#import "HSPyUtil.h"
@implementation HSComboBox
- (id)initWithPyRef:(PyObject *)aPyRef view:(NSComboBox *)aView
{
PySelectableList *m = [[PySelectableList alloc] initWithModel:aPyRef];
self = [super initWithModel:m];
[m bindCallback:createCallback(@"SelectableListView", self)];
[m release];
[self setView:aView];
return self;
}
- (void)dealloc
{
[[self view] setTarget:nil];
[[self view] setDataSource:nil];
[items release];
[super dealloc];
}
- (NSComboBox *)view
{
return (NSComboBox *)view;
}
- (void)setView:(NSComboBox *)aComboboxView
{
if ([self view] != nil) {
[[self view] setDataSource:nil];
[[self view] setTarget:nil];
}
[super setView:aComboboxView];
if (aComboboxView != nil) {
[aComboboxView setUsesDataSource:YES];
[aComboboxView setDataSource:self];
[aComboboxView setAction:@selector(comboboxViewSelectionChanged)];
[aComboboxView setTarget:self];
/* This is required for the combobox to send its action whenever it's changed. Normally, it's
already set, but then the combobox is created programmatically (xibless), it's not. We
make sure it is here.
*/
[[aComboboxView cell] setSendsActionOnEndEditing:YES];
[self refresh];
}
}
- (PySelectableList *)model
{
return (PySelectableList *)model;
}
- (void)comboboxViewSelectionChanged
{
NSInteger index = [[self view] indexOfSelectedItem];
if (index >= 0) {
[[self model] selectIndex:index];
}
}
/* data source */
- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index
{
if (index < 0) {
return nil;
}
return [items objectAtIndex:index];
}
- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox
{
return [items count];
}
- (NSUInteger)comboBox:(NSComboBox *)aComboBox indexOfItemWithStringValue:(NSString *)aString
{
NSInteger index = [[self model] searchByPrefix:aString];
if (index >= 0) {
return index;
}
else {
return NSNotFound;
}
}
- (NSString *)comboBox:(NSComboBox *)aComboBox completedString:(NSString *)uncompletedString
{
NSInteger index = [[self model] searchByPrefix:uncompletedString];
if (index >= 0) {
return [items objectAtIndex:index];
}
else {
return nil;
}
}
/* model --> view */
- (void)refresh
{
[items release];
items = [[[self model] items] retain];
[[self view] reloadData];
[self updateSelection];
}
- (void)updateSelection
{
[[self view] selectItemAtIndex:[[self model] selectedIndex]];
}
@end

View File

@@ -0,0 +1,23 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import "PyGUIObject.h"
@interface HSGUIController : NSObject
{
PyGUIObject *model;
NSView *view;
}
- (id)initWithModel:(PyGUIObject *)aPy;
- (id)initWithModel:(PyGUIObject *)aPy view:(NSView *)aView;
- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(NSView *)aView;
- (PyGUIObject *)model;
- (NSView *)view;
- (void)setView:(NSView *)aView;
@end

View File

@@ -0,0 +1,62 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSGUIController.h"
#import "HSPyUtil.h"
@implementation HSGUIController
- (id)initWithModel:(PyGUIObject *)aModel
{
self = [super init];
model = [aModel retain];
view = nil;
return self;
}
- (id)initWithModel:(PyGUIObject *)aModel view:(NSView *)aView
{
self = [super init];
model = [aModel retain];
[self setView:aView];
return self;
}
- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(NSView *)aView
{
PyGUIObject *m = [[aWrapperClass alloc] initWithModel:aPyRef];
self = [self initWithModel:m view:aView];
[m bindCallback:createCallback(aCallbackClassName, self)];
[m release];
return self;
}
- (void)dealloc
{
// NSLog([NSString stringWithFormat:@"%@ dealloc",[[self class] description]]);
[self setView:nil];
[model free];
[model release];
[super dealloc];
}
- (PyGUIObject *)model
{
return model;
}
- (NSView *)view
{
return view;
}
- (void)setView:(NSView *)aView
{
[view release];
view = [aView retain];
}
@end

View File

@@ -0,0 +1,44 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import "HSGUIController.h"
#import "HSOutlineView.h"
#import "PyOutline.h"
#import "NSIndexPathAdditions.h"
@interface HSOutline : HSGUIController <HSOutlineViewDelegate, NSOutlineViewDataSource> {
NSMutableDictionary *itemData;
NSMutableSet *itemRetainer;
}
- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(HSOutlineView *)aView;
- (PyOutline *)model;
- (HSOutlineView *)view;
/* Public */
- (void)refresh;
- (NSIndexPath *)selectedIndexPath;
- (NSArray *)selectedIndexPaths;
- (NSString *)dataForCopyToPasteboard;
- (void)startEditing;
- (void)stopEditing;
- (void)updateSelection;
- (void)expandItem:(NSIndexPath *)item;
- (NSIndexPath *)internalizedPath:(NSIndexPath *)path;
/* Caching */
- (id)property:(NSString *)property valueAtPath:(NSIndexPath *)path;
- (void)setProperty:(NSString *)property value:(id)value atPath:(NSIndexPath *)path;
- (NSString *)stringProperty:(NSString *)property valueAtPath:(NSIndexPath *)path;
- (void)setStringProperty:(NSString *)property value:(NSString *)value atPath:(NSIndexPath *)path;
- (BOOL)boolProperty:(NSString *)property valueAtPath:(NSIndexPath *)path;
- (void)setBoolProperty:(NSString *)property value:(BOOL)value atPath:(NSIndexPath *)path;
- (NSInteger)intProperty:(NSString *)property valueAtPath:(NSIndexPath *)path;
- (void)setIntProperty:(NSString *)property value:(int)value atPath:(NSIndexPath *)path;
- (void)refreshItemAtPath:(NSIndexPath *)path;
@end

View File

@@ -0,0 +1,286 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSOutline.h"
#import "Utils.h"
#define CHILDREN_COUNT_PROPERTY @"children_count"
@implementation HSOutline
- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(HSOutlineView *)aView
{
self = [super initWithPyRef:aPyRef wrapperClass:aWrapperClass callbackClassName:aCallbackClassName view:aView];
itemData = [[NSMutableDictionary dictionary] retain];
/* Dictionaries don't retain its keys because it copies them. Our items are NSIndexPath and when
an index path has the same value, it's the same instance. Before OS X 10.7, all these instances
stayed in memory, so we didn't need to worry about retaining them. Hoever, it seems now that
index path instances are sometimes released. Oops. So, we now need to retain our index path
instances and that's why we use itemRetainer.
In fact, it seems that unlike what the doc says, it's not true that two index paths with the
same value will always be the same instance.
*/
itemRetainer = [[NSMutableSet set] retain];
if (([[self view] outlineTableColumn] == nil) && ([[[self view] tableColumns] count] > 0)) {
[[self view] setOutlineTableColumn:[[[self view] tableColumns] objectAtIndex:0]];
}
return self;
}
- (void)dealloc
{
[itemData release];
[itemRetainer release];
[super dealloc];
}
- (HSOutlineView *)view
{
return (HSOutlineView *)view;
}
- (void)setView:(HSOutlineView *)aOutlineView
{
if ([self view] != nil) {
[[self view] setDataSource:nil];
[[self view] setDelegate:nil];
}
[super setView:aOutlineView];
if (aOutlineView != nil) {
[aOutlineView setDataSource:self];
[aOutlineView setDelegate:self];
}
}
- (PyOutline *)model
{
return (PyOutline *)model;
}
/* Private */
- (void)setPySelection
{
NSMutableArray *paths = [NSMutableArray array];
NSIndexSet *indexes = [[self view] selectedRowIndexes];
NSInteger i = [indexes firstIndex];
while (i != NSNotFound) {
NSIndexPath *path = [[self view] itemAtRow:i];
[paths addObject:p2a(path)];
i = [indexes indexGreaterThanIndex:i];
}
[[self model] setSelectedPaths:paths];
}
- (NSIndexPath *)internalizedPath:(NSIndexPath *)path
{
/* Because NSIndexPath stopped guaranteeing that the same paths always were represented by the
same instances, we have to make sure, when we manipulate paths, that we manipulate the same
instances as those that were given by outlineView:child:ofItem:
*/
NSIndexPath *result = [itemRetainer member:path];
if (result == nil) {
result = path;
[itemData setObject:[NSMutableDictionary dictionary] forKey:result];
[itemRetainer addObject:result];
}
return result;
}
/* Public */
- (void)refresh
{
[itemData removeAllObjects];
// We can't get rid of our instances just yet, we have to wait until after reloadData
NSSet *oldRetainer = itemRetainer;
itemRetainer = [[NSMutableSet set] retain];
[[self view] setDelegate:nil];
[[self view] reloadData];
[[self view] setDelegate:self];
/* Item retainer and releasing
In theory, [oldRetainer release] should work, but in practice, doing so causes occasional
crashes during drag & drop, which I guess keep the reference of an item a bit longer than it
should. This is why we autorelease here. See #354.
*/
[oldRetainer autorelease];
[self updateSelection];
}
- (NSArray *)selectedIndexPaths
{
NSArray *arrayPaths = [[self model] selectedPaths];
NSMutableArray *result = [NSMutableArray array];
for (NSArray *arrayPath in arrayPaths) {
[result addObject:[self internalizedPath:a2p(arrayPath)]];
}
return result;
}
- (void)startEditing
{
[[self view] startEditing];
}
- (void)stopEditing
{
[[self view] stopEditing];
}
- (void)updateSelection
{
[[self view] updateSelection];
}
- (void)expandItem:(NSIndexPath *)item
{
[[self view] ensureExpanded:item];
}
/* Caching */
- (id)property:(NSString *)property valueAtPath:(NSIndexPath *)path
{
NSMutableDictionary *props = [itemData objectForKey:path];
id value = [props objectForKey:property];
if (value == nil) {
value = [[self model] property:property valueAtPath:p2a(path)];
if (value == nil) {
value = [NSNull null];
}
[props setObject:value forKey:property];
}
if (value == [NSNull null]) {
value = nil;
}
return value;
}
- (void)setProperty:(NSString *)property value:(id)value atPath:(NSIndexPath *)path
{
NSMutableDictionary *props = [itemData objectForKey:path];
[props removeObjectForKey:property];
[[self model] setProperty:property value:value atPath:p2a(path)];
}
- (NSString *)stringProperty:(NSString *)property valueAtPath:(NSIndexPath *)path
{
return [self property:property valueAtPath:path];
}
- (void)setStringProperty:(NSString *)property value:(NSString *)value atPath:(NSIndexPath *)path
{
[self setProperty:property value:value atPath:path];
}
- (BOOL)boolProperty:(NSString *)property valueAtPath:(NSIndexPath *)path
{
NSNumber *value = [self property:property valueAtPath:path];
return [value boolValue];
}
- (void)setBoolProperty:(NSString *)property value:(BOOL)value atPath:(NSIndexPath *)path
{
[self setProperty:property value:[NSNumber numberWithBool:value] atPath:path];
}
- (NSInteger)intProperty:(NSString *)property valueAtPath:(NSIndexPath *)path
{
NSNumber *value = [self property:property valueAtPath:path];
return [value intValue];
}
- (void)setIntProperty:(NSString *)property value:(int)value atPath:(NSIndexPath *)path
{
[self setProperty:property value:[NSNumber numberWithInt:value] atPath:path];
}
- (void)refreshItemAtPath:(NSIndexPath *)path
{
NSMutableDictionary *props = [itemData objectForKey:path];
[props removeAllObjects];
}
/* NSOutlineView data source */
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
return [self intProperty:CHILDREN_COUNT_PROPERTY valueAtPath:(NSIndexPath *)item];
}
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item
{
NSIndexPath *parent = item;
NSIndexPath *path = parent == nil ? [NSIndexPath indexPathWithIndex:index] : [parent indexPathByAddingIndex:index];
return [self internalizedPath:path];
}
- (BOOL)outlineView:(NSOutlineView *)theOutlineView isItemExpandable:(id)item
{
return [self outlineView:[self view] numberOfChildrenOfItem:item] > 0;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)column item:(id)item
{
return [[self model] canEditProperty:[column identifier] atPath:p2a((NSIndexPath *)item)];
}
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)column byItem:(id)item
{
return [self property:[column identifier] valueAtPath:(NSIndexPath *)item];
}
- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)value forTableColumn:(NSTableColumn *)column byItem:(id)item
{
[self setProperty:[column identifier] value:value atPath:(NSIndexPath *)item];
}
/* We need to change the model selection at both IsChanging and DidChange. We need to set the
model selection at IsChanging before of the arrow clicking. The action launched by this little arrow
is performed before DidChange. However, when using the arrow to change the selection, IsChanging is
never called
*/
- (void)outlineViewSelectionIsChanging:(NSNotification *)notification
{
[self setPySelection];
}
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
[self setPySelection];
}
/* HSOutlineView delegate */
- (NSIndexPath *)selectedIndexPath
{
NSArray *paths = [self selectedIndexPaths];
if ([paths count] > 0) {
return [paths objectAtIndex:0];
}
else {
return nil;
}
}
- (NSString *)dataForCopyToPasteboard
{
return nil;
}
- (void)outlineViewDidEndEditing:(HSOutlineView *)outlineView
{
[[self model] saveEdits];
}
- (void)outlineViewWasDoubleClicked:(HSOutlineView *)outlineView
{
}
- (void)outlineViewCancelsEdition:(HSOutlineView *)outlineView
{
[[self model] cancelEdits];
}
@end

View File

@@ -0,0 +1,23 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import <Cocoa/Cocoa.h>
#import <Python.h>
#import "HSGUIController.h"
#import "PySelectableList.h"
@interface HSPopUpList : HSGUIController {}
- (id)initWithPyRef:(PyObject *)aPyRef popupView:(NSPopUpButton *)aPopupView;
- (NSPopUpButton *)view;
- (void)setView:(NSPopUpButton *)aPopupView;
- (PySelectableList *)model;
- (void)popupViewSelectionChanged;
- (void)refresh;
- (void)updateSelection;
@end

View File

@@ -0,0 +1,60 @@
/*
Copyright 2013 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "BSD" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.hardcoded.net/licenses/bsd_license
*/
#import "HSPopUpList.h"
#import "Utils.h"
@implementation HSPopUpList
- (id)initWithPyRef:(PyObject *)aPyRef popupView:(NSPopUpButton *)aPopupView
{
self = [super initWithPyRef:aPyRef wrapperClass:[PySelectableList class]
callbackClassName:@"SelectableListView" view:aPopupView];
return self;
}
- (NSPopUpButton *)view
{
return (NSPopUpButton *)view;
}
- (void)setView:(NSPopUpButton *)aPopupView
{
if ([self view] != nil) {
[[self view] setTarget:nil];
}
[super setView:aPopupView];
if (aPopupView != nil) {
[aPopupView setAction:@selector(popupViewSelectionChanged)];
[aPopupView setTarget:self];
[self refresh];
}
}
- (PySelectableList *)model
{
return (PySelectableList *)model;
}
- (void)popupViewSelectionChanged
{
[[self model] selectIndex:[[self view] indexOfSelectedItem]];
}
/* model --> view */
- (void)refresh
{
[[self view] removeAllItems];
[[self view] addItemsWithTitles:[[self model] items]];
[self updateSelection];
}
- (void)updateSelection
{
[[self view] selectItemAtIndex:[[self model] selectedIndex]];
}
@end

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