@@ -0,0 +1,15 @@ | |||
.DS_Store | |||
__pycache__ | |||
*.so | |||
*.mo | |||
*.waf* | |||
.lock-waf* | |||
/build | |||
/cocoa/build | |||
/env | |||
/cocoa/autogen | |||
/locale | |||
/run.py | |||
/cocoa/*/Info.plist | |||
/cocoa/*/build |
@@ -0,0 +1,9 @@ | |||
[submodule "dupeguru"] | |||
path = dupeguru | |||
url = https://github.com/hsoft/dupeguru.git | |||
[submodule "hscommon"] | |||
path = hscommon | |||
url = https://github.com/hsoft/hscommon.git | |||
[submodule "cocoalib"] | |||
path = cocoalib | |||
url = https://github.com/hsoft/cocoalib.git |
@@ -0,0 +1,37 @@ | |||
PYTHON ?= python3 | |||
REQ_MINOR_VERSION = 4 | |||
all : | env build | |||
@echo "Build complete! You can run dupeGuru with 'make run'" | |||
# If you're installing into a path that is not going to be the final path prefix (such as a | |||
# sandbox), set DESTDIR to that path. | |||
# Our build scripts are not very "make like" yet and perform their task in a bundle. For now, we | |||
# use one of each file to act as a representative, a target, of these groups. | |||
submodules_target = hscommon/__init__.py | |||
reqs : | |||
@ret=`${PYTHON} -c "import sys; print(int(sys.version_info[:2] >= (3, ${REQ_MINOR_VERSION})))"`; \ | |||
if [ $${ret} -ne 1 ]; then \ | |||
echo "Python 3.${REQ_MINOR_VERSION}+ required. Aborting."; \ | |||
exit 1; \ | |||
fi | |||
@${PYTHON} -m venv -h > /dev/null || \ | |||
echo "Creation of our virtualenv failed. Something's wrong with your python install." | |||
# Ensure that submodules are initialized | |||
$(submodules_target) : | |||
git submodule init | |||
git submodule update | |||
cd dupeguru; ln -sf ../hscommon .; ln -sf ../cocoalib . | |||
env : | $(submodules_target) reqs | |||
@echo "Creating our virtualenv" | |||
${PYTHON} -m venv env | |||
./env/bin/python -m pip install -r requirements.txt | |||
build: | |||
./env/bin/python build.py | |||
.PHONY : reqs build all |
@@ -0,0 +1,42 @@ | |||
# dupeguru-cocoa | |||
This is the Cocoa UI for [dupeGuru][dupeguru]. This code was previously directly in the main repo, | |||
but since I'm not planning on supporting MacOS myself any longer, I'm splitting it out. | |||
Also, to make the job easier on a would-be maintainer for the Cocoa UI of dupeGuru, I'm planning | |||
on restoring the XCode/XIB version of the UI from the grave. | |||
### OS X maintainer wanted | |||
My Mac Mini is already a couple of years old and is likely to be my last Apple purchase. When it | |||
dies, I will be unable maintain the OS X version of moneyGuru. I've already stopped paying for the | |||
Mac Developer membership so I can't sign the apps anymore (in the "official way" I mean. The | |||
download is still PGP signed) If you're a Mac developer and are interested in taking this task, | |||
[don't hesitate to let me know][contrib-issue]. | |||
## How to build dupeGuru from source | |||
### Prerequisites | |||
* Python 3.4+ compiled in "framework mode". | |||
* MacOS 10.10+ with XCode command line tools. | |||
### make | |||
You can build the app with `make`: | |||
$ make | |||
$ make run | |||
### pyenv | |||
[pyenv][pyenv] is a popular way to manage multiple python versions. However, be aware that dupeGuru | |||
will not compile with a pyenv's python unless it's been built with `--enable-framework`. You can do | |||
this with: | |||
$ env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install 3.4.3 | |||
[dupeguru]: https://github.com/hsoft/dupeguru | |||
[contrib-issue]: https://github.com/hsoft/dupeguru/issues/300 | |||
[pyenv]: https://github.com/yyuu/pyenv |
@@ -0,0 +1,319 @@ | |||
# Copyright 2017 Virgil Dupras | |||
# | |||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
# which should be included with this package. The terms are also available at | |||
# http://www.gnu.org/licenses/gpl-3.0.html | |||
import sys | |||
sys.path.append('dupeguru') | |||
import os | |||
import os.path as op | |||
from optparse import OptionParser | |||
import shutil | |||
import compileall | |||
from setuptools import setup, Extension | |||
from hscommon import sphinxgen | |||
from hscommon.build import ( | |||
add_to_pythonpath, print_and_do, copy_packages, filereplace, | |||
get_module_version, move_all, copy_all, OSXAppStructure, | |||
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib, | |||
collect_stdlib_dependencies | |||
) | |||
from hscommon import loc | |||
from hscommon.plat import ISOSX | |||
from hscommon.util import ensure_folder, delete_files_with_pattern | |||
def parse_args(): | |||
usage = "usage: %prog [options]" | |||
parser = OptionParser(usage=usage) | |||
parser.add_option( | |||
'--clean', action='store_true', dest='clean', | |||
help="Clean build folder before building" | |||
) | |||
parser.add_option( | |||
'--doc', action='store_true', dest='doc', | |||
help="Build only the help file" | |||
) | |||
parser.add_option( | |||
'--dev', action='store_true', dest='dev', default=False, | |||
help="If this flag is set, will configure for dev builds." | |||
) | |||
parser.add_option( | |||
'--loc', action='store_true', dest='loc', | |||
help="Build only localization" | |||
) | |||
parser.add_option( | |||
'--cocoa-ext', action='store_true', dest='cocoa_ext', | |||
help="Build only Cocoa extensions" | |||
) | |||
parser.add_option( | |||
'--cocoa-compile', action='store_true', dest='cocoa_compile', | |||
help="Build only Cocoa executable" | |||
) | |||
parser.add_option( | |||
'--xibless', action='store_true', dest='xibless', | |||
help="Build only xibless UIs" | |||
) | |||
parser.add_option( | |||
'--updatepot', action='store_true', dest='updatepot', | |||
help="Generate .pot files from source code." | |||
) | |||
parser.add_option( | |||
'--mergepot', action='store_true', dest='mergepot', | |||
help="Update all .po files based on .pot files." | |||
) | |||
parser.add_option( | |||
'--normpo', action='store_true', dest='normpo', | |||
help="Normalize all PO files (do this before commit)." | |||
) | |||
(options, args) = parser.parse_args() | |||
return options | |||
def cocoa_app(): | |||
app_path = 'build/dupeGuru.app' | |||
return OSXAppStructure(app_path) | |||
def build_xibless(dest='cocoa/autogen'): | |||
import xibless | |||
ensure_folder(dest) | |||
FNPAIRS = [ | |||
('ignore_list_dialog.py', 'IgnoreListDialog_UI'), | |||
('deletion_options.py', 'DeletionOptions_UI'), | |||
('problem_dialog.py', 'ProblemDialog_UI'), | |||
('directory_panel.py', 'DirectoryPanel_UI'), | |||
('prioritize_dialog.py', 'PrioritizeDialog_UI'), | |||
('result_window.py', 'ResultWindow_UI'), | |||
('main_menu.py', 'MainMenu_UI'), | |||
('details_panel.py', 'DetailsPanel_UI'), | |||
('details_panel_picture.py', 'DetailsPanelPicture_UI'), | |||
] | |||
for srcname, dstname in FNPAIRS: | |||
xibless.generate( | |||
op.join('cocoa', 'ui', srcname), op.join(dest, dstname), | |||
localizationTable='Localizable' | |||
) | |||
for appmode in ('standard', 'music', 'picture'): | |||
xibless.generate( | |||
op.join('cocoa', 'ui', 'preferences_panel.py'), | |||
op.join(dest, 'PreferencesPanel%s_UI' % appmode.capitalize()), | |||
localizationTable='Localizable', | |||
args={'appmode': appmode}, | |||
) | |||
def build_cocoa(dev): | |||
print("Creating OS X app structure") | |||
app = cocoa_app() | |||
app_version = get_module_version('core') | |||
cocoa_project_path = 'cocoa' | |||
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version) | |||
app.create(op.join('build', 'Info.plist')) | |||
print("Building localizations") | |||
build_localizations() | |||
print("Building xibless UIs") | |||
build_cocoalib_xibless() | |||
build_xibless() | |||
print("Building Python extensions") | |||
build_cocoa_proxy_module() | |||
build_cocoa_bridging_interfaces() | |||
print("Building the cocoa layer") | |||
copy_embeddable_python_dylib('build') | |||
pydep_folder = op.join(app.resources, 'py') | |||
if not op.exists(pydep_folder): | |||
os.mkdir(pydep_folder) | |||
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build') | |||
tocopy = [ | |||
'dupeguru/core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'objp', 'send2trash', 'hsaudiotag', | |||
] | |||
copy_packages(tocopy, pydep_folder, create_links=dev) | |||
sys.path.insert(0, 'build') | |||
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have | |||
# to manually specify it. | |||
extra_deps = ['multiprocessing'] | |||
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder, extra_deps=extra_deps) | |||
del sys.path[0] | |||
# Views are not referenced by python code, so they're not found by the collector. | |||
copy_all('build/inter/*.so', op.join(pydep_folder, 'inter')) | |||
if not dev: | |||
# Important: Don't ever run delete_files_with_pattern('*.py') on dev builds because you'll | |||
# be deleting all py files in symlinked folders. | |||
compileall.compile_dir(pydep_folder, force=True, legacy=True) | |||
delete_files_with_pattern(pydep_folder, '*.py') | |||
delete_files_with_pattern(pydep_folder, '__pycache__') | |||
print("Compiling with WAF") | |||
os.chdir('cocoa') | |||
print_and_do('{0} waf configure && {0} waf'.format(sys.executable)) | |||
os.chdir('..') | |||
app.copy_executable('cocoa/build/dupeGuru') | |||
build_help() | |||
print("Copying resources and frameworks") | |||
image_path = 'cocoa/dupeguru.icns' | |||
resources = [image_path, 'cocoa/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help'] | |||
app.copy_resources(*resources, use_symlinks=dev) | |||
app.copy_frameworks('build/Python') | |||
print("Creating the run.py file") | |||
tmpl = open('cocoa/run_template.py', 'rt').read() | |||
run_contents = tmpl.replace('{{app_path}}', app.dest) | |||
open('run.py', 'wt').write(run_contents) | |||
def build_help(): | |||
print("Generating Help") | |||
current_path = op.abspath('dupeguru') | |||
help_basepath = op.join(current_path, 'help', 'en') | |||
help_destpath = op.join(current_path, '..', 'build', 'help') | |||
changelog_path = op.join(current_path, 'help', 'changelog') | |||
tixurl = "https://github.com/hsoft/dupeguru/issues/{}" | |||
confrepl = {'language': 'en'} | |||
changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl') | |||
conftmpl = op.join(current_path, 'help', 'conf.tmpl') | |||
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl) | |||
def build_localizations(): | |||
if not op.exists('locale'): | |||
os.symlink('dupeguru/locale', 'locale') | |||
loc.compile_all_po('locale') | |||
app = cocoa_app() | |||
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'en.lproj', 'Localizable.strings')) | |||
locale_dest = op.join(app.resources, 'locale') | |||
if op.exists(locale_dest): | |||
shutil.rmtree(locale_dest) | |||
shutil.copytree('locale', locale_dest, ignore=shutil.ignore_patterns('*.po', '*.pot')) | |||
def build_updatepot(): | |||
print("Updating Cocoa strings file.") | |||
build_cocoalib_xibless('cocoalib/autogen') | |||
loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj') | |||
build_xibless() | |||
loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/en.lproj') | |||
def build_mergepot(): | |||
print("Updating .po files using .pot files") | |||
loc.merge_pots_into_pos(op.join('cocoalib', 'locale')) | |||
def build_normpo(): | |||
loc.normalize_all_pos(op.join('cocoalib', 'locale')) | |||
def build_cocoa_proxy_module(): | |||
print("Building Cocoa Proxy") | |||
import objp.p2o | |||
objp.p2o.generate_python_proxy_code('cocoalib/cocoa/CocoaProxy.h', 'build/CocoaProxy.m') | |||
build_cocoa_ext( | |||
"CocoaProxy", 'cocoalib/cocoa', | |||
[ | |||
'cocoalib/cocoa/CocoaProxy.m', 'build/CocoaProxy.m', 'build/ObjP.m', | |||
'cocoalib/HSErrorReportWindow.m', 'cocoa/autogen/HSErrorReportWindow_UI.m' | |||
], | |||
['AppKit', 'CoreServices'], | |||
['cocoalib', 'cocoa/autogen'] | |||
) | |||
def build_cocoa_bridging_interfaces(): | |||
print("Building Cocoa Bridging Interfaces") | |||
import objp.o2p | |||
import objp.p2o | |||
add_to_pythonpath('cocoa') | |||
add_to_pythonpath('cocoalib') | |||
from cocoa.inter import ( | |||
PyGUIObject, GUIObjectView, PyColumns, ColumnsView, PyOutline, | |||
OutlineView, PySelectableList, SelectableListView, PyTable, TableView, PyBaseApp, | |||
PyTextField, ProgressWindowView, PyProgressWindow | |||
) | |||
from inter.deletion_options import PyDeletionOptions, DeletionOptionsView | |||
from inter.details_panel import PyDetailsPanel, DetailsPanelView | |||
from inter.directory_outline import PyDirectoryOutline, DirectoryOutlineView | |||
from inter.prioritize_dialog import PyPrioritizeDialog, PrioritizeDialogView | |||
from inter.prioritize_list import PyPrioritizeList, PrioritizeListView | |||
from inter.problem_dialog import PyProblemDialog | |||
from inter.ignore_list_dialog import PyIgnoreListDialog, IgnoreListDialogView | |||
from inter.result_table import PyResultTable, ResultTableView | |||
from inter.stats_label import PyStatsLabel, StatsLabelView | |||
from inter.app import PyDupeGuru, DupeGuruView | |||
allclasses = [ | |||
PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp, | |||
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog, | |||
PyIgnoreListDialog, PyDeletionOptions, PyResultTable, PyStatsLabel, PyDupeGuru, | |||
PyTextField, PyProgressWindow | |||
] | |||
for class_ in allclasses: | |||
objp.o2p.generate_objc_code(class_, 'cocoa/autogen', inherit=True) | |||
allclasses = [ | |||
GUIObjectView, ColumnsView, OutlineView, SelectableListView, TableView, | |||
DetailsPanelView, DirectoryOutlineView, PrioritizeDialogView, PrioritizeListView, | |||
IgnoreListDialogView, DeletionOptionsView, ResultTableView, StatsLabelView, | |||
ProgressWindowView, DupeGuruView | |||
] | |||
clsspecs = [objp.o2p.spec_from_python_class(class_) for class_ in allclasses] | |||
objp.p2o.generate_python_proxy_code_from_clsspec(clsspecs, 'build/CocoaViews.m') | |||
build_cocoa_ext('CocoaViews', 'cocoa/inter', ['build/CocoaViews.m', 'build/ObjP.m']) | |||
def build_pe_modules(): | |||
print("Building PE Modules") | |||
exts = [ | |||
Extension( | |||
"_block", | |||
[op.join('dupeguru', 'core', 'pe', 'modules', 'block.c'), op.join('dupeguru', 'core', 'pe', 'modules', 'common.c')] | |||
), | |||
Extension( | |||
"_cache", | |||
[op.join('dupeguru', 'core', 'pe', 'modules', 'cache.c'), op.join('dupeguru', 'core', 'pe', 'modules', 'common.c')] | |||
), | |||
] | |||
exts.append(Extension( | |||
"_block_osx", | |||
[op.join('dupeguru', 'core', 'pe', 'modules', 'block_osx.m'), op.join('dupeguru', 'core', 'pe', 'modules', 'common.c')], | |||
extra_link_args=[ | |||
"-framework", "CoreFoundation", | |||
"-framework", "Foundation", | |||
"-framework", "ApplicationServices", | |||
] | |||
)) | |||
setup( | |||
script_args=['build_ext', '--inplace'], | |||
ext_modules=exts, | |||
) | |||
move_all('_block*', op.join('dupeguru', 'core', 'pe')) | |||
move_all('_cache*', op.join('dupeguru', 'core', 'pe')) | |||
def build_normal(dev): | |||
print("Building dupeGuru with UI cocoa") | |||
add_to_pythonpath('.') | |||
build_pe_modules() | |||
build_cocoa(dev) | |||
def main(): | |||
options = parse_args() | |||
if options.dev: | |||
print("Building in Dev mode") | |||
if options.clean: | |||
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]: | |||
if op.exists(path): | |||
shutil.rmtree(path) | |||
if not op.exists('build'): | |||
os.mkdir('build') | |||
if options.doc: | |||
build_help() | |||
elif options.loc: | |||
build_localizations() | |||
elif options.updatepot: | |||
build_updatepot() | |||
elif options.mergepot: | |||
build_mergepot() | |||
elif options.normpo: | |||
build_normpo() | |||
elif options.cocoa_ext: | |||
build_cocoa_proxy_module() | |||
build_cocoa_bridging_interfaces() | |||
elif options.cocoa_compile: | |||
os.chdir('cocoa') | |||
print_and_do('{0} waf configure && {0} waf'.format(sys.executable)) | |||
os.chdir('..') | |||
cocoa_app().copy_executable('cocoa/build/dupeGuru') | |||
elif options.xibless: | |||
build_cocoalib_xibless() | |||
build_xibless() | |||
else: | |||
build_normal(options.dev) | |||
if __name__ == '__main__': | |||
main() |
@@ -0,0 +1,79 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import "PyDupeGuru.h" | |||
#import "ResultWindow.h" | |||
#import "ResultTable.h" | |||
#import "DetailsPanel.h" | |||
#import "DirectoryPanel.h" | |||
#import "IgnoreListDialog.h" | |||
#import "ProblemDialog.h" | |||
#import "DeletionOptions.h" | |||
#import "HSAboutBox.h" | |||
#import "HSRecentFiles.h" | |||
#import "HSProgressWindow.h" | |||
@interface AppDelegate : NSObject <NSFileManagerDelegate> | |||
{ | |||
NSMenu *recentResultsMenu; | |||
NSMenu *columnsMenu; | |||
PyDupeGuru *model; | |||
ResultWindow *_resultWindow; | |||
DirectoryPanel *_directoryPanel; | |||
DetailsPanel *_detailsPanel; | |||
IgnoreListDialog *_ignoreListDialog; | |||
ProblemDialog *_problemDialog; | |||
DeletionOptions *_deletionOptions; | |||
HSProgressWindow *_progressWindow; | |||
NSWindowController *_preferencesPanel; | |||
HSAboutBox *_aboutBox; | |||
HSRecentFiles *_recentResults; | |||
} | |||
@property (readwrite, retain) NSMenu *recentResultsMenu; | |||
@property (readwrite, retain) NSMenu *columnsMenu; | |||
/* Virtual */ | |||
+ (NSDictionary *)defaultPreferences; | |||
- (PyDupeGuru *)model; | |||
- (DetailsPanel *)createDetailsPanel; | |||
- (void)setScanOptions; | |||
/* Public */ | |||
- (void)finalizeInit; | |||
- (ResultWindow *)resultWindow; | |||
- (DirectoryPanel *)directoryPanel; | |||
- (DetailsPanel *)detailsPanel; | |||
- (HSRecentFiles *)recentResults; | |||
- (NSInteger)getAppMode; | |||
- (void)setAppMode:(NSInteger)appMode; | |||
/* Delegate */ | |||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification; | |||
- (void)applicationWillBecomeActive:(NSNotification *)aNotification; | |||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; | |||
- (void)applicationWillTerminate:(NSNotification *)aNotification; | |||
- (void)recentFileClicked:(NSString *)path; | |||
/* Actions */ | |||
- (void)clearPictureCache; | |||
- (void)loadResults; | |||
- (void)openWebsite; | |||
- (void)openHelp; | |||
- (void)showAboutBox; | |||
- (void)showDirectoryWindow; | |||
- (void)showPreferencesPanel; | |||
- (void)showResultWindow; | |||
- (void)showIgnoreList; | |||
- (void)startScanning; | |||
/* model --> view */ | |||
- (void)showMessage:(NSString *)msg; | |||
@end |
@@ -0,0 +1,394 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "AppDelegate.h" | |||
#import "ProgressController.h" | |||
#import "HSPyUtil.h" | |||
#import "Consts.h" | |||
#import "Dialogs.h" | |||
#import "Utils.h" | |||
#import "ValueTransformers.h" | |||
#import "DetailsPanelPicture.h" | |||
#import "PreferencesPanelStandard_UI.h" | |||
#import "PreferencesPanelMusic_UI.h" | |||
#import "PreferencesPanelPicture_UI.h" | |||
@implementation AppDelegate | |||
@synthesize recentResultsMenu; | |||
@synthesize columnsMenu; | |||
+ (NSDictionary *)defaultPreferences | |||
{ | |||
NSMutableDictionary *d = [NSMutableDictionary dictionary]; | |||
[d setObject:i2n(1) forKey:@"scanTypeStandard"]; | |||
[d setObject:i2n(3) forKey:@"scanTypeMusic"]; | |||
[d setObject:i2n(0) forKey:@"scanTypePicture"]; | |||
[d setObject:i2n(95) forKey:@"minMatchPercentage"]; | |||
[d setObject:i2n(30) forKey:@"smallFileThreshold"]; | |||
[d setObject:b2n(YES) forKey:@"wordWeighting"]; | |||
[d setObject:b2n(NO) forKey:@"matchSimilarWords"]; | |||
[d setObject:b2n(YES) forKey:@"ignoreSmallFiles"]; | |||
[d setObject:b2n(NO) forKey:@"scanTagTrack"]; | |||
[d setObject:b2n(YES) forKey:@"scanTagArtist"]; | |||
[d setObject:b2n(YES) forKey:@"scanTagAlbum"]; | |||
[d setObject:b2n(YES) forKey:@"scanTagTitle"]; | |||
[d setObject:b2n(NO) forKey:@"scanTagGenre"]; | |||
[d setObject:b2n(NO) forKey:@"scanTagYear"]; | |||
[d setObject:b2n(NO) forKey:@"matchScaled"]; | |||
[d setObject:i2n(1) forKey:@"recreatePathType"]; | |||
[d setObject:i2n(11) forKey:TableFontSize]; | |||
[d setObject:b2n(YES) forKey:@"mixFileKind"]; | |||
[d setObject:b2n(NO) forKey:@"useRegexpFilter"]; | |||
[d setObject:b2n(NO) forKey:@"ignoreHardlinkMatches"]; | |||
[d setObject:b2n(NO) forKey:@"removeEmptyFolders"]; | |||
[d setObject:b2n(NO) forKey:@"DebugMode"]; | |||
[d setObject:@"" forKey:@"CustomCommand"]; | |||
[d setObject:[NSArray array] forKey:@"recentDirectories"]; | |||
[d setObject:[NSArray array] forKey:@"columnsOrder"]; | |||
[d setObject:[NSDictionary dictionary] forKey:@"columnsWidth"]; | |||
return d; | |||
} | |||
+ (void)initialize | |||
{ | |||
HSVTAdd *vt = [[[HSVTAdd alloc] initWithValue:4] autorelease]; | |||
[NSValueTransformer setValueTransformer:vt forName:@"vtRowHeightOffset"]; | |||
NSDictionary *d = [self defaultPreferences]; | |||
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:d]; | |||
[[NSUserDefaults standardUserDefaults] registerDefaults:d]; | |||
} | |||
- (id)init | |||
{ | |||
self = [super init]; | |||
model = [[PyDupeGuru alloc] init]; | |||
[model bindCallback:createCallback(@"DupeGuruView", self)]; | |||
NSMutableIndexSet *contentsIndexes = [NSMutableIndexSet indexSet]; | |||
[contentsIndexes addIndex:1]; | |||
[contentsIndexes addIndex:2]; | |||
VTIsIntIn *vt = [[[VTIsIntIn alloc] initWithValues:contentsIndexes reverse:YES] autorelease]; | |||
[NSValueTransformer setValueTransformer:vt forName:@"vtScanTypeIsNotContent"]; | |||
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:0]; | |||
VTIsIntIn *vtScanTypeIsFuzzy = [[[VTIsIntIn alloc] initWithValues:i reverse:NO] autorelease]; | |||
[NSValueTransformer setValueTransformer:vtScanTypeIsFuzzy forName:@"vtScanTypeIsFuzzy"]; | |||
i = [NSMutableIndexSet indexSetWithIndex:4]; | |||
VTIsIntIn *vtScanTypeIsNotContent = [[[VTIsIntIn alloc] initWithValues:i reverse:YES] autorelease]; | |||
[NSValueTransformer setValueTransformer:vtScanTypeIsNotContent forName:@"vtScanTypeMusicIsNotContent"]; | |||
VTIsIntIn *vtScanTypeIsTag = [[[VTIsIntIn alloc] initWithValues:[NSIndexSet indexSetWithIndex:3] reverse:NO] autorelease]; | |||
[NSValueTransformer setValueTransformer:vtScanTypeIsTag forName:@"vtScanTypeIsTag"]; | |||
return self; | |||
} | |||
- (void)finalizeInit | |||
{ | |||
// We can only finalize initialization once the main menu has been created, which cannot happen | |||
// before AppDelegate is created. | |||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; | |||
_recentResults = [[HSRecentFiles alloc] initWithName:@"recentResults" menu:recentResultsMenu]; | |||
[_recentResults setDelegate:self]; | |||
_directoryPanel = [[DirectoryPanel alloc] initWithParentApp:self]; | |||
_ignoreListDialog = [[IgnoreListDialog alloc] initWithPyRef:[model ignoreListDialog]]; | |||
_problemDialog = [[ProblemDialog alloc] initWithPyRef:[model problemDialog]]; | |||
_deletionOptions = [[DeletionOptions alloc] initWithPyRef:[model deletionOptions]]; | |||
_progressWindow = [[HSProgressWindow alloc] initWithPyRef:[[self model] progressWindow] view:nil]; | |||
[_progressWindow setParentWindow:[_directoryPanel window]]; | |||
// Lazily loaded | |||
_aboutBox = nil; | |||
_preferencesPanel = nil; | |||
_resultWindow = nil; | |||
_detailsPanel = nil; | |||
[[[self directoryPanel] window] makeKeyAndOrderFront:self]; | |||
} | |||
/* Virtual */ | |||
- (PyDupeGuru *)model | |||
{ | |||
return model; | |||
} | |||
- (DetailsPanel *)createDetailsPanel | |||
{ | |||
NSInteger appMode = [self getAppMode]; | |||
if (appMode == AppModePicture) { | |||
return [[DetailsPanelPicture alloc] initWithApp:model]; | |||
} | |||
else { | |||
return [[DetailsPanel alloc] initWithPyRef:[model detailsPanel]]; | |||
} | |||
} | |||
- (void)setScanOptions | |||
{ | |||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; | |||
NSString *scanTypeOptionName; | |||
NSInteger appMode = [self getAppMode]; | |||
if (appMode == AppModePicture) { | |||
scanTypeOptionName = @"scanTypePicture"; | |||
} | |||
else if (appMode == AppModeMusic) { | |||
scanTypeOptionName = @"scanTypeMusic"; | |||
} | |||
else { | |||
scanTypeOptionName = @"scanTypeStandard"; | |||
} | |||
[model setScanType:n2i([ud objectForKey:scanTypeOptionName])]; | |||
[model setMinMatchPercentage:n2i([ud objectForKey:@"minMatchPercentage"])]; | |||
[model setWordWeighting:n2b([ud objectForKey:@"wordWeighting"])]; | |||
[model setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])]; | |||
[model setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])]; | |||
[model setMatchSimilarWords:n2b([ud objectForKey:@"matchSimilarWords"])]; | |||
int smallFileThreshold = [ud integerForKey:@"smallFileThreshold"]; // In KB | |||
int sizeThreshold = [ud boolForKey:@"ignoreSmallFiles"] ? smallFileThreshold * 1024 : 0; // The py side wants bytes | |||
[model setSizeThreshold:sizeThreshold]; | |||
[model enable:n2b([ud objectForKey:@"scanTagTrack"]) scanForTag:@"track"]; | |||
[model enable:n2b([ud objectForKey:@"scanTagArtist"]) scanForTag:@"artist"]; | |||
[model enable:n2b([ud objectForKey:@"scanTagAlbum"]) scanForTag:@"album"]; | |||
[model enable:n2b([ud objectForKey:@"scanTagTitle"]) scanForTag:@"title"]; | |||
[model enable:n2b([ud objectForKey:@"scanTagGenre"]) scanForTag:@"genre"]; | |||
[model enable:n2b([ud objectForKey:@"scanTagYear"]) scanForTag:@"year"]; | |||
[model setMatchScaled:n2b([ud objectForKey:@"matchScaled"])]; | |||
} | |||
/* Public */ | |||
- (ResultWindow *)resultWindow | |||
{ | |||
return _resultWindow; | |||
} | |||
- (DirectoryPanel *)directoryPanel | |||
{ | |||
return _directoryPanel; | |||
} | |||
- (DetailsPanel *)detailsPanel | |||
{ | |||
return _detailsPanel; | |||
} | |||
- (HSRecentFiles *)recentResults | |||
{ | |||
return _recentResults; | |||
} | |||
- (NSInteger)getAppMode | |||
{ | |||
return [model getAppMode]; | |||
} | |||
- (void)setAppMode:(NSInteger)appMode | |||
{ | |||
[model setAppMode:appMode]; | |||
if (_preferencesPanel != nil) { | |||
[_preferencesPanel release]; | |||
_preferencesPanel = nil; | |||
} | |||
} | |||
/* Actions */ | |||
- (void)clearPictureCache | |||
{ | |||
NSString *msg = NSLocalizedString(@"Do you really want to remove all your cached picture analysis?", @""); | |||
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO | |||
return; | |||
[model clearPictureCache]; | |||
} | |||
- (void)loadResults | |||
{ | |||
NSOpenPanel *op = [NSOpenPanel openPanel]; | |||
[op setCanChooseFiles:YES]; | |||
[op setCanChooseDirectories:NO]; | |||
[op setCanCreateDirectories:NO]; | |||
[op setAllowsMultipleSelection:NO]; | |||
[op setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]]; | |||
[op setTitle:NSLocalizedString(@"Select a results file to load", @"")]; | |||
if ([op runModal] == NSOKButton) { | |||
NSString *filename = [[[op URLs] objectAtIndex:0] path]; | |||
[model loadResultsFrom:filename]; | |||
[[self recentResults] addFile:filename]; | |||
} | |||
} | |||
- (void)openWebsite | |||
{ | |||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru/"]]; | |||
} | |||
- (void)openHelp | |||
{ | |||
NSBundle *b = [NSBundle mainBundle]; | |||
NSString *p = [b pathForResource:@"index" ofType:@"html" inDirectory:@"help"]; | |||
NSURL *u = [NSURL fileURLWithPath:p]; | |||
[[NSWorkspace sharedWorkspace] openURL:u]; | |||
} | |||
- (void)showAboutBox | |||
{ | |||
if (_aboutBox == nil) { | |||
_aboutBox = [[HSAboutBox alloc] initWithApp:model]; | |||
} | |||
[[_aboutBox window] makeKeyAndOrderFront:nil]; | |||
} | |||
- (void)showDirectoryWindow | |||
{ | |||
[[[self directoryPanel] window] makeKeyAndOrderFront:nil]; | |||
} | |||
- (void)showPreferencesPanel | |||
{ | |||
if (_preferencesPanel == nil) { | |||
NSWindow *window; | |||
NSInteger appMode = [model getAppMode]; | |||
if (appMode == AppModePicture) { | |||
window = createPreferencesPanelPicture_UI(nil); | |||
} | |||
else if (appMode == AppModeMusic) { | |||
window = createPreferencesPanelMusic_UI(nil); | |||
} | |||
else { | |||
window = createPreferencesPanelStandard_UI(nil); | |||
} | |||
_preferencesPanel = [[NSWindowController alloc] initWithWindow:window]; | |||
} | |||
[_preferencesPanel showWindow:nil]; | |||
} | |||
- (void)showResultWindow | |||
{ | |||
[[[self resultWindow] window] makeKeyAndOrderFront:nil]; | |||
} | |||
- (void)showIgnoreList | |||
{ | |||
[model showIgnoreList]; | |||
} | |||
- (void)startScanning | |||
{ | |||
[[self directoryPanel] startDuplicateScan]; | |||
} | |||
/* Delegate */ | |||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification | |||
{ | |||
[model loadSession]; | |||
} | |||
- (void)applicationWillBecomeActive:(NSNotification *)aNotification | |||
{ | |||
if (![[[self directoryPanel] window] isVisible]) { | |||
[[self directoryPanel] showWindow:NSApp]; | |||
} | |||
} | |||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender | |||
{ | |||
if ([model resultsAreModified]) { | |||
NSString *msg = NSLocalizedString(@"You have unsaved results, do you really want to quit?", @""); | |||
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) { // NO | |||
return NSTerminateCancel; | |||
} | |||
} | |||
return NSTerminateNow; | |||
} | |||
- (void)applicationWillTerminate:(NSNotification *)aNotification | |||
{ | |||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; | |||
NSInteger sc = [ud integerForKey:@"sessionCountSinceLastIgnorePurge"]; | |||
if (sc >= 10) { | |||
sc = -1; | |||
[model purgeIgnoreList]; | |||
} | |||
sc++; | |||
[model saveSession]; | |||
[ud setInteger:sc forKey:@"sessionCountSinceLastIgnorePurge"]; | |||
// NSApplication does not release nib instances objects, we must do it manually | |||
// Well, it isn't needed because the memory is freed anyway (we are quitting the application | |||
// But I need to release HSRecentFiles so it saves the user defaults | |||
[_directoryPanel release]; | |||
[_recentResults release]; | |||
} | |||
- (void)recentFileClicked:(NSString *)path | |||
{ | |||
[model loadResultsFrom:path]; | |||
} | |||
/* model --> view */ | |||
- (void)showMessage:(NSString *)msg | |||
{ | |||
[Dialogs showMessage:msg]; | |||
} | |||
- (BOOL)askYesNoWithPrompt:(NSString *)prompt | |||
{ | |||
return [Dialogs askYesNo:prompt] == NSAlertFirstButtonReturn; | |||
} | |||
- (void)createResultsWindow | |||
{ | |||
if (_resultWindow != nil) { | |||
[_resultWindow release]; | |||
} | |||
if (_detailsPanel != nil) { | |||
[_detailsPanel release]; | |||
} | |||
// Warning: creation order is important | |||
// If the details panel is not created first and that there are some results in the model | |||
// (happens if we load results), a dupe selection event triggers a details refresh in the | |||
// core before we have the chance to initialize it, and then we crash. | |||
_detailsPanel = [self createDetailsPanel]; | |||
_resultWindow = [[ResultWindow alloc] initWithParentApp:self]; | |||
} | |||
- (void)showResultsWindow | |||
{ | |||
[[[self resultWindow] window] makeKeyAndOrderFront:nil]; | |||
} | |||
- (void)showProblemDialog | |||
{ | |||
[_problemDialog showWindow:self]; | |||
} | |||
- (NSString *)selectDestFolderWithPrompt:(NSString *)prompt | |||
{ | |||
NSOpenPanel *op = [NSOpenPanel openPanel]; | |||
[op setCanChooseFiles:NO]; | |||
[op setCanChooseDirectories:YES]; | |||
[op setCanCreateDirectories:YES]; | |||
[op setAllowsMultipleSelection:NO]; | |||
[op setTitle:prompt]; | |||
if ([op runModal] == NSOKButton) { | |||
return [[[op URLs] objectAtIndex:0] path]; | |||
} | |||
else { | |||
return nil; | |||
} | |||
} | |||
- (NSString *)selectDestFileWithPrompt:(NSString *)prompt extension:(NSString *)extension | |||
{ | |||
NSSavePanel *sp = [NSSavePanel savePanel]; | |||
[sp setCanCreateDirectories:YES]; | |||
[sp setAllowedFileTypes:[NSArray arrayWithObject:extension]]; | |||
[sp setTitle:prompt]; | |||
if ([sp runModal] == NSOKButton) { | |||
return [[sp URL] path]; | |||
} | |||
else { | |||
return nil; | |||
} | |||
} | |||
@end |
@@ -0,0 +1,24 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#define JobStarted @"JobStarted" | |||
#define JobInProgress @"JobInProgress" | |||
#define TableFontSize @"TableFontSize" | |||
#define jobLoad @"job_load" | |||
#define jobScan @"job_scan" | |||
#define jobCopy @"job_copy" | |||
#define jobMove @"job_move" | |||
#define jobDelete @"job_delete" | |||
#define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType" | |||
#define ImageLoadedNotification @"ImageLoadedNotification" | |||
#define AppModeStandard 0 | |||
#define AppModeMusic 1 | |||
#define AppModePicture 2 |
@@ -0,0 +1,33 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import "PyDeletionOptions.h" | |||
@interface DeletionOptions : NSWindowController | |||
{ | |||
PyDeletionOptions *model; | |||
NSTextField *messageTextField; | |||
NSButton *linkButton; | |||
NSMatrix *linkTypeRadio; | |||
NSButton *directButton; | |||
} | |||
@property (readwrite, retain) NSTextField *messageTextField; | |||
@property (readwrite, retain) NSButton *linkButton; | |||
@property (readwrite, retain) NSMatrix *linkTypeRadio; | |||
@property (readwrite, retain) NSButton *directButton; | |||
- (id)initWithPyRef:(PyObject *)aPyRef; | |||
- (void)updateOptions; | |||
- (void)proceed; | |||
- (void)cancel; | |||
@end |
@@ -0,0 +1,72 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "DeletionOptions.h" | |||
#import "DeletionOptions_UI.h" | |||
#import "HSPyUtil.h" | |||
@implementation DeletionOptions | |||
@synthesize messageTextField; | |||
@synthesize linkButton; | |||
@synthesize linkTypeRadio; | |||
@synthesize directButton; | |||
- (id)initWithPyRef:(PyObject *)aPyRef | |||
{ | |||
self = [super initWithWindow:nil]; | |||
model = [[PyDeletionOptions alloc] initWithModel:aPyRef]; | |||
[self setWindow:createDeletionOptions_UI(self)]; | |||
[model bindCallback:createCallback(@"DeletionOptionsView", self)]; | |||
return self; | |||
} | |||
- (void)dealloc | |||
{ | |||
[model release]; | |||
[super dealloc]; | |||
} | |||
- (void)updateOptions | |||
{ | |||
[model setLinkDeleted:[linkButton state] == NSOnState]; | |||
[model setUseHardlinks:[linkTypeRadio selectedColumn] == 1]; | |||
[model setDirect:[directButton state] == NSOnState]; | |||
} | |||
- (void)proceed | |||
{ | |||
[NSApp stopModalWithCode:NSOKButton]; | |||
} | |||
- (void)cancel | |||
{ | |||
[NSApp stopModalWithCode:NSCancelButton]; | |||
} | |||
/* model --> view */ | |||
- (void)updateMsg:(NSString *)msg | |||
{ | |||
[messageTextField setStringValue:msg]; | |||
} | |||
- (BOOL)show | |||
{ | |||
[linkButton setState:NSOffState]; | |||
[directButton setState:NSOffState]; | |||
[linkTypeRadio selectCellAtRow:0 column:0]; | |||
NSInteger r = [NSApp runModalForWindow:[self window]]; | |||
[[self window] close]; | |||
return r == NSOKButton; | |||
} | |||
- (void)setHardlinkOptionEnabled:(BOOL)enabled | |||
{ | |||
[linkTypeRadio setEnabled:enabled]; | |||
} | |||
@end |
@@ -0,0 +1,31 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import <Python.h> | |||
#import "PyDetailsPanel.h" | |||
@interface DetailsPanel : NSWindowController <NSTableViewDataSource> | |||
{ | |||
NSTableView *detailsTable; | |||
PyDetailsPanel *model; | |||
} | |||
@property (readwrite, retain) NSTableView *detailsTable; | |||
- (id)initWithPyRef:(PyObject *)aPyRef; | |||
- (PyDetailsPanel *)model; | |||
- (NSWindow *)createWindow; | |||
- (BOOL)isVisible; | |||
- (void)toggleVisibility; | |||
/* Python --> Cocoa */ | |||
- (void)refresh; | |||
@end |
@@ -0,0 +1,81 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "DetailsPanel.h" | |||
#import "HSPyUtil.h" | |||
#import "DetailsPanel_UI.h" | |||
@implementation DetailsPanel | |||
@synthesize detailsTable; | |||
- (id)initWithPyRef:(PyObject *)aPyRef | |||
{ | |||
self = [super initWithWindow:nil]; | |||
[self setWindow:[self createWindow]]; | |||
model = [[PyDetailsPanel alloc] initWithModel:aPyRef]; | |||
[model bindCallback:createCallback(@"DetailsPanelView", self)]; | |||
return self; | |||
} | |||
- (void)dealloc | |||
{ | |||
[model release]; | |||
[super dealloc]; | |||
} | |||
- (PyDetailsPanel *)model | |||
{ | |||
return (PyDetailsPanel *)model; | |||
} | |||
- (NSWindow *)createWindow | |||
{ | |||
return createDetailsPanel_UI(self); | |||
} | |||
- (void)refreshDetails | |||
{ | |||
[detailsTable reloadData]; | |||
} | |||
- (BOOL)isVisible | |||
{ | |||
return [[self window] isVisible]; | |||
} | |||
- (void)toggleVisibility | |||
{ | |||
if ([self isVisible]) { | |||
[[self window] close]; | |||
} | |||
else { | |||
[self refreshDetails]; // selection might have changed since last time | |||
[[self window] orderFront:nil]; | |||
} | |||
} | |||
/* NSTableView Delegate */ | |||
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView | |||
{ | |||
return [[self model] numberOfRows]; | |||
} | |||
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row | |||
{ | |||
return [[self model] valueForColumn:[column identifier] row:row]; | |||
} | |||
/* Python --> Cocoa */ | |||
- (void)refresh | |||
{ | |||
if ([[self window] isVisible]) { | |||
[self refreshDetails]; | |||
} | |||
} | |||
@end |
@@ -0,0 +1,32 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import "DetailsPanel.h" | |||
#import "PyDupeGuru.h" | |||
@interface DetailsPanelPicture : DetailsPanel | |||
{ | |||
NSImageView *dupeImage; | |||
NSProgressIndicator *dupeProgressIndicator; | |||
NSImageView *refImage; | |||
NSProgressIndicator *refProgressIndicator; | |||
PyDupeGuru *pyApp; | |||
BOOL _needsRefresh; | |||
NSString *_dupePath; | |||
NSString *_refPath; | |||
} | |||
@property (readwrite, retain) NSImageView *dupeImage; | |||
@property (readwrite, retain) NSProgressIndicator *dupeProgressIndicator; | |||
@property (readwrite, retain) NSImageView *refImage; | |||
@property (readwrite, retain) NSProgressIndicator *refProgressIndicator; | |||
- (id)initWithApp:(PyDupeGuru *)aApp; | |||
@end |
@@ -0,0 +1,96 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "Utils.h" | |||
#import "NSNotificationAdditions.h" | |||
#import "NSImageAdditions.h" | |||
#import "PyDupeGuru.h" | |||
#import "DetailsPanelPicture.h" | |||
#import "Consts.h" | |||
#import "DetailsPanelPicture_UI.h" | |||
@implementation DetailsPanelPicture | |||
@synthesize dupeImage; | |||
@synthesize dupeProgressIndicator; | |||
@synthesize refImage; | |||
@synthesize refProgressIndicator; | |||
- (id)initWithApp:(PyDupeGuru *)aApp | |||
{ | |||
self = [super initWithPyRef:[aApp detailsPanel]]; | |||
pyApp = aApp; | |||
_needsRefresh = YES; | |||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageLoaded:) name:ImageLoadedNotification object:self]; | |||
return self; | |||
} | |||
- (NSWindow *)createWindow | |||
{ | |||
return createDetailsPanelPicture_UI(self); | |||
} | |||
- (void)loadImageAsync:(NSString *)imagePath | |||
{ | |||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | |||
NSImage *image = [[NSImage alloc] initByReferencingFile:imagePath]; | |||
NSImage *thumbnail = [image imageByScalingProportionallyToSize:NSMakeSize(512,512)]; | |||
[image release]; | |||
NSMutableDictionary *params = [NSMutableDictionary dictionary]; | |||
[params setValue:imagePath forKey:@"imagePath"]; | |||
[params setValue:thumbnail forKey:@"image"]; | |||
[[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:ImageLoadedNotification object:self userInfo:params waitUntilDone:YES]; | |||
[pool release]; | |||
} | |||
- (void)refreshDetails | |||
{ | |||
if (!_needsRefresh) | |||
return; | |||
[detailsTable reloadData]; | |||
NSString *refPath = [pyApp getSelectedDupeRefPath]; | |||
if (_refPath != nil) | |||
[_refPath autorelease]; | |||
_refPath = [refPath retain]; | |||
[NSThread detachNewThreadSelector:@selector(loadImageAsync:) toTarget:self withObject:refPath]; | |||
NSString *dupePath = [pyApp getSelectedDupePath]; | |||
if (_dupePath != nil) | |||
[_dupePath autorelease]; | |||
_dupePath = [dupePath retain]; | |||
if (![dupePath isEqual: refPath]) | |||
[NSThread detachNewThreadSelector:@selector(loadImageAsync:) toTarget:self withObject:dupePath]; | |||
[refProgressIndicator startAnimation:nil]; | |||
[dupeProgressIndicator startAnimation:nil]; | |||
_needsRefresh = NO; | |||
} | |||
/* Notifications */ | |||
- (void)imageLoaded:(NSNotification *)aNotification | |||
{ | |||
NSString *imagePath = [[aNotification userInfo] valueForKey:@"imagePath"]; | |||
NSImage *image = [[aNotification userInfo] valueForKey:@"image"]; | |||
if ([imagePath isEqual: _refPath]) | |||
{ | |||
[refImage setImage:image]; | |||
[refProgressIndicator stopAnimation:nil]; | |||
} | |||
if ([imagePath isEqual: _dupePath]) | |||
{ | |||
[dupeImage setImage:image]; | |||
[dupeProgressIndicator stopAnimation:nil]; | |||
} | |||
} | |||
/* Python --> Cocoa */ | |||
- (void)refresh | |||
{ | |||
_needsRefresh = YES; | |||
[super refresh]; | |||
} | |||
@end |
@@ -0,0 +1,21 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import <Python.h> | |||
#import "HSOutline.h" | |||
#import "PyDirectoryOutline.h" | |||
#define DGAddedFoldersNotification @"DGAddedFoldersNotification" | |||
@interface DirectoryOutline : HSOutline {} | |||
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView; | |||
- (PyDirectoryOutline *)model; | |||
- (void)selectAll; | |||
@end; |
@@ -0,0 +1,87 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "DirectoryOutline.h" | |||
@implementation DirectoryOutline | |||
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView | |||
{ | |||
self = [super initWithPyRef:aPyRef wrapperClass:[PyDirectoryOutline class] | |||
callbackClassName:@"DirectoryOutlineView" view:aOutlineView]; | |||
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; | |||
return self; | |||
} | |||
- (PyDirectoryOutline *)model | |||
{ | |||
return (PyDirectoryOutline *)model; | |||
} | |||
/* Public */ | |||
- (void)selectAll | |||
{ | |||
[[self model] selectAll]; | |||
} | |||
/* Delegate */ | |||
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index | |||
{ | |||
NSPasteboard *pboard; | |||
NSDragOperation sourceDragMask; | |||
sourceDragMask = [info draggingSourceOperationMask]; | |||
pboard = [info draggingPasteboard]; | |||
if ([[pboard types] containsObject:NSFilenamesPboardType]) { | |||
if (sourceDragMask & NSDragOperationLink) | |||
return NSDragOperationLink; | |||
} | |||
return NSDragOperationNone; | |||
} | |||
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index | |||
{ | |||
NSPasteboard *pboard; | |||
NSDragOperation sourceDragMask; | |||
sourceDragMask = [info draggingSourceOperationMask]; | |||
pboard = [info draggingPasteboard]; | |||
if ([[pboard types] containsObject:NSFilenamesPboardType]) { | |||
NSArray *foldernames = [pboard propertyListForType:NSFilenamesPboardType]; | |||
if (!(sourceDragMask & NSDragOperationLink)) | |||
return NO; | |||
for (NSString *foldername in foldernames) { | |||
[[self model] addDirectory:foldername]; | |||
} | |||
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:foldernames forKey:@"foldernames"]; | |||
[[NSNotificationCenter defaultCenter] postNotificationName:DGAddedFoldersNotification | |||
object:self userInfo:userInfo]; | |||
} | |||
return YES; | |||
} | |||
- (void)outlineView:(NSOutlineView *)aOutlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item | |||
{ | |||
if ([cell isKindOfClass:[NSTextFieldCell class]]) { | |||
NSTextFieldCell *textCell = cell; | |||
NSIndexPath *path = item; | |||
BOOL selected = [path isEqualTo:[[self view] selectedPath]]; | |||
if (selected) { | |||
[textCell setTextColor:[NSColor blackColor]]; | |||
return; | |||
} | |||
NSInteger state = [self intProperty:@"state" valueAtPath:path]; | |||
if (state == 1) { | |||
[textCell setTextColor:[NSColor blueColor]]; | |||
} | |||
else if (state == 2) { | |||
[textCell setTextColor:[NSColor redColor]]; | |||
} | |||
else { | |||
[textCell setTextColor:[NSColor blackColor]]; | |||
} | |||
} | |||
} | |||
@end |
@@ -0,0 +1,57 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import "HSOutlineView.h" | |||
#import "HSRecentFiles.h" | |||
#import "DirectoryOutline.h" | |||
#import "PyDupeGuru.h" | |||
@class AppDelegate; | |||
@interface DirectoryPanel : NSWindowController <NSOpenSavePanelDelegate> | |||
{ | |||
AppDelegate *_app; | |||
PyDupeGuru *model; | |||
HSRecentFiles *_recentDirectories; | |||
DirectoryOutline *outline; | |||
BOOL _alwaysShowPopUp; | |||
NSSegmentedControl *appModeSelector; | |||
NSPopUpButton *scanTypePopup; | |||
NSPopUpButton *addButtonPopUp; | |||
NSPopUpButton *loadRecentButtonPopUp; | |||
HSOutlineView *outlineView; | |||
NSButton *removeButton; | |||
NSButton *loadResultsButton; | |||
} | |||
@property (readwrite, retain) NSSegmentedControl *appModeSelector; | |||
@property (readwrite, retain) NSPopUpButton *scanTypePopup; | |||
@property (readwrite, retain) NSPopUpButton *addButtonPopUp; | |||
@property (readwrite, retain) NSPopUpButton *loadRecentButtonPopUp; | |||
@property (readwrite, retain) HSOutlineView *outlineView; | |||
@property (readwrite, retain) NSButton *removeButton; | |||
@property (readwrite, retain) NSButton *loadResultsButton; | |||
- (id)initWithParentApp:(AppDelegate *)aParentApp; | |||
- (void)fillPopUpMenu; | |||
- (void)fillScanTypeMenu; | |||
- (void)adjustUIToLocalization; | |||
- (void)askForDirectory; | |||
- (void)popupAddDirectoryMenu:(id)sender; | |||
- (void)popupLoadRecentMenu:(id)sender; | |||
- (void)removeSelectedDirectory; | |||
- (void)startDuplicateScan; | |||
- (void)addDirectory:(NSString *)directory; | |||
- (void)refreshRemoveButtonText; | |||
- (void)markAll; | |||
@end |
@@ -0,0 +1,256 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "DirectoryPanel.h" | |||
#import "DirectoryPanel_UI.h" | |||
#import "Dialogs.h" | |||
#import "Utils.h" | |||
#import "AppDelegate.h" | |||
#import "Consts.h" | |||
@implementation DirectoryPanel | |||
@synthesize appModeSelector; | |||
@synthesize scanTypePopup; | |||
@synthesize addButtonPopUp; | |||
@synthesize loadRecentButtonPopUp; | |||
@synthesize outlineView; | |||
@synthesize removeButton; | |||
@synthesize loadResultsButton; | |||
- (id)initWithParentApp:(AppDelegate *)aParentApp | |||
{ | |||
self = [super initWithWindow:nil]; | |||
[self setWindow:createDirectoryPanel_UI(self)]; | |||
_app = aParentApp; | |||
model = [_app model]; | |||
[[self window] setTitle:[model appName]]; | |||
self.appModeSelector.selectedSegment = 0; | |||
[self fillScanTypeMenu]; | |||
_alwaysShowPopUp = NO; | |||
[self fillPopUpMenu]; | |||
_recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]]; | |||
[_recentDirectories setDelegate:self]; | |||
outline = [[DirectoryOutline alloc] initWithPyRef:[model directoryTree] outlineView:outlineView]; | |||
[self refreshRemoveButtonText]; | |||
[self adjustUIToLocalization]; | |||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:) | |||
name:NSOutlineViewSelectionDidChangeNotification object:outlineView]; | |||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(outlineAddedFolders:) | |||
name:DGAddedFoldersNotification object:outline]; | |||
return self; | |||
} | |||
- (void)dealloc | |||
{ | |||
[outline release]; | |||
[_recentDirectories release]; | |||
[super dealloc]; | |||
} | |||
/* Private */ | |||
- (void)fillPopUpMenu | |||
{ | |||
NSMenu *m = [addButtonPopUp menu]; | |||
NSMenuItem *mi = [m addItemWithTitle:NSLocalizedString(@"Add New Folder...", @"") action:@selector(askForDirectory) keyEquivalent:@""]; | |||
[mi setTarget:self]; | |||
[m addItem:[NSMenuItem separatorItem]]; | |||
} | |||
- (void)fillScanTypeMenu | |||
{ | |||
[[self scanTypePopup] unbind:@"selectedIndex"]; | |||
[[self scanTypePopup] removeAllItems]; | |||
[[self scanTypePopup] addItemsWithTitles:[[_app model] getScanOptions]]; | |||
NSString *keypath; | |||
NSInteger appMode = [_app getAppMode]; | |||
if (appMode == AppModePicture) { | |||
keypath = @"values.scanTypePicture"; | |||
} | |||
else if (appMode == AppModeMusic) { | |||
keypath = @"values.scanTypeMusic"; | |||
} | |||
else { | |||
keypath = @"values.scanTypeStandard"; | |||
} | |||
[[self scanTypePopup] bind:@"selectedIndex" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:keypath options:nil]; | |||
} | |||
- (void)adjustUIToLocalization | |||
{ | |||
NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0]; | |||
NSInteger loadResultsWidthDelta = 0; | |||
if ([lang isEqual:@"ru"]) { | |||
loadResultsWidthDelta = 50; | |||
} | |||
else if ([lang isEqual:@"uk"]) { | |||
loadResultsWidthDelta = 70; | |||
} | |||
else if ([lang isEqual:@"hy"]) { | |||
loadResultsWidthDelta = 30; | |||
} | |||
if (loadResultsWidthDelta) { | |||
NSRect r = [loadResultsButton frame]; | |||
r.size.width += loadResultsWidthDelta; | |||
r.origin.x -= loadResultsWidthDelta; | |||
[loadResultsButton setFrame:r]; | |||
} | |||
} | |||
/* Actions */ | |||
- (void)askForDirectory | |||
{ | |||
NSOpenPanel *op = [NSOpenPanel openPanel]; | |||
[op setCanChooseFiles:YES]; | |||
[op setCanChooseDirectories:YES]; | |||
[op setAllowsMultipleSelection:YES]; | |||
[op setTitle:NSLocalizedString(@"Select a folder to add to the scanning list", @"")]; | |||
[op setDelegate:self]; | |||
if ([op runModal] == NSOKButton) { | |||
for (NSURL *directoryURL in [op URLs]) { | |||
[self addDirectory:[directoryURL path]]; | |||
} | |||
} | |||
} | |||
- (void)changeAppMode:(id)sender | |||
{ | |||
NSInteger appMode; | |||
NSUInteger selectedSegment = self.appModeSelector.selectedSegment; | |||
if (selectedSegment == 2) { | |||
appMode = AppModePicture; | |||
} | |||
else if (selectedSegment == 1) { | |||
appMode = AppModeMusic; | |||
} | |||
else { | |||
appMode = AppModeStandard; | |||
} | |||
[_app setAppMode:appMode]; | |||
[self fillScanTypeMenu]; | |||
} | |||
- (void)popupAddDirectoryMenu:(id)sender | |||
{ | |||
if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) { | |||
[self askForDirectory]; | |||
} | |||
else { | |||
[addButtonPopUp selectItem:nil]; | |||
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]]; | |||
} | |||
} | |||
- (void)popupLoadRecentMenu:(id)sender | |||
{ | |||
if ([[[_app recentResults] filepaths] count] > 0) { | |||
NSMenu *m = [loadRecentButtonPopUp menu]; | |||
while ([m numberOfItems] > 0) { | |||
[m removeItemAtIndex:0]; | |||
} | |||
NSMenuItem *mi = [m addItemWithTitle:NSLocalizedString(@"Load from file...", @"") action:@selector(loadResults) keyEquivalent:@""]; | |||
[mi setTarget:_app]; | |||
[m addItem:[NSMenuItem separatorItem]]; | |||
[[_app recentResults] fillMenu:m]; | |||
[loadRecentButtonPopUp selectItem:nil]; | |||
[[loadRecentButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]]; | |||
} | |||
else { | |||
[_app loadResults]; | |||
} | |||
} | |||
- (void)removeSelectedDirectory | |||
{ | |||
[[self window] makeKeyAndOrderFront:nil]; | |||
[[outline model] removeSelectedDirectory]; | |||
[self refreshRemoveButtonText]; | |||
} | |||
- (void)startDuplicateScan | |||
{ | |||
if ([model resultsAreModified]) { | |||
if ([Dialogs askYesNo:NSLocalizedString(@"You have unsaved results, do you really want to continue?", @"")] == NSAlertSecondButtonReturn) // NO | |||
return; | |||
} | |||
[_app setScanOptions]; | |||
[model doScan]; | |||
} | |||
/* Public */ | |||
- (void)addDirectory:(NSString *)directory | |||
{ | |||
[model addDirectory:directory]; | |||
[_recentDirectories addFile:directory]; | |||
[[self window] makeKeyAndOrderFront:nil]; | |||
} | |||
- (void)refreshRemoveButtonText | |||
{ | |||
if ([outlineView selectedRow] < 0) { | |||
[removeButton setEnabled:NO]; | |||
return; | |||
} | |||
[removeButton setEnabled:YES]; | |||
NSIndexPath *path = [outline selectedIndexPath]; | |||
if (path != nil) { | |||
NSInteger state = [outline intProperty:@"state" valueAtPath:path]; | |||
BOOL shouldDisplayArrow = ([path length] > 1) && (state == 2); | |||
NSString *imgName = shouldDisplayArrow ? @"NSGoLeftTemplate" : @"NSRemoveTemplate"; | |||
[removeButton setImage:[NSImage imageNamed:imgName]]; | |||
} | |||
} | |||
- (void)markAll | |||
{ | |||
/* markAll isn't very descriptive of what we do, but since we re-use the Mark All button from | |||
the result window, we don't have much choice. | |||
*/ | |||
[outline selectAll]; | |||
} | |||
/* Delegate */ | |||
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path | |||
{ | |||
BOOL isdir; | |||
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isdir]; | |||
return isdir; | |||
} | |||
- (void)recentFileClicked:(NSString *)path | |||
{ | |||
[self addDirectory:path]; | |||
} | |||
- (BOOL)validateMenuItem:(NSMenuItem *)item | |||
{ | |||
if ([item action] == @selector(markAll)) { | |||
[item setTitle:NSLocalizedString(@"Select All", @"")]; | |||
} | |||
return YES; | |||
} | |||
/* Notifications */ | |||
- (void)directorySelectionChanged:(NSNotification *)aNotification | |||
{ | |||
[self refreshRemoveButtonText]; | |||
} | |||
- (void)outlineAddedFolders:(NSNotification *)aNotification | |||
{ | |||
NSArray *foldernames = [[aNotification userInfo] objectForKey:@"foldernames"]; | |||
for (NSString *foldername in foldernames) { | |||
[_recentDirectories addFile:foldername]; | |||
} | |||
} | |||
@end |
@@ -0,0 +1,25 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import "PyIgnoreListDialog.h" | |||
#import "HSTable.h" | |||
@interface IgnoreListDialog : NSWindowController | |||
{ | |||
PyIgnoreListDialog *model; | |||
HSTable *ignoreListTable; | |||
NSTableView *ignoreListTableView; | |||
} | |||
@property (readwrite, retain) PyIgnoreListDialog *model; | |||
@property (readwrite, retain) NSTableView *ignoreListTableView; | |||
- (id)initWithPyRef:(PyObject *)aPyRef; | |||
- (void)initializeColumns; | |||
@end |
@@ -0,0 +1,51 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "IgnoreListDialog.h" | |||
#import "IgnoreListDialog_UI.h" | |||
#import "HSPyUtil.h" | |||
@implementation IgnoreListDialog | |||
@synthesize model; | |||
@synthesize ignoreListTableView; | |||
- (id)initWithPyRef:(PyObject *)aPyRef | |||
{ | |||
self = [super initWithWindow:nil]; | |||
self.model = [[[PyIgnoreListDialog alloc] initWithModel:aPyRef] autorelease]; | |||
[self.model bindCallback:createCallback(@"IgnoreListDialogView", self)]; | |||
[self setWindow:createIgnoreListDialog_UI(self)]; | |||
ignoreListTable = [[HSTable alloc] initWithPyRef:[model ignoreListTable] tableView:ignoreListTableView]; | |||
[self initializeColumns]; | |||
return self; | |||
} | |||
- (void)dealloc | |||
{ | |||
[ignoreListTable release]; | |||
[super dealloc]; | |||
} | |||
- (void)initializeColumns | |||
{ | |||
HSColumnDef defs[] = { | |||
{@"path1", 240, 40, 0, NO, nil}, | |||
{@"path2", 240, 40, 0, NO, nil}, | |||
nil | |||
}; | |||
[[ignoreListTable columns] initializeColumns:defs]; | |||
[[ignoreListTable columns] setColumnsAsReadOnly]; | |||
} | |||
/* model --> view */ | |||
- (void)show | |||
{ | |||
[self showWindow:self]; | |||
} | |||
@end |
@@ -0,0 +1,38 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |||
<plist version="1.0"> | |||
<dict> | |||
<key>CFBundleDevelopmentRegion</key> | |||
<string>English</string> | |||
<key>CFBundleExecutable</key> | |||
<string>dupeGuru</string> | |||
<key>CFBundleHelpBookFolder</key> | |||
<string>dupeguru_help</string> | |||
<key>CFBundleHelpBookName</key> | |||
<string>dupeGuru Help</string> | |||
<key>CFBundleIconFile</key> | |||
<string>dupeguru</string> | |||
<key>CFBundleIdentifier</key> | |||
<string>com.hardcoded-software.dupeguru</string> | |||
<key>CFBundleInfoDictionaryVersion</key> | |||
<string>6.0</string> | |||
<key>CFBundleName</key> | |||
<string>dupeGuru</string> | |||
<key>CFBundlePackageType</key> | |||
<string>APPL</string> | |||
<key>CFBundleSignature</key> | |||
<string>hsft</string> | |||
<key>CFBundleShortVersionString</key> | |||
<string>{version}</string> | |||
<key>CFBundleVersion</key> | |||
<string>{version}</string> | |||
<key>NSPrincipalClass</key> | |||
<string>NSApplication</string> | |||
<key>NSHumanReadableCopyright</key> | |||
<string>ยฉ Hardcoded Software, 2016</string> | |||
<key>SUFeedURL</key> | |||
<string>https://www.hardcoded.net/updates/dupeguru.appcast</string> | |||
<key>SUPublicDSAKeyFile</key> | |||
<string>dsa_pub.pem</string> | |||
</dict> | |||
</plist> |
@@ -0,0 +1,37 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import "PyPrioritizeDialog.h" | |||
#import "HSPopUpList.h" | |||
#import "HSSelectableList.h" | |||
#import "PrioritizeList.h" | |||
#import "PyDupeGuru.h" | |||
@interface PrioritizeDialog : NSWindowController | |||
{ | |||
NSPopUpButton *categoryPopUpView; | |||
NSTableView *criteriaTableView; | |||
NSTableView *prioritizationTableView; | |||
PyPrioritizeDialog *model; | |||
HSPopUpList *categoryPopUp; | |||
HSSelectableList *criteriaList; | |||
PrioritizeList *prioritizationList; | |||
} | |||
@property (readwrite, retain) NSPopUpButton *categoryPopUpView; | |||
@property (readwrite, retain) NSTableView *criteriaTableView; | |||
@property (readwrite, retain) NSTableView *prioritizationTableView; | |||
- (id)initWithApp:(PyDupeGuru *)aApp; | |||
- (PyPrioritizeDialog *)model; | |||
- (void)ok; | |||
- (void)cancel; | |||
@end; |
@@ -0,0 +1,56 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "PrioritizeDialog.h" | |||
#import "PrioritizeDialog_UI.h" | |||
#import "HSPyUtil.h" | |||
@implementation PrioritizeDialog | |||
@synthesize categoryPopUpView; | |||
@synthesize criteriaTableView; | |||
@synthesize prioritizationTableView; | |||
- (id)initWithApp:(PyDupeGuru *)aApp | |||
{ | |||
self = [super initWithWindowNibName:@"PrioritizeDialog"]; | |||
model = [[PyPrioritizeDialog alloc] initWithApp:[aApp pyRef]]; | |||
[self setWindow:createPrioritizeDialog_UI(self)]; | |||
categoryPopUp = [[HSPopUpList alloc] initWithPyRef:[[self model] categoryList] popupView:categoryPopUpView]; | |||
criteriaList = [[HSSelectableList alloc] initWithPyRef:[[self model] criteriaList] tableView:criteriaTableView]; | |||
prioritizationList = [[PrioritizeList alloc] initWithPyRef:[[self model] prioritizationList] tableView:prioritizationTableView]; | |||
[model bindCallback:createCallback(@"PrioritizeDialogView", self)]; | |||
return self; | |||
} | |||
- (void)dealloc | |||
{ | |||
[categoryPopUp release]; | |||
[criteriaList release]; | |||
[prioritizationList release]; | |||
[model release]; | |||
[super dealloc]; | |||
} | |||
- (PyPrioritizeDialog *)model | |||
{ | |||
return (PyPrioritizeDialog *)model; | |||
} | |||
- (void)ok | |||
{ | |||
[NSApp stopModal]; | |||
[self close]; | |||
} | |||
- (void)cancel | |||
{ | |||
[NSApp abortModal]; | |||
[self close]; | |||
} | |||
@end |
@@ -0,0 +1,16 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import "HSSelectableList.h" | |||
#import "PyPrioritizeList.h" | |||
@interface PrioritizeList : HSSelectableList {} | |||
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView; | |||
- (PyPrioritizeList *)model; | |||
@end |
@@ -0,0 +1,58 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "PrioritizeList.h" | |||
#import "Utils.h" | |||
#import "Consts.h" | |||
@implementation PrioritizeList | |||
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView | |||
{ | |||
self = [super initWithPyRef:aPyRef wrapperClass:[PyPrioritizeList class] | |||
callbackClassName:@"PrioritizeListView" view:aTableView]; | |||
return self; | |||
} | |||
- (PyPrioritizeList *)model | |||
{ | |||
return (PyPrioritizeList *)model; | |||
} | |||
- (void)setView:(NSTableView *)aTableView | |||
{ | |||
[super setView:aTableView]; | |||
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType]]; | |||
} | |||
- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard | |||
{ | |||
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes]; | |||
[pboard declareTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType] owner:self]; | |||
[pboard setData:data forType:DGPrioritizeIndexPasteboardType]; | |||
return YES; | |||
} | |||
- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row | |||
proposedDropOperation:(NSTableViewDropOperation)op | |||
{ | |||
if (op == NSTableViewDropAbove) { | |||
return NSDragOperationMove; | |||
} | |||
return NSDragOperationNone; | |||
} | |||
- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info | |||
row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation | |||
{ | |||
NSPasteboard* pboard = [info draggingPasteboard]; | |||
NSData* rowData = [pboard dataForType:DGPrioritizeIndexPasteboardType]; | |||
NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData]; | |||
[[self model] moveIndexes:[Utils indexSet2Array:rowIndexes] toIndex:row]; | |||
return YES; | |||
} | |||
@end |
@@ -0,0 +1,26 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import <Cocoa/Cocoa.h> | |||
#import "PyProblemDialog.h" | |||
#import "HSTable.h" | |||
@interface ProblemDialog : NSWindowController | |||
{ | |||
PyProblemDialog *model; | |||
HSTable *problemTable; | |||
NSTableView *problemTableView; | |||
} | |||
@property (readwrite, retain) PyProblemDialog *model; | |||
@property (readwrite, retain) NSTableView *problemTableView; | |||
- (id)initWithPyRef:(PyObject *)aPyRef; | |||
- (void)initializeColumns; | |||
@end |
@@ -0,0 +1,44 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||
which should be included with this package. The terms are also available at | |||
http://www.gnu.org/licenses/gpl-3.0.html | |||
*/ | |||
#import "ProblemDialog.h" | |||
#import "ProblemDialog_UI.h" | |||
#import "Utils.h" | |||
@implementation ProblemDialog | |||
@synthesize model; | |||
@synthesize problemTableView; | |||
- (id)initWithPyRef:(PyObject *)aPyRef | |||
{ | |||
self = [super initWithWindow:nil]; | |||
self.model = [[PyProblemDialog alloc] initWithModel:aPyRef]; | |||
[self setWindow:createProblemDialog_UI(self)]; | |||
problemTable = [[HSTable alloc] initWithPyRef:[self.model problemTable] tableView:problemTableView]; | |||
[self initializeColumns]; | |||
return self; | |||
} | |||
- (void)dealloc | |||
{ | |||
[problemTable release]; | |||
[super dealloc]; | |||
} | |||
- (void)initializeColumns | |||
{ | |||
HSColumnDef defs[] = { | |||
{@"path", 202, 40, 0, NO, nil}, | |||
{@"msg", 228, 40, 0, NO, nil}, | |||
nil | |||
}; | |||
[[problemTable columns] initializeColumns:defs]; | |||
[[problemTable columns] setColumnsAsReadOnly]; | |||
} | |||
@end |
@@ -0,0 +1,23 @@ | |||
/* | |||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net) | |||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file, | |||