diff --git a/.gitmodules b/.gitmodules index 5a80a95c..631477ef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "hscommon"] path = hscommon url = https://github.com/hsoft/hscommon.git -[submodule "cocoalib"] - path = cocoalib - url = https://github.com/hsoft/cocoalib.git diff --git a/Makefile b/Makefile index 516a191b..1d5a16c3 100644 --- a/Makefile +++ b/Makefile @@ -17,13 +17,10 @@ mofiles = $(patsubst %.po,%.mo,$(pofiles)) vpath %.po $(localedirs) vpath %.mo $(localedirs) -all : | run.py +all : | env i18n modules qt/dg_rc.py @echo "Build complete! You can run dupeGuru with 'make run'" -run.py : | env i18n modules qt/dg_rc.py - cp qt/run_template.py run.py - -run: | run.py +run: ./env/bin/python run.py pyc: @@ -103,7 +100,6 @@ uninstall : rm -f "${DESTDIR}${PREFIX}/share/pixmaps/dupeguru.png" clean: - -rm run.py -rm -rf build -rm locale/*/LC_MESSAGES/*.mo -rm core/pe/*.so qt/pe/*.so diff --git a/README.md b/README.md index 8487c1d0..a1e7c6da 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ 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, it's written in Python and uses Qt5. +The Cocoa UI of dupeGuru is hosted in a separate repo: https://github.com/hsoft/dupeguru-cocoa + ## Current status: People wanted dupeGuru has currently only one maintainer, me. This is a dangerous situation that needs to be @@ -50,7 +52,6 @@ This folder contains the source for dupeGuru. Its documentation is in `help`, bu [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. -* 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. * pkg: Skeleton files required to create different packages @@ -61,87 +62,22 @@ There are also other sub-folder that comes from external repositories and are pa git submodules: * 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 +### Prerequisites + +* [Python 3.4+][python] +* PyQt5 + ### make -If you're on linux, you can build the ap for local development with `make`: +dupeGuru is built with "make": $ make $ make run -The `Makefile` is a recent addition, however. You might have to fallback to the legacy build -scripts. - -### Legacy build - -If you're on OS X or that if the `make` method didn't work, you can build dupeGuru with the -legacy scripts. - -There's a bootstrap script that will make building 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. - -### 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.4+][python] -* Mac OS X: OS X 10.10+ with XCode command line tools. -* Linux: PyQt5 - -On Ubuntu (14.04+), the apt-get command to install all pre-requisites is: - - $ apt-get install python3-dev python3-pyqt5 pyqt5-dev-tools python3-venv - -### OS X and 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 - -### Setting up the virtual environment - -*This is done automatically by the bootstrap script. This is a reference in case you need to do it -manually.* - -Use Python's built-in `pyvenv` to create a virtual environment in which we're going to install our. -Python-related dependencies. In that environment, we then install our requirements with pip. - -For Linux (`--system-site-packages` is to be able to import PyQt): - - $ pyvenv --system-site-packages env - $ source env/bin/activate - $ pip install -r requirements.txt - -For OS X: - - $ pyvenv env - $ source env/bin/activate - $ pip install -r requirements-osx.txt - -### Actual building and running - -With your virtualenv activated, you can build and run dupeGuru with these commands: - - $ python build.py - $ python run.py - -You can also package dupeGuru into an installable package with: - - $ python package.py - - ### Generate Ubuntu packages $ bash -c "pyvenv --system-site-packages env && source env/bin/activate && pip install -r requirements.txt && python3 build.py --clean && python3 package.py" @@ -158,13 +94,11 @@ You can also run automated tests without Tox. Extra requirements for running tes `requirements-extra.txt`. So, you can do `pip install -r requirements-extra.txt` inside your virtualenv and then `py.test core hscommon` -[dupeguru]: http://www.hardcoded.net/dupeguru/ +[dupeguru]: https://www.hardcoded.net/dupeguru/ [cross-toolkit]: http://www.hardcoded.net/articles/cross-toolkit-software [contrib-issue]: https://github.com/hsoft/dupeguru/issues/300 [nowindows]: https://www.hardcoded.net/archive2015#2015-11-01 [documentation]: http://www.hardcoded.net/dupeguru/help/en/ [python]: http://www.python.org/ [pyqt]: http://www.riverbankcomputing.com -[pyenv]: https://github.com/yyuu/pyenv -[tox]: https://tox.readthedocs.org/en/latest/ - +[tox]: https://tox.readthedocs.org/en/latest/ \ No newline at end of file diff --git a/bootstrap.sh b/bootstrap.sh deleted file mode 100755 index d32800e7..00000000 --- a/bootstrap.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -PYTHON=python3 -ret=`$PYTHON -c "import sys; print(int(sys.version_info[:2] >= (3, 4)))"` -if [ $ret -ne 1 ]; then - echo "Python 3.4+ required. Aborting." - exit 1 -fi - - -if [ -d ".git" ]; then - git submodule init - git submodule update -fi - -if [ ! -d "env" ]; then - echo "No virtualenv. Creating one" - # We need a "system-site-packages" env to have PyQt, but we also need to ensure a local pip - # install. To achieve our latter goal, we start with a normal venv, which we later upgrade to - # a system-site-packages once pip is installed. - if ! $PYTHON -m venv env ; then - echo "Creation of our virtualenv failed. If you're on Ubuntu, you probably need python3-venv." - exit 1 - fi - if [ "$(uname)" != "Darwin" ]; then - $PYTHON -m venv env --upgrade --system-site-packages - fi -fi - -source env/bin/activate - -echo "Installing pip requirements" -if [ "$(uname)" == "Darwin" ]; then - ./env/bin/pip install -r requirements-osx.txt -else - ./env/bin/python -c "import PyQt5" >/dev/null 2>&1 || { echo >&2 "PyQt 5.4+ required. Install it and try again. Aborting"; exit 1; } - ./env/bin/pip install -r requirements.txt -fi - -echo "Bootstrapping complete! You can now configure, build and run dupeGuru with:" -echo ". env/bin/activate && python build.py && python run.py" diff --git a/cocoa/AppDelegate.h b/cocoa/AppDelegate.h deleted file mode 100644 index 63a6c1fd..00000000 --- a/cocoa/AppDelegate.h +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#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 -{ - 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 diff --git a/cocoa/AppDelegate.m b/cocoa/AppDelegate.m deleted file mode 100644 index 59fe9334..00000000 --- a/cocoa/AppDelegate.m +++ /dev/null @@ -1,394 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "AppDelegate.h" -#import "ProgressController.h" -#import "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 diff --git a/cocoa/Consts.h b/cocoa/Consts.h deleted file mode 100644 index ff0c54fc..00000000 --- a/cocoa/Consts.h +++ /dev/null @@ -1,24 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#define JobStarted @"JobStarted" -#define JobInProgress @"JobInProgress" -#define TableFontSize @"TableFontSize" - -#define jobLoad @"job_load" -#define jobScan @"job_scan" -#define jobCopy @"job_copy" -#define jobMove @"job_move" -#define jobDelete @"job_delete" - -#define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType" -#define ImageLoadedNotification @"ImageLoadedNotification" - -#define AppModeStandard 0 -#define AppModeMusic 1 -#define AppModePicture 2 \ No newline at end of file diff --git a/cocoa/DeletionOptions.h b/cocoa/DeletionOptions.h deleted file mode 100644 index 05f2a283..00000000 --- a/cocoa/DeletionOptions.h +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#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 \ No newline at end of file diff --git a/cocoa/DeletionOptions.m b/cocoa/DeletionOptions.m deleted file mode 100644 index 2e12f67f..00000000 --- a/cocoa/DeletionOptions.m +++ /dev/null @@ -1,72 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "DeletionOptions.h" -#import "DeletionOptions_UI.h" -#import "HSPyUtil.h" - -@implementation DeletionOptions - -@synthesize messageTextField; -@synthesize linkButton; -@synthesize linkTypeRadio; -@synthesize directButton; - -- (id)initWithPyRef:(PyObject *)aPyRef -{ - self = [super initWithWindow:nil]; - model = [[PyDeletionOptions alloc] initWithModel:aPyRef]; - [self setWindow:createDeletionOptions_UI(self)]; - [model bindCallback:createCallback(@"DeletionOptionsView", self)]; - return self; -} - -- (void)dealloc -{ - [model release]; - [super dealloc]; -} - -- (void)updateOptions -{ - [model setLinkDeleted:[linkButton state] == NSOnState]; - [model setUseHardlinks:[linkTypeRadio selectedColumn] == 1]; - [model setDirect:[directButton state] == NSOnState]; -} - -- (void)proceed -{ - [NSApp stopModalWithCode:NSOKButton]; -} - -- (void)cancel -{ - [NSApp stopModalWithCode:NSCancelButton]; -} - -/* model --> view */ -- (void)updateMsg:(NSString *)msg -{ - [messageTextField setStringValue:msg]; -} - -- (BOOL)show -{ - [linkButton setState:NSOffState]; - [directButton setState:NSOffState]; - [linkTypeRadio selectCellAtRow:0 column:0]; - NSInteger r = [NSApp runModalForWindow:[self window]]; - [[self window] close]; - return r == NSOKButton; -} - -- (void)setHardlinkOptionEnabled:(BOOL)enabled -{ - [linkTypeRadio setEnabled:enabled]; -} -@end \ No newline at end of file diff --git a/cocoa/DetailsPanel.h b/cocoa/DetailsPanel.h deleted file mode 100644 index 1c11f728..00000000 --- a/cocoa/DetailsPanel.h +++ /dev/null @@ -1,31 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#import -#import "PyDetailsPanel.h" - -@interface DetailsPanel : NSWindowController -{ - 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 \ No newline at end of file diff --git a/cocoa/DetailsPanel.m b/cocoa/DetailsPanel.m deleted file mode 100644 index 2efc7796..00000000 --- a/cocoa/DetailsPanel.m +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "DetailsPanel.h" -#import "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 diff --git a/cocoa/DetailsPanelPicture.h b/cocoa/DetailsPanelPicture.h deleted file mode 100644 index ff6b70d7..00000000 --- a/cocoa/DetailsPanelPicture.h +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#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 \ No newline at end of file diff --git a/cocoa/DetailsPanelPicture.m b/cocoa/DetailsPanelPicture.m deleted file mode 100644 index c8287a6a..00000000 --- a/cocoa/DetailsPanelPicture.m +++ /dev/null @@ -1,96 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "Utils.h" -#import "NSNotificationAdditions.h" -#import "NSImageAdditions.h" -#import "PyDupeGuru.h" -#import "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 diff --git a/cocoa/DirectoryOutline.h b/cocoa/DirectoryOutline.h deleted file mode 100644 index 9132b824..00000000 --- a/cocoa/DirectoryOutline.h +++ /dev/null @@ -1,21 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#import -#import "HSOutline.h" -#import "PyDirectoryOutline.h" - -#define DGAddedFoldersNotification @"DGAddedFoldersNotification" - -@interface DirectoryOutline : HSOutline {} -- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView; -- (PyDirectoryOutline *)model; - -- (void)selectAll; -@end; \ No newline at end of file diff --git a/cocoa/DirectoryOutline.m b/cocoa/DirectoryOutline.m deleted file mode 100644 index aa633577..00000000 --- a/cocoa/DirectoryOutline.m +++ /dev/null @@ -1,87 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "DirectoryOutline.h" - -@implementation DirectoryOutline -- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView -{ - self = [super initWithPyRef:aPyRef wrapperClass:[PyDirectoryOutline class] - callbackClassName:@"DirectoryOutlineView" view:aOutlineView]; - [[self view] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]]; - return self; -} - -- (PyDirectoryOutline *)model -{ - return (PyDirectoryOutline *)model; -} - -/* Public */ -- (void)selectAll -{ - [[self model] selectAll]; -} - -/* Delegate */ -- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index -{ - NSPasteboard *pboard; - NSDragOperation sourceDragMask; - sourceDragMask = [info draggingSourceOperationMask]; - pboard = [info draggingPasteboard]; - if ([[pboard types] containsObject:NSFilenamesPboardType]) { - if (sourceDragMask & NSDragOperationLink) - return NSDragOperationLink; - } - return NSDragOperationNone; -} - -- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index -{ - NSPasteboard *pboard; - NSDragOperation sourceDragMask; - sourceDragMask = [info draggingSourceOperationMask]; - pboard = [info draggingPasteboard]; - if ([[pboard types] containsObject:NSFilenamesPboardType]) { - NSArray *foldernames = [pboard propertyListForType:NSFilenamesPboardType]; - if (!(sourceDragMask & NSDragOperationLink)) - return NO; - for (NSString *foldername in foldernames) { - [[self model] addDirectory:foldername]; - } - NSDictionary *userInfo = [NSDictionary dictionaryWithObject:foldernames forKey:@"foldernames"]; - [[NSNotificationCenter defaultCenter] postNotificationName:DGAddedFoldersNotification - object:self userInfo:userInfo]; - } - return YES; -} - -- (void)outlineView:(NSOutlineView *)aOutlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item -{ - if ([cell isKindOfClass:[NSTextFieldCell class]]) { - NSTextFieldCell *textCell = cell; - NSIndexPath *path = item; - BOOL selected = [path isEqualTo:[[self view] selectedPath]]; - if (selected) { - [textCell setTextColor:[NSColor blackColor]]; - return; - } - NSInteger state = [self intProperty:@"state" valueAtPath:path]; - if (state == 1) { - [textCell setTextColor:[NSColor blueColor]]; - } - else if (state == 2) { - [textCell setTextColor:[NSColor redColor]]; - } - else { - [textCell setTextColor:[NSColor blackColor]]; - } - } -} -@end \ No newline at end of file diff --git a/cocoa/DirectoryPanel.h b/cocoa/DirectoryPanel.h deleted file mode 100644 index b475f2a1..00000000 --- a/cocoa/DirectoryPanel.h +++ /dev/null @@ -1,57 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#import "HSOutlineView.h" -#import "HSRecentFiles.h" -#import "DirectoryOutline.h" -#import "PyDupeGuru.h" - -@class AppDelegate; - -@interface DirectoryPanel : NSWindowController -{ - 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 diff --git a/cocoa/DirectoryPanel.m b/cocoa/DirectoryPanel.m deleted file mode 100644 index 731b11b5..00000000 --- a/cocoa/DirectoryPanel.m +++ /dev/null @@ -1,256 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "DirectoryPanel.h" -#import "DirectoryPanel_UI.h" -#import "Dialogs.h" -#import "Utils.h" -#import "AppDelegate.h" -#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 diff --git a/cocoa/IgnoreListDialog.h b/cocoa/IgnoreListDialog.h deleted file mode 100644 index a392ec91..00000000 --- a/cocoa/IgnoreListDialog.h +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#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 \ No newline at end of file diff --git a/cocoa/IgnoreListDialog.m b/cocoa/IgnoreListDialog.m deleted file mode 100644 index 3967b501..00000000 --- a/cocoa/IgnoreListDialog.m +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "IgnoreListDialog.h" -#import "IgnoreListDialog_UI.h" -#import "HSPyUtil.h" - -@implementation IgnoreListDialog - -@synthesize model; -@synthesize ignoreListTableView; - -- (id)initWithPyRef:(PyObject *)aPyRef -{ - self = [super initWithWindow:nil]; - self.model = [[[PyIgnoreListDialog alloc] initWithModel:aPyRef] autorelease]; - [self.model bindCallback:createCallback(@"IgnoreListDialogView", self)]; - [self setWindow:createIgnoreListDialog_UI(self)]; - ignoreListTable = [[HSTable alloc] initWithPyRef:[model ignoreListTable] tableView:ignoreListTableView]; - [self initializeColumns]; - return self; -} - -- (void)dealloc -{ - [ignoreListTable release]; - [super dealloc]; -} - -- (void)initializeColumns -{ - HSColumnDef defs[] = { - {@"path1", 240, 40, 0, NO, nil}, - {@"path2", 240, 40, 0, NO, nil}, - nil - }; - [[ignoreListTable columns] initializeColumns:defs]; - [[ignoreListTable columns] setColumnsAsReadOnly]; -} - -/* model --> view */ -- (void)show -{ - [self showWindow:self]; -} -@end \ No newline at end of file diff --git a/cocoa/InfoTemplate.plist b/cocoa/InfoTemplate.plist deleted file mode 100644 index d9243aa8..00000000 --- a/cocoa/InfoTemplate.plist +++ /dev/null @@ -1,38 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - dupeGuru - CFBundleHelpBookFolder - dupeguru_help - CFBundleHelpBookName - dupeGuru Help - CFBundleIconFile - dupeguru - CFBundleIdentifier - com.hardcoded-software.dupeguru - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - dupeGuru - CFBundlePackageType - APPL - CFBundleSignature - hsft - CFBundleShortVersionString - {version} - CFBundleVersion - {version} - NSPrincipalClass - NSApplication - NSHumanReadableCopyright - © Hardcoded Software, 2016 - SUFeedURL - https://www.hardcoded.net/updates/dupeguru.appcast - SUPublicDSAKeyFile - dsa_pub.pem - - diff --git a/cocoa/PrioritizeDialog.h b/cocoa/PrioritizeDialog.h deleted file mode 100644 index 35f030c1..00000000 --- a/cocoa/PrioritizeDialog.h +++ /dev/null @@ -1,37 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#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; \ No newline at end of file diff --git a/cocoa/PrioritizeDialog.m b/cocoa/PrioritizeDialog.m deleted file mode 100644 index 97419513..00000000 --- a/cocoa/PrioritizeDialog.m +++ /dev/null @@ -1,56 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "PrioritizeDialog.h" -#import "PrioritizeDialog_UI.h" -#import "HSPyUtil.h" - -@implementation PrioritizeDialog - -@synthesize categoryPopUpView; -@synthesize criteriaTableView; -@synthesize prioritizationTableView; - -- (id)initWithApp:(PyDupeGuru *)aApp -{ - self = [super initWithWindowNibName:@"PrioritizeDialog"]; - model = [[PyPrioritizeDialog alloc] initWithApp:[aApp pyRef]]; - [self setWindow:createPrioritizeDialog_UI(self)]; - categoryPopUp = [[HSPopUpList alloc] initWithPyRef:[[self model] categoryList] popupView:categoryPopUpView]; - criteriaList = [[HSSelectableList alloc] initWithPyRef:[[self model] criteriaList] tableView:criteriaTableView]; - prioritizationList = [[PrioritizeList alloc] initWithPyRef:[[self model] prioritizationList] tableView:prioritizationTableView]; - [model bindCallback:createCallback(@"PrioritizeDialogView", self)]; - return self; -} - -- (void)dealloc -{ - [categoryPopUp release]; - [criteriaList release]; - [prioritizationList release]; - [model release]; - [super dealloc]; -} - -- (PyPrioritizeDialog *)model -{ - return (PyPrioritizeDialog *)model; -} - -- (void)ok -{ - [NSApp stopModal]; - [self close]; -} - -- (void)cancel -{ - [NSApp abortModal]; - [self close]; -} -@end \ No newline at end of file diff --git a/cocoa/PrioritizeList.h b/cocoa/PrioritizeList.h deleted file mode 100644 index a7b3f448..00000000 --- a/cocoa/PrioritizeList.h +++ /dev/null @@ -1,16 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#import "HSSelectableList.h" -#import "PyPrioritizeList.h" - -@interface PrioritizeList : HSSelectableList {} -- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView; -- (PyPrioritizeList *)model; -@end \ No newline at end of file diff --git a/cocoa/PrioritizeList.m b/cocoa/PrioritizeList.m deleted file mode 100644 index 098e073c..00000000 --- a/cocoa/PrioritizeList.m +++ /dev/null @@ -1,58 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "PrioritizeList.h" -#import "Utils.h" -#import "Consts.h" - -@implementation PrioritizeList -- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView -{ - self = [super initWithPyRef:aPyRef wrapperClass:[PyPrioritizeList class] - callbackClassName:@"PrioritizeListView" view:aTableView]; - return self; -} - -- (PyPrioritizeList *)model -{ - return (PyPrioritizeList *)model; -} - -- (void)setView:(NSTableView *)aTableView -{ - [super setView:aTableView]; - [[self view] registerForDraggedTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType]]; -} - -- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard -{ - NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes]; - [pboard declareTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType] owner:self]; - [pboard setData:data forType:DGPrioritizeIndexPasteboardType]; - return YES; -} - -- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id )info proposedRow:(NSInteger)row - proposedDropOperation:(NSTableViewDropOperation)op -{ - if (op == NSTableViewDropAbove) { - return NSDragOperationMove; - } - return NSDragOperationNone; -} - -- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id )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 \ No newline at end of file diff --git a/cocoa/ProblemDialog.h b/cocoa/ProblemDialog.h deleted file mode 100644 index 309f2789..00000000 --- a/cocoa/ProblemDialog.h +++ /dev/null @@ -1,26 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#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 \ No newline at end of file diff --git a/cocoa/ProblemDialog.m b/cocoa/ProblemDialog.m deleted file mode 100644 index 2619aa72..00000000 --- a/cocoa/ProblemDialog.m +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "ProblemDialog.h" -#import "ProblemDialog_UI.h" -#import "Utils.h" - -@implementation ProblemDialog - -@synthesize model; -@synthesize problemTableView; - -- (id)initWithPyRef:(PyObject *)aPyRef -{ - self = [super initWithWindow:nil]; - self.model = [[PyProblemDialog alloc] initWithModel:aPyRef]; - [self setWindow:createProblemDialog_UI(self)]; - problemTable = [[HSTable alloc] initWithPyRef:[self.model problemTable] tableView:problemTableView]; - [self initializeColumns]; - return self; -} - -- (void)dealloc -{ - [problemTable release]; - [super dealloc]; -} - -- (void)initializeColumns -{ - HSColumnDef defs[] = { - {@"path", 202, 40, 0, NO, nil}, - {@"msg", 228, 40, 0, NO, nil}, - nil - }; - [[problemTable columns] initializeColumns:defs]; - [[problemTable columns] setColumnsAsReadOnly]; -} -@end \ No newline at end of file diff --git a/cocoa/ResultTable.h b/cocoa/ResultTable.h deleted file mode 100644 index 713f9dc1..00000000 --- a/cocoa/ResultTable.h +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#import -#import "HSTable.h" -#import "PyResultTable.h" - -@interface ResultTable : HSTable -{ -} -- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView; -- (PyResultTable *)model; -- (BOOL)powerMarkerMode; -- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode; -- (BOOL)deltaValuesMode; -- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode; -@end; \ No newline at end of file diff --git a/cocoa/ResultTable.m b/cocoa/ResultTable.m deleted file mode 100644 index 82d20f6f..00000000 --- a/cocoa/ResultTable.m +++ /dev/null @@ -1,180 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "ResultTable.h" -#import "Dialogs.h" -#import "Utils.h" -#import "HSQuicklook.h" - -@interface HSTable (private) -- (void)setPySelection; -- (void)setViewSelection; -@end - -@implementation ResultTable -- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTableView *)aTableView -{ - self = [super initWithPyRef:aPyRef wrapperClass:[PyResultTable class] callbackClassName:@"ResultTableView" view:aTableView]; - return self; -} - -- (PyResultTable *)model -{ - return (PyResultTable *)model; -} - -/* Private */ -- (void)updateQuicklookIfNeeded -{ - if ([[QLPreviewPanel sharedPreviewPanel] dataSource] == self) { - [[QLPreviewPanel sharedPreviewPanel] reloadData]; - } -} - -- (void)setPySelection -{ - [super setPySelection]; - [self updateQuicklookIfNeeded]; -} - -- (void)setViewSelection -{ - [super setViewSelection]; - [self updateQuicklookIfNeeded]; -} - -/* Public */ -- (BOOL)powerMarkerMode -{ - return [[self model] powerMarkerMode]; -} - -- (void)setPowerMarkerMode:(BOOL)aPowerMarkerMode -{ - [[self model] setPowerMarkerMode:aPowerMarkerMode]; -} - -- (BOOL)deltaValuesMode -{ - return [[self model] deltaValuesMode]; -} - -- (void)setDeltaValuesMode:(BOOL)aDeltaValuesMode -{ - [[self model] setDeltaValuesMode:aDeltaValuesMode]; -} - -/* Datasource */ -- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row -{ - NSString *identifier = [column identifier]; - if ([identifier isEqual:@"marked"]) { - return [[self model] valueForColumn:@"marked" row:row]; - } - return [[self model] valueForRow:row column:identifier]; -} - -- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)object forTableColumn:(NSTableColumn *)column row:(NSInteger)row -{ - NSString *identifier = [column identifier]; - if ([identifier isEqual:@"marked"]) { - [[self model] setValue:object forColumn:identifier row:row]; - } - else if ([identifier isEqual:@"name"]) { - NSString *oldName = [[self model] valueForRow:row column:identifier]; - NSString *newName = object; - if (![newName isEqual:oldName]) { - BOOL renamed = [[self model] renameSelected:newName]; - if (!renamed) { - [Dialogs showMessage:[NSString stringWithFormat:NSLocalizedString(@"The name '%@' already exists.", @""), newName]]; - } - else { - [[self view] setNeedsDisplay:YES]; - } - } - } -} - -/* Delegate */ -- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)tableColumn -{ - if ([[[self view] sortDescriptors] count] < 1) - return; - NSSortDescriptor *sd = [[[self view] sortDescriptors] objectAtIndex:0]; - [[self model] sortBy:[sd key] ascending:[sd ascending]]; -} - -- (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)column row:(NSInteger)row -{ - BOOL isSelected = [[self view] isRowSelected:row]; - BOOL isMarkable = n2b([[self model] valueForColumn:@"markable" row:row]); - if ([[column identifier] isEqual:@"marked"]) { - [cell setEnabled:isMarkable]; - // Low-tech solution, for indentation, but it works... - NSCellImagePosition pos = isMarkable ? NSImageRight : NSImageLeft; - [cell setImagePosition:pos]; - } - if ([cell isKindOfClass:[NSTextFieldCell class]]) { - NSColor *color = [NSColor textColor]; - if (isSelected) { - color = [NSColor selectedTextColor]; - } - else if (isMarkable) { - if ([[self model] isDeltaAtRow:row column:[column identifier]]) { - color = [NSColor orangeColor]; - } - } - else { - color = [NSColor blueColor]; - } - [(NSTextFieldCell *)cell setTextColor:color]; - } -} - -- (BOOL)tableViewHadDeletePressed:(NSTableView *)tableView -{ - [[self model] removeSelected]; - return YES; -} - -- (BOOL)tableViewHadSpacePressed:(NSTableView *)tableView -{ - [[self model] markSelected]; - return YES; -} - -/* Quicklook */ -- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel -{ - return [[[self model] selectedRows] count]; -} - -- (id )previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index -{ - NSArray *selectedRows = [[self model] selectedRows]; - NSInteger absIndex = n2i([selectedRows objectAtIndex:index]); - NSString *path = [[self model] pathAtIndex:absIndex]; - return [[HSQLPreviewItem alloc] initWithUrl:[NSURL fileURLWithPath:path] title:path]; -} - -- (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event -{ - // redirect all key down events to the table view - if ([event type] == NSKeyDown) { - [[self view] keyDown:event]; - return YES; - } - return NO; -} - -/* Python --> Cocoa */ -- (void)invalidateMarkings -{ - [[self view] setNeedsDisplay:YES]; -} -@end \ No newline at end of file diff --git a/cocoa/ResultWindow.h b/cocoa/ResultWindow.h deleted file mode 100644 index 509b042b..00000000 --- a/cocoa/ResultWindow.h +++ /dev/null @@ -1,76 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#import -#import "StatsLabel.h" -#import "ResultTable.h" -#import "HSTableView.h" -#import "PyDupeGuru.h" - -@class AppDelegate; - -@interface ResultWindow : NSWindowController -{ -@protected - NSSegmentedControl *optionsSwitch; - NSToolbarItem *optionsToolbarItem; - HSTableView *matches; - NSTextField *stats; - NSSearchField *filterField; - - AppDelegate *app; - PyDupeGuru *model; - ResultTable *table; - StatsLabel *statsLabel; - QLPreviewPanel* previewPanel; -} - -@property (readwrite, retain) NSSegmentedControl *optionsSwitch; -@property (readwrite, retain) NSToolbarItem *optionsToolbarItem; -@property (readwrite, retain) HSTableView *matches; -@property (readwrite, retain) NSTextField *stats; -@property (readwrite, retain) NSSearchField *filterField; - -- (id)initWithParentApp:(AppDelegate *)app; - -/* Helpers */ -- (void)fillColumnsMenu; -- (void)updateOptionSegments; -- (void)adjustUIToLocalization; -- (void)initResultColumns:(ResultTable *)aTable; - -/* Actions */ -- (void)changeOptions; -- (void)copyMarked; -- (void)trashMarked; -- (void)filter; -- (void)focusOnFilterField; -- (void)ignoreSelected; -- (void)invokeCustomCommand; -- (void)markAll; -- (void)markInvert; -- (void)markNone; -- (void)markSelected; -- (void)moveMarked; -- (void)openClicked; -- (void)openSelected; -- (void)removeMarked; -- (void)removeSelected; -- (void)renameSelected; -- (void)reprioritizeResults; -- (void)resetColumnsToDefault; -- (void)revealSelected; -- (void)saveResults; -- (void)switchSelected; -- (void)toggleColumn:(id)sender; -- (void)toggleDelta; -- (void)toggleDetailsPanel; -- (void)togglePowerMarker; -- (void)toggleQuicklookPanel; -@end diff --git a/cocoa/ResultWindow.m b/cocoa/ResultWindow.m deleted file mode 100644 index f0cc860e..00000000 --- a/cocoa/ResultWindow.m +++ /dev/null @@ -1,406 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "ResultWindow.h" -#import "ResultWindow_UI.h" -#import "Dialogs.h" -#import "ProgressController.h" -#import "Utils.h" -#import "AppDelegate.h" -#import "Consts.h" -#import "PrioritizeDialog.h" - -@implementation ResultWindow - -@synthesize optionsSwitch; -@synthesize optionsToolbarItem; -@synthesize matches; -@synthesize stats; -@synthesize filterField; - -- (id)initWithParentApp:(AppDelegate *)aApp; -{ - self = [super initWithWindow:nil]; - app = aApp; - model = [app model]; - [self setWindow:createResultWindow_UI(self)]; - [[self window] setTitle:fmt(NSLocalizedString(@"%@ Results", @""), [model appName])]; - /* Put a cute iTunes-like bottom bar */ - [[self window] setContentBorderThickness:28 forEdge:NSMinYEdge]; - table = [[ResultTable alloc] initWithPyRef:[model resultTable] view:matches]; - statsLabel = [[StatsLabel alloc] initWithPyRef:[model statsLabel] view:stats]; - [self initResultColumns:table]; - [[table columns] setColumnsAsReadOnly]; - [self fillColumnsMenu]; - [matches setTarget:self]; - [matches setDoubleAction:@selector(openClicked)]; - [self adjustUIToLocalization]; - return self; -} - -- (void)dealloc -{ - [table release]; - [statsLabel release]; - [super dealloc]; -} - -/* Helpers */ -- (void)fillColumnsMenu -{ - [[app columnsMenu] removeAllItems]; - NSArray *menuItems = [[[table columns] model] menuItems]; - for (NSInteger i=0; i < [menuItems count]; i++) { - NSArray *pair = [menuItems objectAtIndex:i]; - NSString *display = [pair objectAtIndex:0]; - BOOL marked = n2b([pair objectAtIndex:1]); - NSMenuItem *mi = [[app columnsMenu] addItemWithTitle:display action:@selector(toggleColumn:) keyEquivalent:@""]; - [mi setTarget:self]; - [mi setState:marked ? NSOnState : NSOffState]; - [mi setTag:i]; - } - [[app columnsMenu] addItem:[NSMenuItem separatorItem]]; - NSMenuItem *mi = [[app columnsMenu] addItemWithTitle:NSLocalizedString(@"Reset to Default", @"") - action:@selector(resetColumnsToDefault) keyEquivalent:@""]; - [mi setTarget:self]; -} - -- (void)updateOptionSegments -{ - [optionsSwitch setSelected:[[app detailsPanel] isVisible] forSegment:0]; - [optionsSwitch setSelected:[table powerMarkerMode] forSegment:1]; - [optionsSwitch setSelected:[table deltaValuesMode] forSegment:2]; -} - -- (void)adjustUIToLocalization -{ - NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0]; - NSInteger seg1delta = 0; - NSInteger seg2delta = 0; - if ([lang isEqual:@"ru"]) { - seg2delta = 20; - } - else if ([lang isEqual:@"uk"]) { - seg2delta = 20; - } - else if ([lang isEqual:@"hy"]) { - seg1delta = 20; - } - if (seg1delta || seg2delta) { - [optionsSwitch setWidth:[optionsSwitch widthForSegment:0]+seg1delta forSegment:0]; - [optionsSwitch setWidth:[optionsSwitch widthForSegment:1]+seg2delta forSegment:1]; - NSSize s = [optionsToolbarItem maxSize]; - s.width += seg1delta + seg2delta; - [optionsToolbarItem setMaxSize:s]; - [optionsToolbarItem setMinSize:s]; - } -} - -- (void)initResultColumns:(ResultTable *)aTable -{ - NSInteger appMode = [app getAppMode]; - if (appMode == AppModePicture) { - HSColumnDef defs[] = { - {@"marked", 26, 26, 26, YES, [NSButtonCell class]}, - {@"name", 162, 16, 0, YES, nil}, - {@"folder_path", 142, 16, 0, YES, nil}, - {@"size", 63, 16, 0, YES, nil}, - {@"extension", 40, 16, 0, YES, nil}, - {@"dimensions", 73, 16, 0, YES, nil}, - {@"exif_timestamp", 120, 16, 0, YES, nil}, - {@"mtime", 120, 16, 0, YES, nil}, - {@"percentage", 58, 16, 0, YES, nil}, - {@"dupe_count", 80, 16, 0, YES, nil}, - nil - }; - [[aTable columns] initializeColumns:defs]; - NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"]; - [[c dataCell] setButtonType:NSSwitchButton]; - [[c dataCell] setControlSize:NSSmallControlSize]; - c = [[aTable view] tableColumnWithIdentifier:@"size"]; - [[c dataCell] setAlignment:NSRightTextAlignment]; - } - else if (appMode == AppModeMusic) { - HSColumnDef defs[] = { - {@"marked", 26, 26, 26, YES, [NSButtonCell class]}, - {@"name", 235, 16, 0, YES, nil}, - {@"folder_path", 120, 16, 0, YES, nil}, - {@"size", 63, 16, 0, YES, nil}, - {@"duration", 50, 16, 0, YES, nil}, - {@"bitrate", 50, 16, 0, YES, nil}, - {@"samplerate", 60, 16, 0, YES, nil}, - {@"extension", 40, 16, 0, YES, nil}, - {@"mtime", 120, 16, 0, YES, nil}, - {@"title", 120, 16, 0, YES, nil}, - {@"artist", 120, 16, 0, YES, nil}, - {@"album", 120, 16, 0, YES, nil}, - {@"genre", 80, 16, 0, YES, nil}, - {@"year", 40, 16, 0, YES, nil}, - {@"track", 40, 16, 0, YES, nil}, - {@"comment", 120, 16, 0, YES, nil}, - {@"percentage", 57, 16, 0, YES, nil}, - {@"words", 120, 16, 0, YES, nil}, - {@"dupe_count", 80, 16, 0, YES, nil}, - nil - }; - [[aTable columns] initializeColumns:defs]; - NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"]; - [[c dataCell] setButtonType:NSSwitchButton]; - [[c dataCell] setControlSize:NSSmallControlSize]; - c = [[aTable view] tableColumnWithIdentifier:@"size"]; - [[c dataCell] setAlignment:NSRightTextAlignment]; - c = [[aTable view] tableColumnWithIdentifier:@"duration"]; - [[c dataCell] setAlignment:NSRightTextAlignment]; - c = [[aTable view] tableColumnWithIdentifier:@"bitrate"]; - [[c dataCell] setAlignment:NSRightTextAlignment]; - } - else { - HSColumnDef defs[] = { - {@"marked", 26, 26, 26, YES, [NSButtonCell class]}, - {@"name", 195, 16, 0, YES, nil}, - {@"folder_path", 183, 16, 0, YES, nil}, - {@"size", 63, 16, 0, YES, nil}, - {@"extension", 40, 16, 0, YES, nil}, - {@"mtime", 120, 16, 0, YES, nil}, - {@"percentage", 60, 16, 0, YES, nil}, - {@"words", 120, 16, 0, YES, nil}, - {@"dupe_count", 80, 16, 0, YES, nil}, - nil - }; - [[aTable columns] initializeColumns:defs]; - NSTableColumn *c = [[aTable view] tableColumnWithIdentifier:@"marked"]; - [[c dataCell] setButtonType:NSSwitchButton]; - [[c dataCell] setControlSize:NSSmallControlSize]; - c = [[aTable view] tableColumnWithIdentifier:@"size"]; - [[c dataCell] setAlignment:NSRightTextAlignment]; - } - [[aTable columns] restoreColumns]; -} - -/* Actions */ -- (void)changeOptions -{ - NSInteger seg = [optionsSwitch selectedSegment]; - if (seg == 0) { - [self toggleDetailsPanel]; - } - else if (seg == 1) { - [self togglePowerMarker]; - } - else if (seg == 2) { - [self toggleDelta]; - } -} - -- (void)copyMarked -{ - NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - [model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])]; - [model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])]; - [model copyMarked]; -} - -- (void)trashMarked -{ - NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - [model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])]; - [model deleteMarked]; -} - -- (void)filter -{ - NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - [model setEscapeFilterRegexp:!n2b([ud objectForKey:@"useRegexpFilter"])]; - [model applyFilter:[filterField stringValue]]; -} - -- (void)focusOnFilterField -{ - [[self window] makeFirstResponder:filterField]; -} - -- (void)ignoreSelected -{ - [model addSelectedToIgnoreList]; -} - -- (void)invokeCustomCommand -{ - [model invokeCustomCommand]; -} - -- (void)markAll -{ - [model markAll]; -} - -- (void)markInvert -{ - [model markInvert]; -} - -- (void)markNone -{ - [model markNone]; -} - -- (void)markSelected -{ - [model toggleSelectedMark]; -} - -- (void)moveMarked -{ - NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; - [model setRemoveEmptyFolders:n2b([ud objectForKey:@"removeEmptyFolders"])]; - [model setCopyMoveDestType:n2i([ud objectForKey:@"recreatePathType"])]; - [model moveMarked]; -} - -- (void)openClicked -{ - if ([matches clickedRow] < 0) { - return; - } - [matches selectRowIndexes:[NSIndexSet indexSetWithIndex:[matches clickedRow]] byExtendingSelection:NO]; - [model openSelected]; -} - -- (void)openSelected -{ - [model openSelected]; -} - -- (void)removeMarked -{ - [model removeMarked]; -} - -- (void)removeSelected -{ - [model removeSelected]; -} - -- (void)renameSelected -{ - NSInteger col = [matches columnWithIdentifier:@"name"]; - NSInteger row = [matches selectedRow]; - [matches editColumn:col row:row withEvent:[NSApp currentEvent] select:YES]; -} - -- (void)reprioritizeResults -{ - PrioritizeDialog *dlg = [[PrioritizeDialog alloc] initWithApp:model]; - NSInteger result = [NSApp runModalForWindow:[dlg window]]; - if (result == NSRunStoppedResponse) { - [[dlg model] performReprioritization]; - } - [dlg release]; - [[self window] makeKeyAndOrderFront:nil]; -} - -- (void)resetColumnsToDefault -{ - [[[table columns] model] resetToDefaults]; - [self fillColumnsMenu]; -} - -- (void)revealSelected -{ - [model revealSelected]; -} - -- (void)saveResults -{ - NSSavePanel *sp = [NSSavePanel savePanel]; - [sp setCanCreateDirectories:YES]; - [sp setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]]; - [sp setTitle:NSLocalizedString(@"Select a file to save your results to", @"")]; - if ([sp runModal] == NSOKButton) { - [model saveResultsAs:[[sp URL] path]]; - [[app recentResults] addFile:[[sp URL] path]]; - } -} - -- (void)switchSelected -{ - [model makeSelectedReference]; -} - -- (void)toggleColumn:(id)sender -{ - NSMenuItem *mi = sender; - BOOL checked = [[[table columns] model] toggleMenuItem:[mi tag]]; - [mi setState:checked ? NSOnState : NSOffState]; -} - -- (void)toggleDetailsPanel -{ - [[app detailsPanel] toggleVisibility]; - [self updateOptionSegments]; -} - -- (void)toggleDelta -{ - [table setDeltaValuesMode:![table deltaValuesMode]]; - [self updateOptionSegments]; -} - -- (void)togglePowerMarker -{ - [table setPowerMarkerMode:![table powerMarkerMode]]; - [self updateOptionSegments]; -} - -- (void)toggleQuicklookPanel -{ - if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) { - [[QLPreviewPanel sharedPreviewPanel] orderOut:nil]; - } - else { - [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil]; - } -} - -/* Quicklook */ -- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel; -{ - return YES; -} - -- (void)beginPreviewPanelControl:(QLPreviewPanel *)panel -{ - // This document is now responsible of the preview panel - // It is allowed to set the delegate, data source and refresh panel. - previewPanel = [panel retain]; - panel.delegate = table; - panel.dataSource = table; -} - -- (void)endPreviewPanelControl:(QLPreviewPanel *)panel -{ - // This document loses its responsisibility on the preview panel - // Until the next call to -beginPreviewPanelControl: it must not - // change the panel's delegate, data source or refresh it. - [previewPanel release]; - previewPanel = nil; -} - -- (BOOL)validateToolbarItem:(NSToolbarItem *)theItem -{ - return ![[ProgressController mainProgressController] isShown]; -} - -- (BOOL)validateMenuItem:(NSMenuItem *)item -{ - if ([item action] == @selector(markAll)) { - [item setTitle:NSLocalizedString(@"Mark All", @"")]; - } - return ![[ProgressController mainProgressController] isShown]; -} -@end diff --git a/cocoa/StatsLabel.h b/cocoa/StatsLabel.h deleted file mode 100644 index 9641d55b..00000000 --- a/cocoa/StatsLabel.h +++ /dev/null @@ -1,17 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#import "HSGUIController.h" -#import "PyStatsLabel.h" - -@interface StatsLabel : HSGUIController {} -- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aLabelView; -- (PyStatsLabel *)model; -- (NSTextField *)labelView; -@end \ No newline at end of file diff --git a/cocoa/StatsLabel.m b/cocoa/StatsLabel.m deleted file mode 100644 index ca14533c..00000000 --- a/cocoa/StatsLabel.m +++ /dev/null @@ -1,34 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import "StatsLabel.h" -#import "Utils.h" - -@implementation StatsLabel -- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aLabelView -{ - return [super initWithPyRef:aPyRef wrapperClass:[PyStatsLabel class] - callbackClassName:@"StatsLabelView" view:aLabelView]; -} - -- (PyStatsLabel *)model -{ - return (PyStatsLabel *)model; -} - -- (NSTextField *)labelView -{ - return (NSTextField *)view; -} - -/* Python --> Cocoa */ -- (void)refresh -{ - [[self labelView] setStringValue:[[self model] display]]; -} -@end diff --git a/cocoa/dg_cocoa.py b/cocoa/dg_cocoa.py deleted file mode 100644 index 312d9e2c..00000000 --- a/cocoa/dg_cocoa.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2015 Hardcoded Software (http://www.hardcoded.net) -# -# This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -# which should be included with this package. The terms are also available at -# http://www.gnu.org/licenses/gpl-3.0.html - -from hscommon.trans import install_gettext_trans_under_cocoa -install_gettext_trans_under_cocoa() - -from cocoa.inter import PySelectableList, PyColumns, PyTable - -from inter.all import * -from inter.app import PyDupeGuru - -# When built under virtualenv, the dependency collector misses this module, so we have to force it -# to see the module. -import distutils.sysconfig \ No newline at end of file diff --git a/cocoa/dsa_pub.pem b/cocoa/dsa_pub.pem deleted file mode 100644 index 18e81668..00000000 --- a/cocoa/dsa_pub.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN PUBLIC KEY----- -MIIDOjCCAi0GByqGSM44BAEwggIgAoIBAQDSurIL+HKbw+jsppG6tp3+WOcA4W71 -nhwR/DD2Se076AtCXJcssAhuCDUm+AVkQ3l34D++aYWtLR575rCrwU4lZXfQe+b9 -plHK02oOuqAY8lO5y02xoHEh7XeGunZ0u8wOVZw8MI999vIJ8rtCdvIF3r26wkjx -9sieSxVpzJHDV5JHVdK3ObkXp/ts99dOD5B3CWGS8UiroMgS0FmRl7uPuADRRn2G -srHTBYMwJvq8HFzQmDxcLldGQMAKvRKchtH+nH6ci1unSnpDUyrsCd+7qv1cSTse -qc4OgXBDQ94MfVEh6Bs0S9stYfJf8cp6iV18J0sqMb9rbP4qC56iBsXfAhUAj6tx -gwima7VaNI4YiC69jpLod3MCggEAYx+/mbU8P/xGooV9MgA3nI2v2vVNkwZVFcPa -ROLQHg+R7bAftF3+1M9AnSP2O+PnXL65DwyTOab/Z/zM/vof3LLCGLYCmzPL+xvB -6PxlqO374kFsKHEaaw66nnFWzPSdks/il0rauAiEbO8Gn/a8F2HFdA/OCCzq83l6 -cOhya7kGXZxdjeIfpfiNjDqZXi+8VRNDcDXx5u/T4vpkliQ+4O8ZXjwE4z2dPHfu -Bw/N7DUalkzhZygYqcgx3tUxu3x/Pso+inmIBbk/As0uZv2nEll2CkEI6CSJIpfn -pLKNQb4E4G7h+u+8kfHcwQ59RU1uGh0PU5uM+DOPg6HsC41RwgOCAQUAAoIBABLY -T8gN8KdxWheESorvgksdG+Fizhkafpac08MCwJFF24v5a8AvZbhcCMLhChrloKcQ -19qHshRIuWbSma/OqCmQKH752PTOKxRKsmqAfO0Rej2aDJrd0s7YBMY72DqeSYPP -peLlwv0gkgRW7/EbDvBI18iTbrQLZtdqs9Xajc3dyIG5wrMtAf/Gta2oWChHlBLZ -S45++Y9ou+LtW7dMc7c+aTxbzeLG36S57kAenRzjfP8zOi3P+Cc+5b9+SZgqfFrz -/ch/HjB2zYAKq9AZSmgp9qIlOIuXnctJUD9hHivuEXFDr6xi1cxj7Q8WnX4+C58/ -QyGS4lebbLQ35x6fTQ8= ------END PUBLIC KEY----- diff --git a/cocoa/dupeguru.icns b/cocoa/dupeguru.icns deleted file mode 100755 index 6641a6b4..00000000 Binary files a/cocoa/dupeguru.icns and /dev/null differ diff --git a/cocoa/en.lproj/Localizable.strings b/cocoa/en.lproj/Localizable.strings deleted file mode 100644 index 60d7efcd..00000000 --- a/cocoa/en.lproj/Localizable.strings +++ /dev/null @@ -1,140 +0,0 @@ - -"%@ Results" = "%@ Results"; -"About dupeGuru" = "About dupeGuru"; -"Action" = "Action"; -"Actions" = "Actions"; -"Add criteria to the right box and click OK to send the dupes that correspond the best to these criteria to their respective group's reference position. Read the help file for more information." = "Add criteria to the right box and click OK to send the dupes that correspond the best to these criteria to their respective group's reference position. Read the help file for more information."; -"Add New Folder..." = "Add New Folder..."; -"Add Selected to Ignore List" = "Add Selected to Ignore List"; -"Advanced" = "Advanced"; -"After having deleted a duplicate, place a link targeting the reference file to replace the deleted file." = "After having deleted a duplicate, place a link targeting the reference file to replace the deleted file."; -"Album" = "Album"; -"Application Mode:" = "Application Mode:"; -"Artist" = "Artist"; -"Attribute" = "Attribute"; -"Automatically check for updates" = "Automatically check for updates"; -"Basic" = "Basic"; -"Bring All to Front" = "Bring All to Front"; -"Can mix file kind" = "Can mix file kind"; -"Cancel" = "Cancel"; -"Check for update..." = "Check for update..."; -"Clear" = "Clear"; -"Clear Picture Cache" = "Clear Picture Cache"; -"Close" = "Close"; -"Close Window" = "Close Window"; -"Columns" = "Columns"; -"Copy" = "Copy"; -"Copy and Move:" = "Copy and Move:"; -"Copy Marked to..." = "Copy Marked to..."; -"Custom command (arguments: %d for dupe, %r for ref):" = "Custom command (arguments: %d for dupe, %r for ref):"; -"Cut" = "Cut"; -"Debug mode (restart required)" = "Debug mode (restart required)"; -"Deletion Options" = "Deletion Options"; -"Delta" = "Delta"; -"Details" = "Details"; -"Details of Selected File" = "Details of Selected File"; -"Details Panel" = "Details Panel"; -"Directly delete files" = "Directly delete files"; -"Directories" = "Directories"; -"Do you really want to remove all your cached picture analysis?" = "Do you really want to remove all your cached picture analysis?"; -"dupeGuru" = "dupeGuru"; -"dupeGuru Help" = "dupeGuru Help"; -"dupeGuru Preferences" = "dupeGuru Preferences"; -"dupeGuru Results" = "dupeGuru Results"; -"dupeGuru Website" = "dupeGuru Website"; -"Dupes Only" = "Dupes Only"; -"Edit" = "Edit"; -"Excluded" = "Excluded"; -"Export Results to CSV" = "Export Results to CSV"; -"Export Results to XHTML" = "Export Results to XHTML"; -"Fewer results" = "Fewer results"; -"File" = "File"; -"Filter" = "Filter"; -"Filter hardness:" = "Filter hardness:"; -"Filter Results..." = "Filter Results..."; -"Folder Selection Window" = "Folder Selection Window"; -"Font Size:" = "Font Size:"; -"Genre" = "Genre"; -"Help" = "Help"; -"Hide dupeGuru" = "Hide dupeGuru"; -"Hide Others" = "Hide Others"; -"Ignore duplicates hardlinking to the same file" = "Ignore duplicates hardlinking to the same file"; -"Ignore files smaller than:" = "Ignore files smaller than:"; -"Ignore List" = "Ignore List"; -"Instead of sending files to trash, delete them directly. This option is usually used as a workaround when the normal deletion method doesn't work." = "Instead of sending files to trash, delete them directly. This option is usually used as a workaround when the normal deletion method doesn't work."; -"Invert Marking" = "Invert Marking"; -"Invoke Custom Command" = "Invoke Custom Command"; -"KB" = "KB"; -"Link deleted files" = "Link deleted files"; -"Load from file..." = "Load from file..."; -"Load Recent Results" = "Load Recent Results"; -"Load Results" = "Load Results"; -"Load Results..." = "Load Results..."; -"Make Selected into Reference" = "Make Selected into Reference"; -"Mark All" = "Mark All"; -"Mark None" = "Mark None"; -"Mark Selected" = "Mark Selected"; -"Match pictures of different dimensions" = "Match pictures of different dimensions"; -"Match similar words" = "Match similar words"; -"Minimize" = "Minimize"; -"Mode" = "Mode"; -"More results" = "More results"; -"Move Marked to..." = "Move Marked to..."; -"Music" = "Music"; -"Name" = "Name"; -"Normal" = "Normal"; -"Ok" = "Ok"; -"Open Selected with Default Application" = "Open Selected with Default Application"; -"Options" = "Options"; -"Paste" = "Paste"; -"Picture" = "Picture"; -"Preferences..." = "Preferences..."; -"Problems!" = "Problems!"; -"Proceed" = "Proceed"; -"Quick Look" = "Quick Look"; -"Quit dupeGuru" = "Quit dupeGuru"; -"Re-Prioritize duplicates" = "Re-Prioritize duplicates"; -"Re-Prioritize Results..." = "Re-Prioritize Results..."; -"Recreate absolute path" = "Recreate absolute path"; -"Recreate relative path" = "Recreate relative path"; -"Reference" = "Reference"; -"Remove empty folders on delete or move" = "Remove empty folders on delete or move"; -"Remove Marked from Results" = "Remove Marked from Results"; -"Remove Selected" = "Remove Selected"; -"Remove Selected from Results" = "Remove Selected from Results"; -"Rename Selected" = "Rename Selected"; -"Reset to Default" = "Reset to Default"; -"Reset To Defaults" = "Reset To Defaults"; -"Results Window" = "Results Window"; -"Reveal" = "Reveal"; -"Reveal Selected in Finder" = "Reveal Selected in Finder"; -"Right in destination" = "Right in destination"; -"Save Results..." = "Save Results..."; -"Scan" = "Scan"; -"Scan Type:" = "Scan Type:"; -"Select a file to save your results to" = "Select a file to save your results to"; -"Select a folder to add to the scanning list" = "Select a folder to add to the scanning list"; -"Select a results file to load" = "Select a results file to load"; -"Select All" = "Select All"; -"Select folders to scan and press \"Scan\"." = "Select folders to scan and press \"Scan\"."; -"Selected" = "Selected"; -"Send Marked to Trash..." = "Send Marked to Trash..."; -"Services" = "Services"; -"Show All" = "Show All"; -"Show Delta Values" = "Show Delta Values"; -"Show Dupes Only" = "Show Dupes Only"; -"Standard" = "Standard"; -"Start Duplicate Scan" = "Start Duplicate Scan"; -"State" = "State"; -"Tags to scan:" = "Tags to scan:"; -"The name '%@' already exists." = "The name '%@' already exists."; -"There were problems processing some (or all) of the files. The cause of these problems are described in the table below. Those files were not removed from your results." = "There were problems processing some (or all) of the files. The cause of these problems are described in the table below. Those files were not removed from your results."; -"Title" = "Title"; -"Track" = "Track"; -"Use regular expressions when filtering" = "Use regular expressions when filtering"; -"Window" = "Window"; -"Word weighting" = "Word weighting"; -"Year" = "Year"; -"You have unsaved results, do you really want to continue?" = "You have unsaved results, do you really want to continue?"; -"You have unsaved results, do you really want to quit?" = "You have unsaved results, do you really want to quit?"; -"Zoom" = "Zoom"; diff --git a/cocoa/inter/__init__.py b/cocoa/inter/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/cocoa/inter/all.py b/cocoa/inter/all.py deleted file mode 100644 index fdc9d57c..00000000 --- a/cocoa/inter/all.py +++ /dev/null @@ -1,10 +0,0 @@ -from cocoa.inter import PyTextField, PyProgressWindow -from .deletion_options import PyDeletionOptions -from .details_panel import PyDetailsPanel -from .directory_outline import PyDirectoryOutline -from .prioritize_dialog import PyPrioritizeDialog -from .prioritize_list import PyPrioritizeList -from .problem_dialog import PyProblemDialog -from .ignore_list_dialog import PyIgnoreListDialog -from .result_table import PyResultTable -from .stats_label import PyStatsLabel \ No newline at end of file diff --git a/cocoa/inter/app.py b/cocoa/inter/app.py deleted file mode 100644 index 6bc3e4e6..00000000 --- a/cocoa/inter/app.py +++ /dev/null @@ -1,252 +0,0 @@ -import logging - -from objp.util import pyref, dontwrap -from cocoa import install_exception_hook, install_cocoa_logger, patch_threaded_job_performer -from cocoa.inter import PyBaseApp, BaseAppView - -import core.pe.photo -from core.app import DupeGuru as DupeGuruBase, AppMode -from .directories import Directories, Bundle -from .photo import Photo - -class DupeGuru(DupeGuruBase): - PICTURE_CACHE_TYPE = 'shelve' - - def __init__(self, view): - DupeGuruBase.__init__(self, view) - self.directories = Directories() - - def selected_dupe_path(self): - if not self.selected_dupes: - return None - return self.selected_dupes[0].path - - def selected_dupe_ref_path(self): - if not self.selected_dupes: - return None - ref = self.results.get_group_of_duplicate(self.selected_dupes[0]).ref - if ref is self.selected_dupes[0]: # we don't want the same pic to be displayed on both sides - return None - return ref.path - - def _get_fileclasses(self): - result = DupeGuruBase._get_fileclasses(self) - if self.app_mode == AppMode.Standard: - result = [Bundle] + result - return result - -class DupeGuruView(BaseAppView): - def askYesNoWithPrompt_(self, prompt: str) -> bool: pass - def createResultsWindow(self): pass - def showResultsWindow(self): pass - def showProblemDialog(self): pass - def selectDestFolderWithPrompt_(self, prompt: str) -> str: pass - def selectDestFileWithPrompt_extension_(self, prompt: str, extension: str) -> str: pass - -class PyDupeGuru(PyBaseApp): - @dontwrap - def __init__(self): - core.pe.photo.PLAT_SPECIFIC_PHOTO_CLASS = Photo - logging.basicConfig(level=logging.WARNING, format='%(levelname)s %(message)s') - install_exception_hook('https://github.com/hsoft/dupeguru/issues') - install_cocoa_logger() - patch_threaded_job_performer() - self.model = DupeGuru(self) - - #---Sub-proxies - def detailsPanel(self) -> pyref: - return self.model.details_panel - - def directoryTree(self) -> pyref: - return self.model.directory_tree - - def problemDialog(self) -> pyref: - return self.model.problem_dialog - - def statsLabel(self) -> pyref: - return self.model.stats_label - - def resultTable(self) -> pyref: - return self.model.result_table - - def ignoreListDialog(self) -> pyref: - return self.model.ignore_list_dialog - - def progressWindow(self) -> pyref: - return self.model.progress_window - - def deletionOptions(self) -> pyref: - return self.model.deletion_options - - #---Directories - def addDirectory_(self, directory: str): - self.model.add_directory(directory) - - #---Results - def doScan(self): - self.model.start_scanning() - - def exportToXHTML(self): - self.model.export_to_xhtml() - - def exportToCSV(self): - self.model.export_to_csv() - - def loadSession(self): - self.model.load() - - def loadResultsFrom_(self, filename: str): - self.model.load_from(filename) - - def markAll(self): - self.model.mark_all() - - def markNone(self): - self.model.mark_none() - - def markInvert(self): - self.model.mark_invert() - - def purgeIgnoreList(self): - self.model.purge_ignore_list() - - def toggleSelectedMark(self): - self.model.toggle_selected_mark_state() - - def saveSession(self): - self.model.save() - - def saveResultsAs_(self, filename: str): - self.model.save_as(filename) - - #---Actions - def addSelectedToIgnoreList(self): - self.model.add_selected_to_ignore_list() - - def deleteMarked(self): - self.model.delete_marked() - - def applyFilter_(self, filter: str): - self.model.apply_filter(filter) - - def makeSelectedReference(self): - self.model.make_selected_reference() - - def copyMarked(self): - self.model.copy_or_move_marked(copy=True) - - def moveMarked(self): - self.model.copy_or_move_marked(copy=False) - - def openSelected(self): - self.model.open_selected() - - def removeMarked(self): - self.model.remove_marked() - - def removeSelected(self): - self.model.remove_selected() - - def revealSelected(self): - self.model.reveal_selected() - - def invokeCustomCommand(self): - self.model.invoke_custom_command() - - def showIgnoreList(self): - self.model.ignore_list_dialog.show() - - def clearPictureCache(self): - self.model.clear_picture_cache() - - #---Information - def getScanOptions(self) -> list: - return [o.label for o in self.model.SCANNER_CLASS.get_scan_options()] - - def resultsAreModified(self) -> bool: - return self.model.results.is_modified - - def getSelectedDupePath(self) -> str: - return str(self.model.selected_dupe_path()) - - def getSelectedDupeRefPath(self) -> str: - return str(self.model.selected_dupe_ref_path()) - - #---Properties - def getAppMode(self) -> int: - return self.model.app_mode - - def setAppMode_(self, app_mode: int): - self.model.app_mode = app_mode - - def setScanType_(self, scan_type_index: int): - scan_options = self.model.SCANNER_CLASS.get_scan_options() - try: - so = scan_options[scan_type_index] - self.model.options['scan_type'] = so.scan_type - except IndexError: - pass - - def setMinMatchPercentage_(self, percentage: int): - self.model.options['min_match_percentage'] = int(percentage) - - def setWordWeighting_(self, words_are_weighted: bool): - self.model.options['word_weighting'] = words_are_weighted - - def setMatchSimilarWords_(self, match_similar_words: bool): - self.model.options['match_similar_words'] = match_similar_words - - def setSizeThreshold_(self, size_threshold: int): - self.model.options['size_threshold'] = size_threshold - - def enable_scanForTag_(self, enable: bool, scan_tag: str): - if 'scanned_tags' not in self.model.options: - self.model.options['scanned_tags'] = set() - if enable: - self.model.options['scanned_tags'].add(scan_tag) - else: - self.model.options['scanned_tags'].discard(scan_tag) - - def setMatchScaled_(self, match_scaled: bool): - self.model.options['match_scaled'] = match_scaled - - def setMixFileKind_(self, mix_file_kind: bool): - self.model.options['mix_file_kind'] = mix_file_kind - - def setEscapeFilterRegexp_(self, escape_filter_regexp: bool): - self.model.options['escape_filter_regexp'] = escape_filter_regexp - - def setRemoveEmptyFolders_(self, remove_empty_folders: bool): - self.model.options['clean_empty_dirs'] = remove_empty_folders - - def setIgnoreHardlinkMatches_(self, ignore_hardlink_matches: bool): - self.model.options['ignore_hardlink_matches'] = ignore_hardlink_matches - - def setCopyMoveDestType_(self, copymove_dest_type: int): - self.model.options['copymove_dest_type'] = copymove_dest_type - - #--- model --> view - @dontwrap - def ask_yes_no(self, prompt): - return self.callback.askYesNoWithPrompt_(prompt) - - @dontwrap - def create_results_window(self): - self.callback.createResultsWindow() - - @dontwrap - def show_results_window(self): - self.callback.showResultsWindow() - - @dontwrap - def show_problem_dialog(self): - self.callback.showProblemDialog() - - @dontwrap - def select_dest_folder(self, prompt): - return self.callback.selectDestFolderWithPrompt_(prompt) - - @dontwrap - def select_dest_file(self, prompt, extension): - return self.callback.selectDestFileWithPrompt_extension_(prompt, extension) - diff --git a/cocoa/inter/deletion_options.py b/cocoa/inter/deletion_options.py deleted file mode 100644 index e483d4d9..00000000 --- a/cocoa/inter/deletion_options.py +++ /dev/null @@ -1,37 +0,0 @@ -# Created On: 2012-05-30 -# Copyright 2015 Hardcoded Software (http://www.hardcoded.net) -# -# This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -# which should be included with this package. The terms are also available at -# http://www.gnu.org/licenses/gpl-3.0.html - -from objp.util import dontwrap -from cocoa.inter import PyGUIObject, GUIObjectView - -class DeletionOptionsView(GUIObjectView): - def updateMsg_(self, msg: str): pass - def show(self) -> bool: pass - def setHardlinkOptionEnabled_(self, enabled: bool): pass - -class PyDeletionOptions(PyGUIObject): - def setLinkDeleted_(self, link_deleted: bool): - self.model.link_deleted = link_deleted - - def setUseHardlinks_(self, use_hardlinks: bool): - self.model.use_hardlinks = use_hardlinks - - def setDirect_(self, direct: bool): - self.model.direct = direct - - #--- model --> view - @dontwrap - def update_msg(self, msg): - self.callback.updateMsg_(msg) - - @dontwrap - def show(self): - return self.callback.show() - - @dontwrap - def set_hardlink_option_enabled(self, enabled): - self.callback.setHardlinkOptionEnabled_(enabled) diff --git a/cocoa/inter/details_panel.py b/cocoa/inter/details_panel.py deleted file mode 100644 index b68f8d01..00000000 --- a/cocoa/inter/details_panel.py +++ /dev/null @@ -1,11 +0,0 @@ -from cocoa.inter import PyGUIObject, GUIObjectView - -class DetailsPanelView(GUIObjectView): - pass - -class PyDetailsPanel(PyGUIObject): - def numberOfRows(self) -> int: - return self.model.row_count() - - def valueForColumn_row_(self, column: str, row: int) -> object: - return self.model.row(row)[int(column)] diff --git a/cocoa/inter/directories.py b/cocoa/inter/directories.py deleted file mode 100644 index 2b316059..00000000 --- a/cocoa/inter/directories.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2016 Hardcoded Software (http://www.hardcoded.net) -# -# This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -# which should be included with this package. The terms are also available at -# http://www.gnu.org/licenses/gpl-3.0.html - -from cocoa import proxy -from hscommon.path import Path, pathify -from core.se import fs -from core.directories import Directories as DirectoriesBase, DirectoryState - -def is_bundle(str_path): - uti = proxy.getUTI_(str_path) - if uti is None: - logging.warning('There was an error trying to detect the UTI of %s', str_path) - return proxy.type_conformsToType_(uti, 'com.apple.bundle') or proxy.type_conformsToType_(uti, 'com.apple.package') - -class Bundle(fs.Folder): - @classmethod - @pathify - def can_handle(cls, path: Path): - return not path.islink() and path.isdir() and is_bundle(str(path)) - -class Directories(DirectoriesBase): - ROOT_PATH_TO_EXCLUDE = list(map(Path, ['/Library', '/Volumes', '/System', '/bin', '/sbin', '/opt', '/private', '/dev'])) - HOME_PATH_TO_EXCLUDE = [Path('Library')] - - def _default_state_for_path(self, path): - result = DirectoriesBase._default_state_for_path(self, path) - if result is not None: - return result - if path in self.ROOT_PATH_TO_EXCLUDE: - return DirectoryState.Excluded - if path[:2] == Path('/Users') and path[3:] in self.HOME_PATH_TO_EXCLUDE: - return DirectoryState.Excluded - - def _get_folders(self, from_folder, j): - # We don't want to scan bundle's subfolder even in Folders mode. Bundle's integrity has to - # stay intact. - if is_bundle(str(from_folder.path)): - # just yield the current folder and bail - state = self.get_state(from_folder.path) - if state != DirectoryState.Excluded: - from_folder.is_ref = state == DirectoryState.Reference - yield from_folder - return - else: - yield from DirectoriesBase._get_folders(self, from_folder, j) - - @staticmethod - def get_subfolders(path): - result = DirectoriesBase.get_subfolders(path) - return [p for p in result if not is_bundle(str(p))] diff --git a/cocoa/inter/directory_outline.py b/cocoa/inter/directory_outline.py deleted file mode 100644 index a25a13a8..00000000 --- a/cocoa/inter/directory_outline.py +++ /dev/null @@ -1,21 +0,0 @@ -from objp.util import dontwrap -from cocoa.inter import PyOutline, GUIObjectView - -class DirectoryOutlineView(GUIObjectView): - pass - -class PyDirectoryOutline(PyOutline): - def addDirectory_(self, path: str): - self.model.add_directory(path) - - def removeSelectedDirectory(self): - self.model.remove_selected() - - def selectAll(self): - self.model.select_all() - - # python --> cocoa - @dontwrap - def refresh_states(self): - # Under cocoa, both refresh() and refresh_states() do the same thing. - self.callback.refresh() \ No newline at end of file diff --git a/cocoa/inter/ignore_list_dialog.py b/cocoa/inter/ignore_list_dialog.py deleted file mode 100644 index 4873b2fa..00000000 --- a/cocoa/inter/ignore_list_dialog.py +++ /dev/null @@ -1,21 +0,0 @@ -from objp.util import pyref, dontwrap -from cocoa.inter import PyGUIObject, GUIObjectView - -class IgnoreListDialogView(GUIObjectView): - def show(self): pass - -class PyIgnoreListDialog(PyGUIObject): - def ignoreListTable(self) -> pyref: - return self.model.ignore_list_table - - def removeSelected(self): - self.model.remove_selected() - - def clear(self): - self.model.clear() - - #--- model --> view - @dontwrap - def show(self): - self.callback.show() - diff --git a/cocoa/inter/photo.py b/cocoa/inter/photo.py deleted file mode 100644 index 3e52c8aa..00000000 --- a/cocoa/inter/photo.py +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright 2016 Hardcoded Software (http://www.hardcoded.net) -# -# This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -# which should be included with this package. The terms are also available at -# http://www.gnu.org/licenses/gpl-3.0.html - -from cocoa import proxy -from core.pe import _block_osx -from core.pe.photo import Photo as PhotoBase - -class Photo(PhotoBase): - HANDLED_EXTS = PhotoBase.HANDLED_EXTS.copy() - HANDLED_EXTS.update({'psd', 'nef', 'cr2', 'orf'}) - - def _plat_get_dimensions(self): - return _block_osx.get_image_size(str(self.path)) - - def _plat_get_blocks(self, block_count_per_side, orientation): - try: - blocks = _block_osx.getblocks(str(self.path), block_count_per_side, orientation) - except Exception as e: - raise IOError('The reading of "%s" failed with "%s"' % (str(self.path), str(e))) - if not blocks: - raise IOError('The picture %s could not be read' % str(self.path)) - return blocks - - def _get_exif_timestamp(self): - exifdata = proxy.readExifData_(str(self.path)) - if exifdata: - try: - return exifdata['{Exif}']['DateTimeOriginal'] - except KeyError: - return '' - else: - return '' \ No newline at end of file diff --git a/cocoa/inter/prioritize_dialog.py b/cocoa/inter/prioritize_dialog.py deleted file mode 100644 index 95a5e086..00000000 --- a/cocoa/inter/prioritize_dialog.py +++ /dev/null @@ -1,29 +0,0 @@ -from objp.util import pyref -from cocoa.inter import PyGUIObject, GUIObjectView -from core.gui.prioritize_dialog import PrioritizeDialog - -class PrioritizeDialogView(GUIObjectView): - pass - -class PyPrioritizeDialog(PyGUIObject): - def __init__(self, app: pyref): - model = PrioritizeDialog(app.model) - PyGUIObject.__init__(self, model) - - def categoryList(self) -> pyref: - return self.model.category_list - - def criteriaList(self) -> pyref: - return self.model.criteria_list - - def prioritizationList(self) -> pyref: - return self.model.prioritization_list - - def addSelected(self): - self.model.add_selected() - - def removeSelected(self): - self.model.remove_selected() - - def performReprioritization(self): - self.model.perform_reprioritization() diff --git a/cocoa/inter/prioritize_list.py b/cocoa/inter/prioritize_list.py deleted file mode 100644 index d9e86a0e..00000000 --- a/cocoa/inter/prioritize_list.py +++ /dev/null @@ -1,8 +0,0 @@ -from cocoa.inter import PySelectableList, SelectableListView - -class PrioritizeListView(SelectableListView): - pass - -class PyPrioritizeList(PySelectableList): - def moveIndexes_toIndex_(self, indexes: list, dest_index: int): - self.model.move_indexes(indexes, dest_index) diff --git a/cocoa/inter/problem_dialog.py b/cocoa/inter/problem_dialog.py deleted file mode 100644 index f65f6145..00000000 --- a/cocoa/inter/problem_dialog.py +++ /dev/null @@ -1,9 +0,0 @@ -from objp.util import pyref -from cocoa.inter import PyGUIObject - -class PyProblemDialog(PyGUIObject): - def problemTable(self) -> pyref: - return self.model.problem_table - - def revealSelected(self): - self.model.reveal_selected_dupe() diff --git a/cocoa/inter/result_table.py b/cocoa/inter/result_table.py deleted file mode 100644 index 4a27205b..00000000 --- a/cocoa/inter/result_table.py +++ /dev/null @@ -1,50 +0,0 @@ -from objp.util import dontwrap -from cocoa.inter import PyTable, TableView - -class ResultTableView(TableView): - def invalidateMarkings(self): pass - -class PyResultTable(PyTable): - def powerMarkerMode(self) -> bool: - return self.model.power_marker - - def setPowerMarkerMode_(self, value: bool): - self.model.power_marker = value - - def deltaValuesMode(self) -> bool: - return self.model.delta_values - - def setDeltaValuesMode_(self, value: bool): - self.model.delta_values = value - - def valueForRow_column_(self, row_index: int, column: str) -> object: - return self.model.get_row_value(row_index, column) - - def isDeltaAtRow_column_(self, row_index: int, column: str) -> bool: - row = self.model[row_index] - return row.is_cell_delta(column) - - def renameSelected_(self, newname: str) -> bool: - return self.model.rename_selected(newname) - - def sortBy_ascending_(self, key: str, asc: bool): - self.model.sort(key, asc) - - def markSelected(self): - self.model.app.toggle_selected_mark_state() - - def removeSelected(self): - self.model.app.remove_selected() - - def selectedDupeCount(self) -> int: - return self.model.selected_dupe_count - - def pathAtIndex_(self, index: int) -> str: - row = self.model[index] - return str(row._dupe.path) - - # python --> cocoa - @dontwrap - def invalidate_markings(self): - self.callback.invalidateMarkings() - \ No newline at end of file diff --git a/cocoa/inter/stats_label.py b/cocoa/inter/stats_label.py deleted file mode 100644 index dafbe516..00000000 --- a/cocoa/inter/stats_label.py +++ /dev/null @@ -1,9 +0,0 @@ -from cocoa.inter import PyGUIObject, GUIObjectView - -class StatsLabelView(GUIObjectView): - pass - -class PyStatsLabel(PyGUIObject): - - def display(self) -> str: - return self.model.display diff --git a/cocoa/main.m b/cocoa/main.m deleted file mode 100644 index e97e22ac..00000000 --- a/cocoa/main.m +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2015 Hardcoded Software (http://www.hardcoded.net) - -This software is licensed under the "GPLv3" License as described in the "LICENSE" file, -which should be included with this package. The terms are also available at -http://www.gnu.org/licenses/gpl-3.0.html -*/ - -#import -#import -#import -#import -#import "AppDelegate.h" -#import "MainMenu_UI.h" - -int main(int argc, char *argv[]) -{ - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - /* We have to set the locate to UTF8 for mbstowcs() to correctly convert non-ascii chars in paths */ - setlocale(LC_ALL, "en_US.UTF-8"); - NSString *respath = [[NSBundle mainBundle] resourcePath]; - NSString *mainpy = [respath stringByAppendingPathComponent:@"dg_cocoa.py"]; - wchar_t wPythonPath[PATH_MAX+1]; - NSString *pypath = [respath stringByAppendingPathComponent:@"py"]; - mbstowcs(wPythonPath, [pypath fileSystemRepresentation], PATH_MAX+1); - Py_SetPath(wPythonPath); - Py_SetPythonHome(wPythonPath); - Py_Initialize(); - PyEval_InitThreads(); - PyGILState_STATE gilState = PyGILState_Ensure(); - FILE* fp = fopen([mainpy UTF8String], "r"); - PyRun_SimpleFile(fp, [mainpy UTF8String]); - fclose(fp); - PyGILState_Release(gilState); - if (gilState == PyGILState_LOCKED) { - PyThreadState_Swap(NULL); - PyEval_ReleaseLock(); - } - - [NSApplication sharedApplication]; - AppDelegate *appDelegate = [[AppDelegate alloc] init]; - [NSApp setDelegate:appDelegate]; - [NSApp setMainMenu:createMainMenu_UI(appDelegate)]; - [appDelegate finalizeInit]; - [pool release]; - [NSApp run]; - Py_Finalize(); - return 0; -} diff --git a/cocoa/run_template.py b/cocoa/run_template.py deleted file mode 100644 index 840e7107..00000000 --- a/cocoa/run_template.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 - -import sys -import os - -def main(): - return os.system('open "{{app_path}}"') - -if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file diff --git a/cocoa/ui/deletion_options.py b/cocoa/ui/deletion_options.py deleted file mode 100644 index 5e6aebda..00000000 --- a/cocoa/ui/deletion_options.py +++ /dev/null @@ -1,49 +0,0 @@ -ownerclass = 'DeletionOptions' -ownerimport = 'DeletionOptions.h' - -result = Window(450, 240, "Deletion Options") -messageLabel = Label(result, "") -linkCheckbox = Checkbox(result, "Link deleted files") -linkLabel = Label(result, "After having deleted a duplicate, place a link targeting the " - "reference file to replace the deleted file.") -linkTypeChoice = RadioButtons(result, ["Symlink", "Hardlink"], columns=2) -directCheckbox = Checkbox(result, "Directly delete files") -directLabel = Label(result, "Instead of sending files to trash, delete them directly. This option " - "is usually used as a workaround when the normal deletion method doesn't work.") -proceedButton = Button(result, "Proceed") -cancelButton = Button(result, "Cancel") - -owner.linkButton = linkCheckbox -owner.linkTypeRadio = linkTypeChoice -owner.directButton = directCheckbox -owner.messageTextField = messageLabel - -result.canMinimize = False -result.canResize = False -linkLabel.controlSize = ControlSize.Small -directLabel.controlSize = ControlSize.Small -linkTypeChoice.controlSize = ControlSize.Small -proceedButton.keyEquivalent = '\\r' -cancelButton.keyEquivalent = '\\e' -linkCheckbox.action = directCheckbox.action = linkTypeChoice.action = Action(owner, 'updateOptions') -proceedButton.action = Action(owner, 'proceed') -cancelButton.action = Action(owner, 'cancel') - -linkLabel.height *= 2 # 2 lines -directLabel.height *= 3 # 3 lines -proceedButton.width = 92 -cancelButton.width = 92 - -mainLayout = VLayout([messageLabel, linkCheckbox, linkLabel, linkTypeChoice, directCheckbox, - directLabel]) -mainLayout.packToCorner(Pack.UpperLeft) -mainLayout.fill(Pack.Right) -buttonLayout = HLayout([cancelButton, proceedButton]) -buttonLayout.packToCorner(Pack.LowerRight) - -# indent the labels under checkboxes a little bit to the right -for indentedView in (linkLabel, directLabel, linkTypeChoice): - indentedView.x += 20 - indentedView.width -= 20 -# We actually don't want the link choice radio buttons to take all the width, it looks weird. -linkTypeChoice.width = 170 diff --git a/cocoa/ui/details_panel.py b/cocoa/ui/details_panel.py deleted file mode 100644 index d8d02905..00000000 --- a/cocoa/ui/details_panel.py +++ /dev/null @@ -1,32 +0,0 @@ -ownerclass = 'DetailsPanel' -ownerimport = 'DetailsPanel.h' - -result = Panel(451, 146, "Details of Selected File") -table = TableView(result) - -owner.detailsTable = table - -result.style = PanelStyle.Utility -result.xProportion = 0.2 -result.yProportion = 0.4 -result.canMinimize = False -result.autosaveName = 'DetailsPanel' -result.minSize = Size(result.width, result.height) - -table.dataSource = owner -table.allowsColumnReordering = False -table.allowsColumnSelection = False -table.allowsMultipleSelection = False -table.font = Font(FontFamily.System, FontSize.SmallSystem) -table.rowHeight = 14 -table.editable = False -col = table.addColumn('0', "Attribute", 70) -col.autoResizable = True -col = table.addColumn('1', "Selected", 198) -col.autoResizable = True -col = table.addColumn('2', "Reference", 172) -col.autoResizable = True - -table.packToCorner(Pack.UpperLeft, margin=0) -table.fill(Pack.LowerRight, margin=0) -table.setAnchor(Pack.UpperLeft, growX=True, growY=True) diff --git a/cocoa/ui/details_panel_picture.py b/cocoa/ui/details_panel_picture.py deleted file mode 100644 index 5d0d110b..00000000 --- a/cocoa/ui/details_panel_picture.py +++ /dev/null @@ -1,70 +0,0 @@ -ownerclass = 'DetailsPanelPicture' -ownerimport = 'DetailsPanelPicture.h' - -result = Panel(593, 398, "Details of Selected File") -table = TableView(result) -split = SplitView(result, 2, vertical=True) -leftSplit, rightSplit = split.subviews -selectedLabel = Label(leftSplit, "Selected") -selectedImage = ImageView(leftSplit, 'NSApplicationIcon') -leftSpinner = ProgressIndicator(leftSplit) -referenceLabel = Label(rightSplit, "Reference") -referenceImage = ImageView(rightSplit, 'NSApplicationIcon') -rightSpinner = ProgressIndicator(rightSplit) - -owner.detailsTable = table -owner.dupeImage = selectedImage -owner.dupeProgressIndicator = leftSpinner -owner.refImage = referenceImage -owner.refProgressIndicator = rightSpinner -table.dataSource = owner - -result.style = PanelStyle.Utility -result.xProportion = 0.6 -result.yProportion = 0.6 -result.canMinimize = False -result.autosaveName = 'DetailsPanel' -result.minSize = Size(451, 240) - -table.allowsColumnReordering = False -table.allowsColumnSelection = False -table.allowsMultipleSelection = False -table.font = Font(FontFamily.System, FontSize.SmallSystem) -table.rowHeight = 14 -table.editable = False -col = table.addColumn('0', "Attribute", 70) -col.autoResizable = True -col = table.addColumn('1', "Selected", 198) -col.autoResizable = True -col = table.addColumn('2', "Reference", 172) -col.autoResizable = True -table.height = 165 - -sides = [ - (leftSplit, selectedLabel, selectedImage, leftSpinner), - (rightSplit, referenceLabel, referenceImage, rightSpinner), -] -for subSplit, label, image, spinner in sides: - label.alignment = TextAlignment.Center - spinner.style = const.NSProgressIndicatorSpinningStyle - spinner.controlSize = const.NSSmallControlSize - spinner.displayedWhenStopped = False - - label.packToCorner(Pack.UpperLeft, margin=0) - label.fill(Pack.Right, margin=0) - label.setAnchor(Pack.UpperLeft, growX=True) - image.packRelativeTo(label, Pack.Below) - image.fill(Pack.LowerRight, margin=0) - image.setAnchor(Pack.UpperLeft, growX=True, growY=True) - spinner.y = label.y - spinner.x = subSplit.width - 30 - spinner.setAnchor(Pack.UpperRight) - -table.packToCorner(Pack.UpperLeft, margin=0) -table.fill(Pack.Right, margin=0) -table.setAnchor(Pack.UpperLeft, growX=True) - -split.packRelativeTo(table, Pack.Below) -split.fill(Pack.LowerRight, margin=0) -split.setAnchor(Pack.UpperLeft, growX=True, growY=True) - diff --git a/cocoa/ui/directory_panel.py b/cocoa/ui/directory_panel.py deleted file mode 100644 index 9f362173..00000000 --- a/cocoa/ui/directory_panel.py +++ /dev/null @@ -1,76 +0,0 @@ -ownerclass = 'DirectoryPanel' -ownerimport = 'DirectoryPanel.h' - -result = Window(425, 300, "dupeGuru") -promptLabel = Label(result, "Select folders to scan and press \"Scan\".") -directoryOutline = OutlineView(result) -directoryOutline.OBJC_CLASS = 'HSOutlineView' -appModeSelector = SegmentedControl(result) -appModeLabel = Label(result, "Application Mode:") -scanTypePopup = Popup(result) -scanTypeLabel = Label(result, "Scan Type:") -addButton = Button(result, "") -removeButton = Button(result, "") -loadResultsButton = Button(result, "Load Results") -scanButton = Button(result, "Scan") -addPopup = Popup(None) -loadRecentPopup = Popup(None) - -owner.outlineView = directoryOutline -owner.appModeSelector = appModeSelector -owner.scanTypePopup = scanTypePopup -owner.removeButton = removeButton -owner.loadResultsButton = loadResultsButton -owner.addButtonPopUp = addPopup -owner.loadRecentButtonPopUp = loadRecentPopup - -result.autosaveName = 'DirectoryPanel' -result.canMinimize = False -result.minSize = Size(400, 270) -for label in ["Standard", "Music", "Picture"]: - appModeSelector.addSegment(label, 80) -addButton.bezelStyle = removeButton.bezelStyle = const.NSTexturedRoundedBezelStyle -addButton.image = 'NSAddTemplate' -removeButton.image = 'NSRemoveTemplate' -for button in (addButton, removeButton): - button.style = const.NSTexturedRoundedBezelStyle - button.imagePosition = const.NSImageOnly -scanButton.keyEquivalent = '\\r' -appModeSelector.action = Action(owner, 'changeAppMode:') -addButton.action = Action(owner, 'popupAddDirectoryMenu:') -removeButton.action = Action(owner, 'removeSelectedDirectory') -loadResultsButton.action = Action(owner, 'popupLoadRecentMenu:') -scanButton.action = Action(None, 'startScanning') - -directoryOutline.font = Font(FontFamily.System, FontSize.SmallSystem) -col = directoryOutline.addColumn('name', "Name", 100) -col.editable = False -col.autoResizable = True -col = directoryOutline.addColumn('state', "State", 85) -col.editable = True -col.autoResizable = False -col.dataCell = Popup(None, ["Normal", "Reference", "Excluded"]) -col.dataCell.controlSize = const.NSSmallControlSize -directoryOutline.allowsColumnReordering = False -directoryOutline.allowsColumnSelection = False -directoryOutline.allowsMultipleSelection = True - -appModeLabel.width = scanTypeLabel.width = 110 -scanTypePopup.width = 248 -appModeLayout = HLayout([appModeLabel, appModeSelector]) -scanTypeLayout = HLayout([scanTypeLabel, scanTypePopup]) - -for button in (addButton, removeButton): - button.width = 28 -for button in (loadResultsButton, scanButton): - button.width = 118 - -buttonLayout = HLayout([addButton, removeButton, None, loadResultsButton, scanButton]) -mainLayout = VLayout([appModeLayout, scanTypeLayout, promptLabel, directoryOutline, buttonLayout], filler=directoryOutline) -mainLayout.packToCorner(Pack.UpperLeft) -mainLayout.fill(Pack.LowerRight) -directoryOutline.packRelativeTo(promptLabel, Pack.Below) - -promptLabel.setAnchor(Pack.UpperLeft, growX=True) -directoryOutline.setAnchor(Pack.UpperLeft, growX=True, growY=True) -buttonLayout.setAnchor(Pack.Below) diff --git a/cocoa/ui/ignore_list_dialog.py b/cocoa/ui/ignore_list_dialog.py deleted file mode 100644 index 60ad46ab..00000000 --- a/cocoa/ui/ignore_list_dialog.py +++ /dev/null @@ -1,30 +0,0 @@ -ownerclass = 'IgnoreListDialog' -ownerimport = 'IgnoreListDialog.h' - -result = Window(550, 350, "Ignore List") -table = TableView(result) -removeSelectedButton = Button(result, "Remove Selected") -clearButton = Button(result, "Clear") -closeButton = Button(result, "Close") - -owner.ignoreListTableView = table - -result.canMinimize = False -removeSelectedButton.action = Action(owner.model, 'removeSelected') -clearButton.action = Action(owner.model, 'clear') -closeButton.action = Action(result, 'performClose:') -closeButton.keyEquivalent = '\\r' -table.allowsColumnReordering = False -table.allowsColumnSelection = False -table.allowsMultipleSelection = True - -removeSelectedButton.width = 142 -clearButton.width = 142 -closeButton.width = 84 -buttonLayout = HLayout([removeSelectedButton, clearButton, None, closeButton]) -buttonLayout.packToCorner(Pack.LowerLeft) -buttonLayout.fill(Pack.Right) -buttonLayout.setAnchor(Pack.Below) -table.packRelativeTo(buttonLayout, Pack.Above) -table.fill(Pack.UpperRight) -table.setAnchor(Pack.UpperLeft, growX=True, growY=True) diff --git a/cocoa/ui/main_menu.py b/cocoa/ui/main_menu.py deleted file mode 100644 index 5b0e74bc..00000000 --- a/cocoa/ui/main_menu.py +++ /dev/null @@ -1,77 +0,0 @@ -ownerclass = 'AppDelegate' -ownerimport = 'AppDelegate.h' - -result = Menu("") -appMenu = result.addMenu("dupeGuru") -fileMenu = result.addMenu("File") -editMenu = result.addMenu("Edit") -actionMenu = result.addMenu("Actions") -owner.columnsMenu = result.addMenu("Columns") -modeMenu = result.addMenu("Mode") -windowMenu = result.addMenu("Window") -helpMenu = result.addMenu("Help") - -appMenu.addItem("About dupeGuru", Action(owner, 'showAboutBox')) -appMenu.addSeparator() -appMenu.addItem("Preferences...", Action(owner, 'showPreferencesPanel'), 'cmd+,') -appMenu.addSeparator() -NSApp.servicesMenu = appMenu.addMenu("Services") -appMenu.addSeparator() -appMenu.addItem("Hide dupeGuru", Action(NSApp, 'hide:'), 'cmd+h') -appMenu.addItem("Hide Others", Action(NSApp, 'hideOtherApplications:'), 'cmd+alt+h') -appMenu.addItem("Show All", Action(NSApp, 'unhideAllApplications:')) -appMenu.addSeparator() -appMenu.addItem("Quit dupeGuru", Action(NSApp, 'terminate:'), 'cmd+q') - -fileMenu.addItem("Load Results...", Action(None, 'loadResults'), 'cmd+o') -owner.recentResultsMenu = fileMenu.addMenu("Load Recent Results") -fileMenu.addItem("Save Results...", Action(None, 'saveResults'), 'cmd+s') -fileMenu.addItem("Export Results to XHTML", Action(owner.model, 'exportToXHTML'), 'cmd+shift+e') -fileMenu.addItem("Export Results to CSV", Action(owner.model, 'exportToCSV')) -fileMenu.addItem("Clear Picture Cache", Action(owner, 'clearPictureCache'), 'cmd+shift+p') - -editMenu.addItem("Mark All", Action(None, 'markAll'), 'cmd+a') -editMenu.addItem("Mark None", Action(None, 'markNone'), 'cmd+shift+a') -editMenu.addItem("Invert Marking", Action(None, 'markInvert'), 'cmd+alt+a') -editMenu.addItem("Mark Selected", Action(None, 'markSelected'), 'ctrl+cmd+a') -editMenu.addSeparator() -editMenu.addItem("Cut", Action(None, 'cut:'), 'cmd+x') -editMenu.addItem("Copy", Action(None, 'copy:'), 'cmd+c') -editMenu.addItem("Paste", Action(None, 'paste:'), 'cmd+v') -editMenu.addSeparator() -editMenu.addItem("Filter Results...", Action(None, 'focusOnFilterField'), 'cmd+alt+f') - -actionMenu.addItem("Start Duplicate Scan", Action(owner, 'startScanning'), 'cmd+d') -actionMenu.addSeparator() -actionMenu.addItem("Send Marked to Trash...", Action(None, 'trashMarked'), 'cmd+t') -actionMenu.addItem("Move Marked to...", Action(None, 'moveMarked'), 'cmd+m') -actionMenu.addItem("Copy Marked to...", Action(None, 'copyMarked'), 'cmd+alt+m') -actionMenu.addItem("Remove Marked from Results", Action(None, 'removeMarked'), 'cmd+r') -actionMenu.addItem("Re-Prioritize Results...", Action(None, 'reprioritizeResults')) -actionMenu.addSeparator() -actionMenu.addItem("Remove Selected from Results", Action(None, 'removeSelected'), 'cmd+backspace') -actionMenu.addItem("Add Selected to Ignore List", Action(None, 'ignoreSelected'), 'cmd+g') -actionMenu.addItem("Make Selected into Reference", Action(None, 'switchSelected'), 'cmd+arrowup') -actionMenu.addSeparator() -actionMenu.addItem("Open Selected with Default Application", Action(None, 'openSelected'), 'cmd+return') -actionMenu.addItem("Reveal Selected in Finder", Action(None, 'revealSelected'), 'cmd+alt+return') -actionMenu.addItem("Invoke Custom Command", Action(None, 'invokeCustomCommand'), 'cmd+shift+c') -actionMenu.addItem("Rename Selected", Action(None, 'renameSelected'), 'enter') - -modeMenu.addItem("Show Dupes Only", Action(None, 'togglePowerMarker'), 'cmd+1') -modeMenu.addItem("Show Delta Values", Action(None, 'toggleDelta'), 'cmd+2') - -windowMenu.addItem("Results Window", Action(owner, 'showResultWindow')) -windowMenu.addItem("Folder Selection Window", Action(owner, 'showDirectoryWindow')) -windowMenu.addItem("Ignore List", Action(owner, 'showIgnoreList')) -windowMenu.addItem("Details Panel", Action(None, 'toggleDetailsPanel'), 'cmd+i') -windowMenu.addItem("Quick Look", Action(None, 'toggleQuicklookPanel'), 'cmd+l') -windowMenu.addSeparator() -windowMenu.addItem("Minimize", Action(None, 'performMinimize:')) -windowMenu.addItem("Zoom", Action(None, 'performZoom:')) -windowMenu.addItem("Close Window", Action(None, 'performClose:'), 'cmd+w') -windowMenu.addSeparator() -windowMenu.addItem("Bring All to Front", Action(None, 'arrangeInFront:')) - -helpMenu.addItem("dupeGuru Help", Action(owner, 'openHelp'), 'cmd+?') -helpMenu.addItem("dupeGuru Website", Action(owner, 'openWebsite')) diff --git a/cocoa/ui/preferences_panel.py b/cocoa/ui/preferences_panel.py deleted file mode 100644 index 227bc681..00000000 --- a/cocoa/ui/preferences_panel.py +++ /dev/null @@ -1,173 +0,0 @@ -appmode = args.get('appmode', 'standard') -dialogHeights = { - 'standard': 325, - 'music': 345, - 'picture': 255, -} - -result = Window(410, dialogHeights[appmode], "dupeGuru Preferences") -tabView = TabView(result) -basicTab = tabView.addTab("Basic") -advancedTab = tabView.addTab("Advanced") -thresholdSlider = Slider(basicTab.view, 1, 100, 80) -thresholdLabel = Label(basicTab.view, "Filter hardness:") -moreResultsLabel = Label(basicTab.view, "More results") -fewerResultsLabel = Label(basicTab.view, "Fewer results") -thresholdValueLabel = Label(basicTab.view, "") -fontSizeCombo = Combobox(basicTab.view, ["11", "12", "13", "14", "18", "24"]) -fontSizeLabel = Label(basicTab.view, "Font Size:") -if appmode in ('standard', 'music'): - wordWeightingBox = Checkbox(basicTab.view, "Word weighting") - matchSimilarWordsBox = Checkbox(basicTab.view, "Match similar words") -elif appmode == 'picture': - matchDifferentDimensionsBox = Checkbox(basicTab.view, "Match pictures of different dimensions") -mixKindBox = Checkbox(basicTab.view, "Can mix file kind") -removeEmptyFoldersBox = Checkbox(basicTab.view, "Remove empty folders on delete or move") -checkForUpdatesBox = Checkbox(basicTab.view, "Automatically check for updates") -if appmode == 'standard': - ignoreSmallFilesBox = Checkbox(basicTab.view, "Ignore files smaller than:") - smallFilesThresholdText = TextField(basicTab.view, "") - smallFilesThresholdSuffixLabel = Label(basicTab.view, "KB") -elif appmode == 'music': - tagsToScanLabel = Label(basicTab.view, "Tags to scan:") - trackBox = Checkbox(basicTab.view, "Track") - artistBox = Checkbox(basicTab.view, "Artist") - albumBox = Checkbox(basicTab.view, "Album") - titleBox = Checkbox(basicTab.view, "Title") - genreBox = Checkbox(basicTab.view, "Genre") - yearBox = Checkbox(basicTab.view, "Year") - tagBoxes = [trackBox, artistBox, albumBox, titleBox, genreBox, yearBox] - -regexpCheckbox = Checkbox(advancedTab.view, "Use regular expressions when filtering") -ignoreHardlinksBox = Checkbox(advancedTab.view, "Ignore duplicates hardlinking to the same file") -debugModeCheckbox = Checkbox(advancedTab.view, "Debug mode (restart required)") -customCommandLabel = Label(advancedTab.view, "Custom command (arguments: %d for dupe, %r for ref):") -customCommandText = TextField(advancedTab.view, "") -copyMoveLabel = Label(advancedTab.view, "Copy and Move:") -copyMovePopup = Popup(advancedTab.view, ["Right in destination", "Recreate relative path", "Recreate absolute path"]) - -resetToDefaultsButton = Button(result, "Reset To Defaults") -thresholdSlider.bind('value', defaults, 'values.minMatchPercentage') -thresholdValueLabel.bind('value', defaults, 'values.minMatchPercentage') -fontSizeCombo.bind('value', defaults, 'values.TableFontSize') -mixKindBox.bind('value', defaults, 'values.mixFileKind') -removeEmptyFoldersBox.bind('value', defaults, 'values.removeEmptyFolders') -checkForUpdatesBox.bind('value', defaults, 'values.SUEnableAutomaticChecks') -regexpCheckbox.bind('value', defaults, 'values.useRegexpFilter') -ignoreHardlinksBox.bind('value', defaults, 'values.ignoreHardlinkMatches') -debugModeCheckbox.bind('value', defaults, 'values.DebugMode') -customCommandText.bind('value', defaults, 'values.CustomCommand') -copyMovePopup.bind('selectedIndex', defaults, 'values.recreatePathType') -if appmode in ('standard', 'music'): - wordWeightingBox.bind('value', defaults, 'values.wordWeighting') - matchSimilarWordsBox.bind('value', defaults, 'values.matchSimilarWords') - disableWhenContentScan = [thresholdSlider, wordWeightingBox, matchSimilarWordsBox] - for control in disableWhenContentScan: - vtname = 'vtScanTypeMusicIsNotContent' if appmode == 'music' else 'vtScanTypeIsNotContent' - prefname = 'values.scanTypeMusic' if appmode == 'music' else 'values.scanTypeStandard' - control.bind('enabled', defaults, prefname, valueTransformer=vtname) - if appmode == 'standard': - ignoreSmallFilesBox.bind('value', defaults, 'values.ignoreSmallFiles') - smallFilesThresholdText.bind('value', defaults, 'values.smallFileThreshold') - elif appmode == 'music': - for box in tagBoxes: - box.bind('enabled', defaults, 'values.scanTypeMusic', valueTransformer='vtScanTypeIsTag') - trackBox.bind('value', defaults, 'values.scanTagTrack') - artistBox.bind('value', defaults, 'values.scanTagArtist') - albumBox.bind('value', defaults, 'values.scanTagAlbum') - titleBox.bind('value', defaults, 'values.scanTagTitle') - genreBox.bind('value', defaults, 'values.scanTagGenre') - yearBox.bind('value', defaults, 'values.scanTagYear') -elif appmode == 'picture': - matchDifferentDimensionsBox.bind('value', defaults, 'values.matchScaled') - thresholdSlider.bind('enabled', defaults, 'values.scanTypePicture', valueTransformer='vtScanTypeIsFuzzy') - -result.canResize = False -result.canMinimize = False -thresholdValueLabel.formatter = NumberFormatter(NumberStyle.Decimal) -thresholdValueLabel.formatter.maximumFractionDigits = 0 -allLabels = [thresholdValueLabel, moreResultsLabel, fewerResultsLabel, - thresholdLabel, fontSizeLabel, customCommandLabel, copyMoveLabel] -allCheckboxes = [mixKindBox, removeEmptyFoldersBox, checkForUpdatesBox, regexpCheckbox, - ignoreHardlinksBox, debugModeCheckbox] -if appmode == 'standard': - allLabels += [smallFilesThresholdSuffixLabel] - allCheckboxes += [ignoreSmallFilesBox, wordWeightingBox, matchSimilarWordsBox] -elif appmode == 'music': - allLabels += [tagsToScanLabel] - allCheckboxes += tagBoxes + [wordWeightingBox, matchSimilarWordsBox] -elif appmode == 'picture': - allCheckboxes += [matchDifferentDimensionsBox] -for label in allLabels: - label.controlSize = ControlSize.Small -fewerResultsLabel.alignment = TextAlignment.Right -for checkbox in allCheckboxes: - checkbox.font = thresholdValueLabel.font -resetToDefaultsButton.action = Action(defaults, 'revertToInitialValues:') - -thresholdLabel.width = fontSizeLabel.width = 94 -fontSizeCombo.width = 66 -thresholdValueLabel.width = 25 -resetToDefaultsButton.width = 136 -if appmode == 'standard': - smallFilesThresholdText.width = 60 - smallFilesThresholdSuffixLabel.width = 40 -elif appmode == 'music': - for box in tagBoxes: - box.width = 70 - -tabView.packToCorner(Pack.UpperLeft) -tabView.fill(Pack.Right) -resetToDefaultsButton.packRelativeTo(tabView, Pack.Below, align=Pack.Right) -tabView.fill(Pack.Below, margin=14) -tabView.setAnchor(Pack.UpperLeft, growX=True, growY=True) -thresholdLayout = HLayout([thresholdLabel, thresholdSlider, thresholdValueLabel], filler=thresholdSlider) -thresholdLayout.packToCorner(Pack.UpperLeft) -thresholdLayout.fill(Pack.Right) -# We want to give the labels as much space as possible, and we only "know" how much is available -# after the slider's fill operation. -moreResultsLabel.width = fewerResultsLabel.width = thresholdSlider.width // 2 -moreResultsLabel.packRelativeTo(thresholdSlider, Pack.Below, align=Pack.Left, margin=6) -fewerResultsLabel.packRelativeTo(thresholdSlider, Pack.Below, align=Pack.Right, margin=6) -fontSizeCombo.packRelativeTo(moreResultsLabel, Pack.Below) -fontSizeLabel.packRelativeTo(fontSizeCombo, Pack.Left) - -if appmode == 'music': - tagsToScanLabel.packRelativeTo(fontSizeCombo, Pack.Below) - tagsToScanLabel.fill(Pack.Left) - tagsToScanLabel.fill(Pack.Right) - trackBox.packRelativeTo(tagsToScanLabel, Pack.Below) - trackBox.x += 10 - artistBox.packRelativeTo(trackBox, Pack.Right) - albumBox.packRelativeTo(artistBox, Pack.Right) - titleBox.packRelativeTo(trackBox, Pack.Below) - genreBox.packRelativeTo(titleBox, Pack.Right) - yearBox.packRelativeTo(genreBox, Pack.Right) - viewToPackCheckboxesUnder = titleBox -else: - viewToPackCheckboxesUnder = fontSizeCombo - -if appmode == 'standard': - checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox, - ignoreSmallFilesBox] -elif appmode == 'music': - checkboxesToLayout = [wordWeightingBox, matchSimilarWordsBox, mixKindBox, removeEmptyFoldersBox, - checkForUpdatesBox] -elif appmode == 'picture': - checkboxesToLayout = [matchDifferentDimensionsBox, mixKindBox, removeEmptyFoldersBox, - checkForUpdatesBox] -checkboxLayout = VLayout(checkboxesToLayout) -checkboxLayout.packRelativeTo(viewToPackCheckboxesUnder, Pack.Below) -checkboxLayout.fill(Pack.Left) -checkboxLayout.fill(Pack.Right) - -if appmode == 'standard': - smallFilesThresholdText.packRelativeTo(ignoreSmallFilesBox, Pack.Below, margin=4) - checkForUpdatesBox.packRelativeTo(smallFilesThresholdText, Pack.Below, margin=4) - checkForUpdatesBox.fill(Pack.Right) - smallFilesThresholdText.x += 20 - smallFilesThresholdSuffixLabel.packRelativeTo(smallFilesThresholdText, Pack.Right) - -advancedLayout = VLayout(advancedTab.view.subviews[:]) -advancedLayout.packToCorner(Pack.UpperLeft) -advancedLayout.fill(Pack.Right) diff --git a/cocoa/ui/prioritize_dialog.py b/cocoa/ui/prioritize_dialog.py deleted file mode 100644 index 69271139..00000000 --- a/cocoa/ui/prioritize_dialog.py +++ /dev/null @@ -1,65 +0,0 @@ -ownerclass = 'PrioritizeDialog' -ownerimport = 'PrioritizeDialog.h' - -result = Window(610, 400, "Re-Prioritize duplicates") -promptLabel = Label(result, "Add criteria to the right box and click OK to send the dupes that " - "correspond the best to these criteria to their respective group's reference position. Read " - "the help file for more information.") -split = SplitView(result, 2, vertical=True) -categoryPopup = Popup(split.subviews[0]) -criteriaTable = ListView(split.subviews[0]) -prioritizationTable = ListView(split.subviews[1]) -addButton = Button(split.subviews[1], NLSTR("-->")) -removeButton = Button(split.subviews[1], NLSTR("<--")) -okButton = Button(result, "Ok") -cancelButton = Button(result, "Cancel") - -owner.categoryPopUpView = categoryPopup -owner.criteriaTableView = criteriaTable -owner.prioritizationTableView = prioritizationTable - -result.canMinimize = False -result.canClose = False -result.minSize = Size(result.width, result.height) -addButton.action = Action(owner.model, 'addSelected') -removeButton.action = Action(owner.model, 'removeSelected') -okButton.action = Action(owner, 'ok') -cancelButton.action = Action(owner, 'cancel') -okButton.keyEquivalent = '\\r' -cancelButton.keyEquivalent = '\\e' - -# For layouts to correctly work, subviews need to have the dimensions they'll approximately have -# at runtime. -split.subviews[0].width = 260 -split.subviews[0].height = 260 -split.subviews[1].width = 340 -split.subviews[1].height = 260 -promptLabel.height *= 3 # 3 lines - -leftLayout = VLayout([categoryPopup, criteriaTable], filler=criteriaTable) -middleLayout = VLayout([addButton, removeButton], width=41) -buttonLayout = HLayout([None, cancelButton, okButton]) - -#pack split subview 0 -leftLayout.fillAll() - -#pack split subview 1 -prioritizationTable.fillAll() -prioritizationTable.width -= 48 -prioritizationTable.moveTo(Pack.Right) -middleLayout.moveNextTo(prioritizationTable, Pack.Left, align=Pack.Middle) - -# Main layout -promptLabel.packToCorner(Pack.UpperLeft) -promptLabel.fill(Pack.Right) -split.moveNextTo(promptLabel, Pack.Below) -buttonLayout.moveNextTo(split, Pack.Below) -buttonLayout.fill(Pack.Right) -split.fill(Pack.LowerRight) - -promptLabel.setAnchor(Pack.UpperLeft, growX=True) -prioritizationTable.setAnchor(Pack.UpperLeft, growX=True, growY=True) -categoryPopup.setAnchor(Pack.UpperLeft, growX=True) -criteriaTable.setAnchor(Pack.UpperLeft, growX=True, growY=True) -split.setAnchor(Pack.UpperLeft, growX=True, growY=True) -buttonLayout.setAnchor(Pack.Below) diff --git a/cocoa/ui/problem_dialog.py b/cocoa/ui/problem_dialog.py deleted file mode 100644 index cd3430ba..00000000 --- a/cocoa/ui/problem_dialog.py +++ /dev/null @@ -1,35 +0,0 @@ -ownerclass = 'ProblemDialog' -ownerimport = 'ProblemDialog.h' - -result = Window(480, 310, "Problems!") -messageLabel = Label(result, "There were problems processing some (or all) of the files. The cause " - "of these problems are described in the table below. Those files were not removed from your " - "results.") -problemTable = TableView(result) -revealButton = Button(result, "Reveal") -closeButton = Button(result, "Close") - -owner.problemTableView = problemTable - -result.canMinimize = False -result.minSize = Size(300, 300) -closeButton.keyEquivalent = '\\r' -revealButton.action = Action(owner.model, 'revealSelected') -closeButton.action = Action(result, 'performClose:') - -messageLabel.height *= 3 # 3 lines -revealButton.width = 150 -closeButton.width = 98 - -messageLabel.packToCorner(Pack.UpperLeft) -messageLabel.fill(Pack.Right) -problemTable.packRelativeTo(messageLabel, Pack.Below) -problemTable.fill(Pack.Right) -revealButton.packRelativeTo(problemTable, Pack.Below) -closeButton.packRelativeTo(problemTable, Pack.Below, align=Pack.Right) -problemTable.fill(Pack.Below) - -messageLabel.setAnchor(Pack.UpperLeft, growX=True) -problemTable.setAnchor(Pack.UpperLeft, growX=True, growY=True) -revealButton.setAnchor(Pack.LowerLeft) -closeButton.setAnchor(Pack.LowerRight) diff --git a/cocoa/ui/result_window.py b/cocoa/ui/result_window.py deleted file mode 100644 index ad3b075a..00000000 --- a/cocoa/ui/result_window.py +++ /dev/null @@ -1,97 +0,0 @@ -ownerclass = 'ResultWindow' -ownerimport = 'ResultWindow.h' - -result = Window(557, 400, "dupeGuru Results") -toolbar = result.createToolbar('ResultsToolbar') -table = TableView(result) -table.OBJC_CLASS = 'HSTableView' -statsLabel = Label(result, "") -contextMenu = Menu("") - -#Setup toolbar items -toolbar.displayMode = const.NSToolbarDisplayModeIconOnly -directoriesToolItem = toolbar.addItem('Directories', "Directories", image='folder32') -actionToolItem = toolbar.addItem('Action', "Action") -filterToolItem = toolbar.addItem('Filter', "Filter") -optionsToolItem = toolbar.addItem('Options', "Options") -quicklookToolItem = toolbar.addItem('QuickLook', "Quick Look") -toolbar.defaultItems = [actionToolItem, optionsToolItem, quicklookToolItem, directoriesToolItem, - toolbar.flexibleSpace(), filterToolItem] -actionPopup = Popup(None) -actionPopup.pullsdown = True -actionPopup.bezelStyle = const.NSTexturedRoundedBezelStyle -actionPopup.arrowPosition = const.NSPopUpArrowAtBottom -item = actionPopup.menu.addItem("") # First item is invisible -item.hidden = True -item.image = 'NSActionTemplate' -actionPopup.width = 44 -actionToolItem.view = actionPopup -filterField = SearchField(None, "Filter") -filterField.action = Action(owner, 'filter') -filterField.sendsWholeSearchString = True -filterToolItem.view = filterField -filterToolItem.minSize = Size(80, 22) -filterToolItem.maxSize = Size(300, 22) -quickLookButton = Button(None, "") -quickLookButton.bezelStyle = const.NSTexturedRoundedBezelStyle -quickLookButton.image = 'NSQuickLookTemplate' -quickLookButton.width = 44 -quickLookButton.action = Action(owner, 'toggleQuicklookPanel') -quicklookToolItem.view = quickLookButton -optionsSegments = SegmentedControl(None) -optionsSegments.segmentStyle = const.NSSegmentStyleCapsule -optionsSegments.trackingMode = const.NSSegmentSwitchTrackingSelectAny -optionsSegments.font = Font(FontFamily.System, 11) -optionsSegments.addSegment("Details", 57) -optionsSegments.addSegment("Dupes Only", 82) -optionsSegments.addSegment("Delta", 48) -optionsSegments.action = Action(owner, 'changeOptions') -optionsToolItem.view = optionsSegments - -# Popuplate menus -actionPopup.menu.addItem("Send Marked to Trash...", action=Action(owner, 'trashMarked')) -actionPopup.menu.addItem("Move Marked to...", action=Action(owner, 'moveMarked')) -actionPopup.menu.addItem("Copy Marked to...", action=Action(owner, 'copyMarked')) -actionPopup.menu.addItem("Remove Marked from Results", action=Action(owner, 'removeMarked')) -actionPopup.menu.addSeparator() -for menu in (actionPopup.menu, contextMenu): - menu.addItem("Remove Selected from Results", action=Action(owner, 'removeSelected')) - menu.addItem("Add Selected to Ignore List", action=Action(owner, 'ignoreSelected')) - menu.addItem("Make Selected into Reference", action=Action(owner, 'switchSelected')) - menu.addSeparator() - menu.addItem("Open Selected with Default Application", action=Action(owner, 'openSelected')) - menu.addItem("Reveal Selected in Finder", action=Action(owner, 'revealSelected')) - menu.addItem("Rename Selected", action=Action(owner, 'renameSelected')) - -# Doing connections -owner.filterField = filterField -owner.matches = table -owner.optionsSwitch = optionsSegments -owner.optionsToolbarItem = optionsToolItem -owner.stats = statsLabel -table.bind('rowHeight', defaults, 'values.TableFontSize', valueTransformer='vtRowHeightOffset') - -# Rest of the setup -result.minSize = Size(340, 340) -result.autosaveName = 'MainWindow' -statsLabel.alignment = TextAlignment.Center -table.alternatingRows = True -table.menu = contextMenu -table.allowsColumnReordering = True -table.allowsColumnResizing = True -table.allowsColumnSelection = False -table.allowsEmptySelection = False -table.allowsMultipleSelection = True -table.allowsTypeSelect = True -table.gridStyleMask = const.NSTableViewSolidHorizontalGridLineMask -table.setAnchor(Pack.UpperLeft, growX=True, growY=True) -statsLabel.setAnchor(Pack.LowerLeft, growX=True) - -# Layout -# It's a little weird to pack with a margin of -1, but if I don't do that, I get too thick of a -# border on the upper side of the table. -table.packToCorner(Pack.UpperLeft, margin=-1) -table.fill(Pack.Right, margin=0) -statsLabel.packRelativeTo(table, Pack.Below, margin=6) -statsLabel.fill(Pack.Right, margin=0) -table.fill(Pack.Below, margin=5) diff --git a/cocoa/waf b/cocoa/waf deleted file mode 100755 index d8ea88d9..00000000 --- a/cocoa/waf +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python -# encoding: ISO8859-1 -# Thomas Nagy, 2005-2015 - -""" -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. 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. - -3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 AUTHOR 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. -""" - -import os, sys, inspect - -VERSION="1.8.17" -REVISION="15ca44d2d9ba2cd22650638fdb07c7a7" -GIT="18449feb5e938f9cd2b5dba046dccdb9e3d4c34c" -INSTALL='' -C1='#.' -C2='#-' -C3='#,' -cwd = os.getcwd() -join = os.path.join - - -WAF='waf' -def b(x): - return x -if sys.hexversion>0x300000f: - WAF='waf3' - def b(x): - return x.encode() - -def err(m): - print(('\033[91mError: %s\033[0m' % m)) - sys.exit(1) - -def unpack_wafdir(dir, src): - f = open(src,'rb') - c = 'corrupt archive (%d)' - while 1: - line = f.readline() - if not line: err('run waf-light from a folder containing waflib') - if line == b('#==>\n'): - txt = f.readline() - if not txt: err(c % 1) - if f.readline() != b('#<==\n'): err(c % 2) - break - if not txt: err(c % 3) - txt = txt[1:-1].replace(b(C1), b('\n')).replace(b(C2), b('\r')).replace(b(C3), b('\x00')) - - import shutil, tarfile - try: shutil.rmtree(dir) - except OSError: pass - try: - for x in ('Tools', 'extras'): - os.makedirs(join(dir, 'waflib', x)) - except OSError: - err("Cannot unpack waf lib into %s\nMove waf in a writable directory" % dir) - - os.chdir(dir) - tmp = 't.bz2' - t = open(tmp,'wb') - try: t.write(txt) - finally: t.close() - - try: - t = tarfile.open(tmp) - except: - try: - os.system('bunzip2 t.bz2') - t = tarfile.open('t') - tmp = 't' - except: - os.chdir(cwd) - try: shutil.rmtree(dir) - except OSError: pass - err("Waf cannot be unpacked, check that bzip2 support is present") - - try: - for x in t: t.extract(x) - finally: - t.close() - - for x in ('Tools', 'extras'): - os.chmod(join('waflib',x), 493) - - if sys.hexversion<0x300000f: - sys.path = [join(dir, 'waflib')] + sys.path - import fixpy2 - fixpy2.fixdir(dir) - - os.remove(tmp) - os.chdir(cwd) - - try: dir = unicode(dir, 'mbcs') - except: pass - try: - from ctypes import windll - windll.kernel32.SetFileAttributesW(dir, 2) - except: - pass - -def test(dir): - try: - os.stat(join(dir, 'waflib')) - return os.path.abspath(dir) - except OSError: - pass - -def find_lib(): - src = os.path.abspath(inspect.getfile(inspect.getmodule(err))) - base, name = os.path.split(src) - - #devs use $WAFDIR - w=test(os.environ.get('WAFDIR', '')) - if w: return w - - #waf-light - if name.endswith('waf-light'): - w = test(base) - if w: return w - err('waf-light requires waflib -> export WAFDIR=/folder') - - dirname = '%s-%s-%s' % (WAF, VERSION, REVISION) - for i in (INSTALL,'/usr','/usr/local','/opt'): - w = test(i + '/lib/' + dirname) - if w: return w - - #waf-local - dir = join(base, (sys.platform != 'win32' and '.' or '') + dirname) - w = test(dir) - if w: return w - - #unpack - unpack_wafdir(dir, src) - return dir - -wafdir = find_lib() -sys.path.insert(0, wafdir) - -if __name__ == '__main__': - - from waflib import Scripting - Scripting.waf_entry_point(cwd, VERSION, wafdir) - -#==> -#BZh91AY&SY 7#,Y #h%H5`(a|n#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,#,{8`ٴQVT]n/[k}=۹uV^w_|,VRz;جkggwvz7WgfwUw=ro{=||{m/>wcgp#,#,#,#,#,c#,{BYUn;7^w@-5mꈨ#. #,R0)T#.P{tD@h2@0[)#,ݯ^}N3zsRन/sV]΄V׵8ӽ۷fwoyuW{of#-Ώ;CEVٻ{xd4>9jى{׸#,҂=2#,u٠ vֺO=ݺVǗ^5hUX`_;ccB]#.tyw=u9={W<=)Ѡ}q4׷ǻxT6n;^ݞv{{{{;.{n {f{>m}|1@ӑޱJiY;۴|R[m/w;s;޶i>[]F}}<[nX#,}>o pxQV*wd윥ʒӇÕzakw#,q#,T>iovB{^=BНǷv Sut{ܛb͋O^6޷e4'oowb}t7w[noz{n{v}};_ox0[mzutr8OoEG׾_&ZoO{#,}}86÷}׶m|ym7^+}]/\Ѿrc:tP4VN›q>={;۽wyw #-{/#,6mV=>wk쓏vnv]][u$׼{k+SGz}2+M;O^!ҽ۳=0=4tm^]9rmwNJgom[Jvv|ڽ{3I}B=Jh#,@h#,#, D4)@4 dڂS@  &#-S1'4M6Pi#-#,4h#,#,#,#, MlAO'MQ#.g}~‹nuZ8biDr:ƱBaISn\m(d,7Y4HiǍ9g]l/)/ou¯u{=W^ck0MurK]]DΩsI#,ՎZorb@nTW&̱0Y/.Xwv֌*te (C (`)<3XӒҵTؖQ GgM(DlbM=\Μo(`68Qd&ykEhndAhH[#,XOPtHQv#.20lEQiƗ!놠uJ&%7TDmM ђP$e R{zY]J4~nVXͼW{S˒n:(#.T w_8b#.VhuMG.|ү0tqy_$1"UȂ  qk ݉٥FGU/~^}37Z#.}\ę衎O9rj(PCD=IҼ׼vSƉf >D p5hQd=neGce'!ڔV6x17Y;#.X,x_(؁Q;5͒87\]I>l5-!m{a}.ddki45ڐZ|Ry= ޔ//yT6zXV1g=˺Eѯ̲ocU)pid+ڌc:JPXb!!qTRu!%#- ȫ:r&/(O#.Z$"%(Tr{/3`n3u돋Kd޻%_){CE#-[hcFrk@t6?J[Y)'HZwy\۩2FHsAuIk8JD'mi)>)#.T[Jkw۞ +zӻ"#.I ʊ90 L"T%]6m-8Ւd!mL:t±8WЬPfG.DPd4!P[VҞ{7I\X.c'>3B #*8cn욡/ڛF.K#. 0XՋ=4Ɏn9^QmOÎ1~+>[ԣ6GNZc:=S(RUPMxZi*#-|3 )+0j>,uiUŚRoeZwf10""`7Iz38cܞ{󿔧gķC:}xGtH6i%!rOJa0i;[6әW^@&4*m?jÃPwFe{m( ׉sHvYQaG ^rf4ٌ{GP"ǘ"䔈et@"0cm`MgP\k7H~ʹ$=8g4cZD/w9ûAr:Z5x܅@ ,t#KnOvZ3lSNQ(F ջ#.,ԡEAbpC@Km"1wHȦS߶ǎd(s7.6g}zq)8r3 rnEuTvy #0-V!Op~GjC1_gnǬ0Tp'O'q(!2 Õb;v?Uz{y;ΫUgsq*"Y)OdR4O-ruMwT R'̖ꮍTG264TloA"=19'4bN,$}m;,&.!(1/V!(=GWQ$,>#e)|{{{4r8pXĨ&m+۾Vu/*'"S+W!]C-,g3#.~uWTZ^3X::hʚ'EHdNEQ褡ugi(:!*0EႱ\cz:y5dtܟՠ_ž AYS;0hwT$ۮVm7K!0X*ӑ1H"bV?o+j}zː̆% 08J z*B/6g+,gNGm#c]T񑨲qI#-2oT(u,ݵy f=5 J*.1"h<]ϚvɭEz\4+^̏m-#.ᘈ.O"fwgFT!c^[\JnƼP`iMyBҏBn)jE}kf=zgT(AUMh&>ܭVBzLL4RlӌK|-xQogʩ"Z+\#-{xqzԱ/uZ Ҍ(vߟ aO}6#-NgxAX-e#.gkXbkE+RjjM4uF"s$hA azY7mw[$]RꔬW]I[C|2ln%P֪.n97I4w_Duk[zi'CB}#-+3qS%#+ IvTAPgTFBA7HA92ԻH8:G_W՟˗ .H>4VHB ޴WZ(m߅EOY狒DyQM~./^9^\/LK5ASwGPJbZ9N 8U )duzԭ'1Olrwfzk?=3Xvbä́tY'\RO+dЩeКӫ>+#'4Rq#-VG3B6Kڣ{JN'zݼvҟ ]yQ5#"*L#I5~J#-7. wJX;6S$+K7{bJz~;)-K?#,TEeA#-)r%~56[#.ñ=v Xi4)F$)Y,m %jjViIL34IrF@+Z+Ubd^{+ݜѶ1'}8Mҧm$[Sl#-\+}iMSkNbΗU˖ݻ#.:ZX I9u֨{]kfH%_zB>cܸ>?ZTX%$AQ,a*5ҳmTxj[3EHPuxW46򐄚NX:Gt5~oWU"\6Hs8B@)"<|f8W{q݌">JLjc :?D;1H=9ӷM=/p)P~y5xTMY1+0ش䚬?3ר0&KޗuPL$IeVv,!Scxt44AFܒin2W֩{3.i$C7~~7SjLc(avMWl"K<]E+I=NsIU9*Yl`UW?wǎ2:-s? #Ϛd!,Y kUkLTM?I W?F82|y*T6RP<>_ǀ Bcg\ںىu*?/=zSR{ O7U6춲۳:wogu7۳lu2B3s1ƗdrU f&!"@#,?魙ޱF^ ~i_|8ZM-31kr.#_y1PlGDov#,`5Tjbi]}n31? { &gqV~U3JߟHU|rR#E 4B6vzgJ *C8 8;uN#-µJ$ݐģP[48y爙Pv&#.]%ݾ{e*0yD0b!6;7?aj/WRo=A_^#,O䚳(oovݛ}RV7_y 6q,{fWqc_Ն.9hU妟VCWwi04luo_j6ݿ"k5hA^6p"tf#-(R뱑}2hߦ3H&WK()J:)ŝJ.i?\I O"' 8eЇK_W}I꼮ܥD#-rI 2nz7e`ﻳ iҵVr;2^A3γ2a`3+im N:s?a 1KC-jGr1;I$U-#-NrK/hQmDKmIyO;-٦lpx?f2P_ fzѝVLS3Lgs ]==sh/.;h#.:n0-Ű2kICډ7>Nb0݃qae RqvAMOb<jqrKNX˙7;i#.ŧ eC%=N\sm垻U,~rS-WMfPmXV4ݯ&6B>6C!6ѓ9IѮ=SF9g!8y~W4bcQLP{RnXgv^W NRWΘw] ԁ)jl"kS-~\̧(yuZľ0t>=)H*$[FM% __9<~`lV^?FjXjeLf^jEl3}Z!7mxM:#.[2w$M( 0=[a|1>Q=evJ!9*Fvnn0THZ0G( kuf"h4(Lpn [Tj1TSSlsVz'y|kя):Muܒɞ6L_\zkRO$k;A:p#]SJp蚺8m+3@\Hfŏ]ZdjOj8|^שf/QxmytF{z{8ͥ:^u&T碑rcSoN uur#-y#-#-h@r)YTliɏ9a$4C nF)GجJ.#-KgSu|j!?bҾ<܄"͸+0Nз[F2 p.4MsCWަ2X|u2Fkrέ#}Bٜ~Mbh▴1 yFcS뷢ipAքMP+eU@2c]|TCBVDba K"YI b+EmT*LPXL㿧}Qy`SICI5zF5U4[55^ͱ| )PcdFlQ5Xcad={ͺ_`\fo0 m2~=~lTk=Js^^pUsxW;nnt_h՞LŹ?Qʍ?rELn}jzeo].nSDםy zɕgՐ=Y7YѪKUn\ad# `/ֺoV#EZq䬨5Ơ'sRNe;/6n{XUrw~JK}jB %0Bq4#.-K1k80lAi7~Wlk.?Lk3I k i(B$GHMfӂ\c٧Ch\pJCoI*1h@tKǮ`RAw|t^Z]~'Y6aB!0BoQ~s:فFPXs0qvr#-5LMUpqoqR{^*ͰJLVɞ2cK^nX#.-ZZL 4];^j+Szڀ4`H"T6ٛHiEEC#-*"a4l$XFL,]#.a m$41&gb#,%YME:AO5BͲӘƿ"jd#H.A>bъqx!3U}G8rh]W#<ʌ=s'CZGzIVɩl\i .kY7v:- Mu~ÃCu N酇RżhT@za}2ԙx_Ljc|DFyPFS>JXy2?b2F=T}/!;W4et;glFSP|xgEOO-0wC]^!}(R!!]&VTs3zH3]jI8dTc5.\kY#QcOm ;;F1KXYR vĿ^[YdKh9n?ͷ.LI,ʄloÏ[Cq#-UTq'=1#\i%wϴ?7tD0¦0ْ#-,k2]ng@vFEI[)2a|q[l,c,Bu!{NU1PМKdQDd嶹ksZ-kLәnz/Ww>~ʉO$I(dqRpt3kPɊc8a;Oa=A3U)T9=//?z~\C)u=s}JEuaQ6Wk\ڥ}oى|]<z2^8bدV?>a`ٜfH|Fxtgb 8&,b% JV^Wϙ~!AH2ȬB68/Ϣ&c}Rq^O<L)WOzox޷9ڏɧrk3m$h:&JY~.?56(U7ug۷;_Jç;0;迤B0}Ou[Z$~{S_p>&Ӡݡ|^m7C{c=YmDl%&RźٗNE|ovF`0Mv݋ Rrxk}48hT?wwN#-XQctFTjKe꼖ciCM,oC-# Y)#!Z%#-PŮ&ӧT3׬HJ^k'wH!x*4#ޛLzaPXS-IdLHfʖ%I8M%FuB0joreGXFW ƁӉI1NrUy+~>74DbQ(Ka=ኊEpB3ALCػ0 8^G҇d,">)< +Qw`DtD[Mub2؇})KJZ(FE#-LZ|Fp|=ž: Һ}z5N*bꜾs!/~>|Ih:n5w#.($#-KQ`tEi=uV5(ҧF}7#,l8h7Kdၒ,b曑TJR/ Utx.կ=~#.Nnd6zu3W,uyu \l}>oUR <ɼ*?dU2|<&|MNF/a^lm̎O`ؼ1Dnm; l5@B:'e#-[}4~aFpoMI##!OX&wvw q5B4d\<6I8P`__m x!+MB)9i"e_}TGC:S< p[7\Jwt5|RS_b40ݨ#uoމ=^U@$sfwXO4H;t(sY"ipڢ\<~^#.|KPJ#.@. _.JK)%'_ǩzș׃ac%6C% 0|40Q|y Km #.ySֿѷY>nV}7>hw}~1qt[M[qO͑~ݺ?ߝgңGNKS/FeSqJtiqE?#.kbBۗ#-DK%OH)O9^xz_Fq#}:p#_ GpjwL{׾G9я~^ѧVk.k_Q+vUs/WjwF9[:k]Y7W=Hy{SO=x=Ҳ7U᠖An<3lXƚ^{'U׳M+w4O6W."|'g23½ⲩkrne ~˴Y*xE+᥵y~΍s)vҨ*aIJ]O8(m!vOz"~ni]O7__qls{tG.mr+˗.9-wL$]a"b9;(^')AtaV ڏWtw}ϗ>IcR4>=\UZ̦`l$җ,zt[t'ԚDnoe6a!a^?f4n*xK闼0߲yUWsBkno˿iɯ%~4jS,$UzpaU|MY]G}+~bߎ5,LO;uBvc:zU[M~e!?ؒYw^Z ݓs#EJ%=lz}M4u!>{_F)#.R[tW/W(l 4R<9~,@($2LH-ȸkT%a __y_OyٷrÑ~kݤ=^mGn[=ĹEvv>vn׷A`Zz7>>zGQmq>/gG_ 6۳ն3L:~y/7P|_iIQ{ŮC?)G0{K\Nx<|hTo57aCг7s1lt|f.8%#..*xz}]2īxم?++I 쥿/MX_?gCl[Nmgc=>uZChwc/u^CV5FwI,4`w!]=ՋV'nj&]y쥖՞ |[jUO^vhur*ewJsvTLщ٤q1; #-${o\=6ig?N?7eUxumM$ꪎ?E+[ThΞ*|Rϻ1,%04The{뷉96>jڻ1TuȠQ܏;"'Q?v)?Y7qՊ߼r%u7퓮cawY(F$.kmypo6v靈A,4goFVuh䑫K$#YSOW- uA&\YЂldZq`'/F1N9:l.diۮ~v=Yu7HqgH:' q(pU_NFY٠%,9aی/MVS>훷|5v)^/!y,*mO˟w%b<{wF=K|io/|凇i6ʟ. D"{³,F%x{Mfrf>:IO{-TSf(cω/Rd5vgEl}>ȳ g]HI6o;~@K2Fې>xAZ?-c#-цF8"hQZ*Th#.#-#.4p(i09zkmmn'H%J83!1b8&LF"ʚM&Bi"F6ca4}Qji"E,1EYY(?B3p88+È`5*&=nqa#ϛ| k=P]y!wͰ홝wytSo"FuûG> ^۳OFU}uOXc]Yy-3ׯ+q:vr8l͛rhSXޓ{t0j?63EP]%2q3n=9ʕ6GNntfT,軿dIfy^'UbA޴A쿄[UB ur8xP` 2֥}Txd䏫@]5^|w哖W@qlRwI%kNrlhoDֺL/{5CK7Fa qy==V]^ȱR()dY*MLn?,Z&uLDRD`ZFXŒ~&z/hx(_W5A5#̾ve.\ fWrf!#6cLٚ#.<ɁHh8SzYޅ?F୍c8~@L2 !5${ՎOgY_߶ai*,dk4mŗiu1Fj<\#-x`ټ93ƾ'[ZYI݆碣D̎b%۠'/'~8&tB*DԮڤldq!حOf60 ea\LA|z}^ϫٮeP>!#.)cFy.ќT{1,R2/h>LgdEbs&X;e̳Sl#-mI%)Ќ\&Ra<~y> ĎfQ(醫/9ԦgLD13C]Vӎdžpo1 %2w}x4\k,B~ǣxy=pe񚈘1k-O>dGXs8=)ipg!5I!t> 5}lG@(ooꪔ<\0މÐ\ӽE>z)n0L=~电EJ\O/\qeֆ ":(eCD#- SU*:9xo% d.c Kegϫ]5G?y3yKI!5(;S#-!LWef%qiyȝ#-Cum+2Ӭ\p\$ryh VOJP2pZ=$ɴa*K; XCȇw3 5YVH00VQ"9ucht㣙redWR[%BZ3 Ĥf*)՗-l6^[l.%Qhp70k2cM1D;%ؠF,TRN&׵YrXMJ\fd#-u!jN\v8kmTS#H9'Mz6/Ƽwj20&0w"#-d''}7{-hxGmQYROn<@ᢣܲZ欞HbԿOը_t{քLV𝼳)atͅeI^#o81ϚJzAJB.k}x4lմrJ^N\frDT{鬓%(N>#9o]]t?sM֯1עa;Y*RfX]*(9q܎OQakgd1uu{&XÎ΋%K \^ѓD`aݡk#. {7Ac-0t\smkgE nkm[exdhs)֫vIhFVӉp3r*^6,<8z*惺yefk h-(#伌WC_x׵]D5覐Wnov^YrCq.ڽbx'sK5-ZDZ]m|m5(D8Eç{3KJ6ELóW2#&b %ZqZTvC A&SmUDc##,zRvqu|okt/g92!!(rD줉iAzlJ=wƺX>bG`JǣG#--֭ކ{QHa#qbMƷlh|㝶{eL\+5MY1Ȣ^gan=eYȂH,7wDX"B$ n'>i;W,$~<;5~C7Т7=$oeDWXF0ꙖR{`X#.gg9"[kQKczKo#.E1S%Fj?>Pܙ&Ht41R%31}qɽe;O,v-9'!̣}܈Inh{aЌؚTyV驵3#TDMQ"I:dCTLTzs-V?ݺn2}Ծ)$=<86°fC!~mU#.F%Saƚ׬Z,'U-*s=eMWN'إcNbQG_MZl*4;ج?6Uʭ-"Yq"DOukOsOV#-b8 -ڻ*՝Zm^Þ _e}F٣)|g=qH$">\tzo>#.ry?Oi1Y ]߅8>@lsoXodϚumUoT{:yz0Qn:?ʳ4@SyǷ\q2u#-+/7=wX6W9:#-V_BSG}|ޘum*z>KgE+`s+MWMyRl1Uv95+m ЪeT"s35*qWhӿ]هb&j'#G95T\,+Ϻ<\ Zs.A$ NHvsn4|ͽɷ#.aQ8)emVp)qE#-{c+<ڎٺT"uVNzgۄ[dZV8=3:9]\VNVwL]exO=M=I'5}^G_hCjs$=/1k}tQXhQ: ŋ Y/X)`<[{]QO1Tb#-<wg_}̿.\Zc"_z9y^Rti H\9+__W7̗.kdƼ3ZRvΙ3]8O=d?1ӣvݍZќ"qfJTn%JCL;L'z$yaNa>P jΏPMuKD#%Yڃo$;U8\A~AoDqJ&9J(VuqS.ݩ:úi)DQ~#V;p t]~+%}7Z/]XDjj+*$/N$©d" .˜#zv$jRl5ó#-yfPݮ6j%YJ-79$_¿n6Zt\xƴ|wE܈F}: U*UOf5Dî/ݢvZRُ:s.Ҳl[8a+[TTbW]~7Z;xqmuM_O|#,Ey^Knwc{Hs_pJxq?Q5ޛϮ5د//U?#YACIt7dEMotʻjlͺ?gJ-7٫EݝК÷eŹO$RJr*zweHgQ%;=%.cTGҨcm,Is`b@p5"RȲA걪>mêb)螑CM6yΐ?=c&˗M%ybC}+O5Ӧ5xCu`nKjg_hsŹʎrR <"atۋL?3rpeGid'ǚYR1tZNĸ5Vϼl6gM#qquMxїo0uww4BHtн'96J^Ѯۯjsgjw|!N#ԡe0iԦqF/in߹ջ%-Hz.|s*k#-)E@u:gmk#\xys7kS"4T("hXE[njMst@=0aRl{}d9r"I @8|?| ]ej&e\:2S4oێ߇siPBWT;~-d`Uznr9qQ#.y}6R:\|ŏbwLUR)wq:i%RqiǮR׮֤Rl"ٖܢ r&\RBPI֤#DJcˆ>-ΞK]MAp/(]QyG#-Mtx)/UX6`5ڣVֲ-IW\(S(<3BFgΨ1'+'-w:|MxZx9VڙBdB֏߯/^⹋?MlFmOo,MΙT^yy^6] 9P$Q-"6V5LdD(ߥYs׍FZ&UoZXwh*YKiZ!4{m_Ñ]s- dZ|'?xr4enYZZ4?XlFHNPzHjK?1f,7g¤d0#-oCT?t,H8$*! qs ^dS0sŗVpljkEhG׹7t}OlV*4;Pr6q:9wXgɥA͉_voT֎b[pO^ok:ڭ鉶gOmR$*O _>yT%8 !%O O?сcHU!oSgIZԤU#,@dE}iԸ=7hmi9}/w'LRI┎[G WtfuSƩ-u/-.Ct]XE VZʏ4cjAb4xj7JPV/ͽ6 *CNϲM~I#-Y#-P7?m(vQ8ê(%k8%D$U2Ⱦ6TRdI:nI7#.ɘC7#,l9؀W#-#. -D5U`~6 N tu@Nc)mOƔPS*Bƽ; {~{tyʆЍ(DƷ/R}2AsZh\/O1#- CCc B 9# *1,y9ٴ(9MK-=b9L:po$xYnU0)HPd6+3WxQFQ=Ķh plD3T]%Ȗ;_a ڂsRags\xU=|x7ZFyޙ‘*bī p#,opl赯WO˱YiR+7?vOsʾPy0~fzĤ!XdlBV#-*T;=!ȿ}wy7vΥkEOfHt#OǗu+Oy-2ƈűM{MC!o ٌ"J>e;ڋ;40Rb<VT3ʗ㧷|<1eVM=CRI̸vU ?VlzetwV>X#-㺫Mj|z$kbW{)r}=VXmW:%8IRnmz+|wYE!H9I5{lF u6sQ\VFzr1k`aX# 0IjvlbRl[*MvX~H7P5v:|1֣V[_u5l-#-GL7)s$-(ؑ om_#,$.ȝTf֘? hָJ#2M!xtL]UE##Ͳ #u`F84.5PiDw'WLR#-&OƖ#6x=W#p||P58w#-oR&@f4Esσ^Oe;"Sf7Aݼ@+F&&S*va[V:w_~2O*Ƚ4˝Wy}<ᎎzn3C,٤;ʺ&AVg4xѮKax^ܲvtm}wF&Bƅ]f5*묨57ip&G%&$4?妼S+uqG`LU9h[)+'7wG` (PҬ&x!ۂ&^ٮoj1oyt䎝s &`MgIB:ŵZdB{ͪ3Sd)1l|U~;+i"̓t--h5V{$SC2CC' N謆3L:nm]Ѱy㮼4hlrdA!.OmW̔ׯ_t_(<"Ck~z#.GDDa>xyî#-i)*(TRkwLbwsm'VkEE\ѐX i1\}M6y&M(z{8cV79SUvjD0U͖皈@\׽<#-g<|Hó_#-@&pA*Ӷw:'" #-pgf5hwVi./_WKZ׏=V^dF%c62 PD0hf_S >3an>|/7Mf{c5iEKaM?#-~>#9L5g^[8:%7P:B5D4*ڡDTi$kԾl4"Iw_m؟;SBM4Ƿ'-t& )4Jf+4}yyyBT@$wsURҡ3>CP:+\($jz29/o1@_##.׺WCP?WnY_[{~ٝ|v:믕dkpb0AWh]*>7$T8>c rQa&9΂ܛVcSG1b|EK||pt_Mo;0&HK%N򍴩E,:R0!f0cyñ>q61%0Fs F5cqbK`s842*:Q5 3Õ_!Oʌ'M/kj=iM:Hч,";2%^ D:g'"&vVz#-c]#r1iQ5t)f}XɰB-a6!y#.gPdt n'h!a<kL Z7}bjD,葊#,@֖쁧fdH0 K#.PYb[#, #,쎔" /*v%ɳvEXdFK .BO`CcT:-xW}zwzrɗϡٶ&r@&!S/HZrk>oL0$㝝#m?Ccv^~BSOKRlrff#-T)BLPEK#hWYTtm"9T3z90^\VZ̛êb^…bŔDtIږpMڹ5`,MDfW[iYVx܄p݆9#.­K8(Z+e r[ƉpL`eL4/rU#-;he~7)#- |h7zvM*JtHtP#P#-.O!u{a՞ ќfݬ8ZϯBGϑ>p# >p)*RD@$X9Kf:`C"AJ*gfź`PjmG֎u$V,ՙ(#.L &0 +Ī3$Q@~#.2 %/L?k8Da}#.T7Klyʸ2E0ƦAZN#w=L8.NuC70HRI)PH QSD&QS" -w[km9gat] Mxca 1LʜC^qɈ!Jt&@!Lhَ&Hx>ݻ[t0ϕe|;R#- (Q~[xaϐU#-Y'i!T*=um0%@P!DDɿN\d7N811 ̀1:g$3#,c:gH#lMEV+˹>*eS/^rp0Qa@יzKͺ 뼛&ؔ*% It,^c x6aB#.q6,$R+ĭ)RԢzf޼;[!}>S#,RH,4|BBdQꔳ+?[H(h4ܺu^I=@X)hZe3k#,`Z;#.覞Vhxje9 /r$(td|jI8}/NY' X#.'7#.#-zuW#-֦2#,&#,pC0h$w!29ݱ4T+%@6hPtwΉIX""Ad(yy.8wʕBd-,简e͍.RuD^NI m.oX]mǍֶPP¥7y9mo!@$,]#./w6t#u.C]?bCw͂J(!2JNhJ;N;YL>n\־\~81>Q^E3)J<,t}} mv"Q^y)]!$ﮱCI”e{}2ٮ8I#.fTa,?|Y;t5mNN}j3'oO iwW\ azHkf#,(ՑH#Ҵ#-Bl_t]=bMu* ƴ'{1[0>zw]vQ˒DD-a5yi^*j06CC8iT#-R$ G݊Z0:먇)2*)f.UXd퍂ݶdž2 .󭔼?L}XKK9loܷ#,!I'D΍-2*tA"iV0#,;1?eZ#IŁ]l#-uM}vl iݝu:5DyQspX*a #- cHvTgePƻ#s5.p$Ъ+KB"A}5IfI)L|!m},L<)j%6&M"e&1pQ uˇa-61: \·}ڤE fۙ [tӾE'"t$DNQ\#-}]Wr=Emь>1D/X2XrFz 8]ImAjkCnBL T}oc5ָnkXcr&+"'),@J&{ 0^P#,AAqy}&G;=ޅP5PvY|`iU#-22jMxmjԝ僖%䔒4B^ě\AMC!cTDBTpc .M?H? dp"B6~94PwWBST3AID*SRM$Oϣ{X6'#- >CBl>#-fnIa:>!$a AJ&GTs%sۊ,Zx]P{AQV1кugmmڬoz<חFKYn;/U ߞ:sT=kxEKD:ATOUDn_HA&y&DCt=aVH**#.,~?N?eG*cΌdg:EDb#,5X3YH4?AT#,ĔGÅ/R5<͛10\oA/hhblXHB ]ݵ+Zr0jP%b4A},EsV.M[A6'@lfb9.H|;0GvttY'Ms;30GZd5ٙBR=]Oj܅GD'z). uu)Zy~I> x3#-Z[4̆;(~=:_3t$5x>o]p m8<#ͨYǐ7ݺR;J{?V/v>S< 7*LC02Ty9F-W.xaꊡY#, bU駪Tk\7z=>Ve_Fϧӻ30CsjqrkNg&t? aWbѶfIFtaԼLӊN8o\SΨS#.$>ה{4E#-sRt:.Ge[绪}~;4#,&Ls k#-Ѻ%*E9+hI6#2nIbI_g5xӫO`o~F?۴i` @n՗LS!WIYI(V|:$*30mu%eTnRysn]ff ̗g?Z*p]Y6fM)F-omxOB͎\Y64>蘀D/.63~w_UQ/S|1EL3yYo;LU>\4^VZ[g.#-rJq²8y|\lST]J<#.BJ)};D%َ$M9rA5<פazG$YA_Jidk:߉[şsUn a>BQj_HG-BRTt^on$)o_cF-a/b + oBL}o:>U_7@8%'\(u%ǭ~1qOܕVN!ֹm)AxwT#.ϏOw,DxcA4pynPΑԯ+ՃSgh2cjH#,]8j|W3"^v!Ħ G=N'z9vtB!ΫG2}tPD2+ޙ6xtNI$gosl8~PKz2΍jtAylU!]WJHQVYK#..R?inO5NW<3휹wa+H :C~sq^Gt;>gOu!!l]9&hԃ=QI'qN ng˺<ozpaC^~J>]Hě\:&}?5EEc<'Dpy7HBB1Ϥ#-]<3L{'I9o8=zX,F73+cl:| '݈DBgYqXvM.eԧZɧS9j)RCy}TB%H Qgɸ]@On#-nOR\rFWsĩIpB[9qq y?ד_ t:nvԿ:$;%vn}Kssݷ;xhчY}ϔ+!t&Z;;JSKZxކpsO6h߫8RkiMLt #-_? od~x}T3ZJ}΍PWZ-!ޣBҿ侯G1` L<'nE$=u?:j&)%/#WV`R<8p}-T~#UȘK4ub'"w8Wn ,:N^髑Mq(aUïLkf#. _#.Io?(N8DIm@ysTcmYIڎ͋K1r`xzh0tP>L'6Nž1ѧ[ZXBtKJITh3JS:Fё4{>2RܝF֥ɿ˫u\tf#,RW¡#,PD%*@#.TQVѹ1%̚i#rӯ^߿9[Ԃ/L#.'JU|9WEc8~,AkIH/V[b~Z棴hO:15OjfEr~CP<kё!ڟ!WXuu]WCuj$ei$ᙡ/CU|y:d"#-~j}ƿW*U'* ?UX-oQܤ$D=~Z{yU׎/E۩Dߘ4~>of@'f6&#-0*;~7E}>P#-ZxL!'sAԺ#.p~4~Vyc)?~3݆*ڼNOjr3?f[L5A7 5!V)yzGT8ujliPtX9$AɁPՏ3뼝RڟJ&ЁSHq9 Etr'43ul%9#-=}qLcz;1? k#-nn5n+/%C\WkT`M,ι*)5|c8[ߢ>:#OZ| C(Fu,j1|%ݫθKӹivATQB<*1OҳF4{GXiscĚDK])bW#,qU*fW+ OwXҪI_I;jULAd1[?}}PP]bour ڐ-#.6;CYϿ{ۯ&>_..>t{W=.>Xל|0ճYI\Ɓ[+NƊ -D:r<x = 0ycV1<#ߛY$g;c,i+И}Hm8)6('V3nTrnwU(ԇԙ1(TdTH;AyZ\l֍;RT1*ضf*÷}4v33NmKoYJf}-q䝻PSLV.u*knNXp.x;]ji[Wbr%aĩ)}TB뿺:cm]?wu9~g~,Oi}#^1&I-HZqCh΍׽v[vZF.YNTsjzN#G^w gQ,Go x+xJo(Y3%=!0gP^qfZQ#.^QBoM^i+iwDlvwp/#-M:h0|oQ>Mq/<^w2ܜ6z}5rFf 'OOEJ8";߱-W}9=5tblLad*EQ2,^ɐ{t9f CF_TLg=O(ՓEbcd#.I?W_pɠN?<ܫG|u#-Lo +VeVY]}r3}.@4ٹM…Rk̢ZQ=P|3tfbE6jhd*4rVj tل}U%\(t09et+oj\w.V#-1mN(8s>?Nܷ6K?ɜn4G/ۤـj$P$,{0,[^.wN*{32鰭z#.q0sR`z?=Z0j3y;,9s;٠yI$ȵ3u{hFgUTVKsiJr݃z%) ~taOMZ^ g_O.z=;:l856EfR׃?]~HVYdL5?;[#|Gﺃ;N3UU}F?#,}^ 0("P`k$:p6 b?UU2XRGb;,J,Pp",#,HC)#-}C~OfH9&,2?(?@dpܹu 746mwmxXҧq#$U$Uut{*QJ1F¶@[0/##-qЃ:PtwdK>@Q`iLA, .'R%э]@_4fm+XYի$Qoz#-p:z?FD=#,WG'']G{ XgT=Z#}#.o $C#-O]+OԺѦ:+Rmoz=Ss?6q*IΘtѠ Q&Uﲻ }=@ߘ-$`dL,I[cZ N#]N "m-6!I0x,ܸm;t ,ćlI#ʙΖEy{NO- Sϱ*@ $7]\m&;,9dh|>X.AE~fdjzE*;#-Rv=hWN!&496()caP3}D7(~m C2VjpfPpe譢vHKws@4nUR2#,qDBH@!`r;˙pjh\cpG\9ۚeҏ=M|濯vn3Gi6+YwI$|ӾX,uո8g`#5r3^w;###$]޶:rXa#-ѺS`Md4 0䍍\u7%%?Y6? m4:'_uIPl؈T0GtP#-槬8ϸq3|sWñl^6M]8~S8.(yv#. !Wo1a9ewYu6/Bjh\1!` `Sq }Yo?H]hh(u6#-:_a]4@"#-Xyd#-ul>J />xwI LSi?^J#Gi %.xda (]g| 6vrƲt϶;n\BSHR"D:jS9+33R-Li+:OlʰE?U wd8 nXH~tp:AqpbF@]0GisF@|?Ţ8"}.rrQ3ΗI0v*d9+7wuKX7SoZZUf5%q$yiI*U&I*mGQ 26$L:3c̹RE'@cZpn*YEY.6plyȉ"&`RdL^Y?ÁznǍC, 鱾~8Cpf3 RBIC^PSGgw~DPOO'woB2$$EE/ _4#,H\! MTBދX}QMNqFNn{qmpP%a$]ìD3 HX\#.)= ن8e'~-GS_PZfYz_NBd?`PF&Ad㾀հ6I$`E$C~:iTѴă_+0e}юwcvٰp<1t#.3IHr9+r2 a#d?hsgȹO6R(uɱ{EK5#,7Fób bR *&GnϚW һt}\?C;I-I-)?!>#,Pv@7 #-珨-#,ո<ѾBѩGQ #,ž{8``ȃ5bda=H"${5"FG(㐐2l&c;!z_Tge >QZ 6y=X2~=P}_&8^oh D2"E"`@~}%gj~ϥ5kT fA+auLF;`Yhp8= 13Z6 HGUqw!сa%wh :G@l?=0* DB,)3n2(DЇzs|~#-@fzDa՛09@tK^M@;yCpHLW1ɞu{C?7{o hfv5)YSjv!!ɒI'AO 4=bڐ-r4l#,Y ''#.xð-j;ǗZ}MPDVֺg&(Q<_~h>,gJӢ=dMéyW*'PLBִAU~B]:,6ԁƪ3Z@j!ƯH *%f ԼyiH}("7&Ic!Dč#-2bD㳩BC+=τbf#TLH`%Ǖb;o1`F4#-5rbfe}Ɗ IkvhHb2ޖm!+M-tsOB#,#,?8{,b+!P]aCWUګbU6Mb Cd*#,BjjZy矹B63;1Gx#4(G")aV .jO "SyL|5J#.$V`D#WHs7d&:ޝ*:W& yr߉گA=&FfcKjgvZv`P#D26mkU]rj.!G#,_cxbߐD q=ϰ(8.a$ZĢf/$5Au_R5FL@bDH@m.YE42/T#-5@uw ټt'-W^|!5 ٹ:m\ 3w`f=lm8װarh='f!y`MVtz-'Qе+Z$'pl\7tJV*=#.P>#.a%-Л9m  {yV_WhAFH4!ިTycj_U #-M1@Qx%H4#A#-frPd6>;>al#GFIYC9F@8ؤ*7 P>=~п#!';Zq׳j( !lEPC_.} ƛ)WJ|7#- Y1da[$",ܧHb-dl7(>aE{2L*#FAA߿c&e$1wBDDDJ*z:X\ЎM3Q_/>b7e``% DFe&Q[Dpҩ/uAGy>ac/g%Z)`"sy0oHS4 @p?$.l&;Cp`6K }=G#.t^:Ck|&r (eLM@ucG/M#-Nhp'>ZEX``,8}a%zOW)%lg$Ι6'~n1BeZROegu%hߗQaIzB`I4B1"m0?eZ8&NvѣEE5s ~xuNmYI俴G2OTaɷ~9W3P&i]8bZcN,ߨZS;V:MgC{D]#.\ AFi5ר2(:#,D=0I1"B =T^yTLq؄rƃpF뽸Zfa9~DQgj7F0rB* J@m#,Pע'd+=L8A*Z*k׼u:# P@~Ԛ̵)T=(I㥻"g#-j~,|Qx{14_k)#.)raݘINWûNrMogo PE}r %X=堥~T1#+#.~i=>yٹR$""sr=IyvT6h"7gU.trf`?@\)fLR5CsV] 9؄#.61FL+hWW|?Xwؓ!bѠ#.C儴tY :øW;N`>6(1"`vg.ϴ ,mFMyel^=#FXbi|c)ШѨȟpnߍC mbN#-! {^.B93S>{+g$sld +#.B,Lf=f"dK\( |l3BeaVs Ny"Ӟy7.ƛR3Z;}Wn89#?ʏ_~`RHWG!a#-&fHwS;Lx Bd8aMdW7w|)_ղ#m#+wg&יA=>1,e"#UcU$c1jCF\8Obz:<*@ D7~Cff%RȯTeJ}5Ս^w,}/$>^ ܘ=ae7@o#1x`F2=}KU۬)CLʆtwρor^_$#ax}}vZTT*97ifi)wxd$bvYo}#.[_ä%==| [gsK>ۥ@jEs&ЂAx`Kb7)#.ݍIkqw+W«vd;!#-Zo.q2<5v*(YV E#,Q6#@cdݻh1&1(X*+Q#.h #.5B6 Ph I@X[@@HU 6ϮWrs:YĠoTS˗I1_?#-I1ŋ.-DI@ITox.5w raD o}@ Veɹb988K+XPv!:ov_]no!*l|yн^8}-L\EQ#-B@Ȯ}ŔrD)ZV&U(6vUFt;#.5 1R`!#.^i$/,ߣ$$P$ÜU(jHQ@35T.9G3};G*=2Q1ECD'9y*;IV#.uی&"U*;~92r,eXw{ &mPBB̞ăP$& . ۙ?Gk}>V??XQ{# VͧnҀ=E`;^MvG-P#,f v71Car"$ q֚ÁݲDˤ71i>LWI@o!x'-N`H) DPN-Ft)aN">bPcL#F#.t,e&{kCXQ LED$&}NU_FZ'I>=;iG\;\׾f DL1L.w-/>6E-s.2yW=-WWŗi辙 IB_]scy{hVD20|| j SkO" sW>qe$MVզmIR68ȞsI?=:K%N/8(pTf QƩͳB}\xgX5=crd!WujMFchаD#N]*8CU!&+"S|&8>eil8hVnRZX2b|ֿ濋JЋ^]o('\J}үpo5|Wªo^V]V^yZ<,E.VMmb⎼m}O/N𠒿s71vU;ɏC]ĎD>p#Aغ7#,󦦾hVmα+vD0$^eK$ASʪq5pdz#.[##.;_hf7}9О_7sfG},qd0iW; _i'$'2n?^ʂ+,E]n;6JM+<_}<5uZ|J-Ҫ{mre\jw_S9ʁ炽*'UT#.d,e-H,a.ʫi~&%-MT75mo{ylV7qp~T6AiIRO`{U#!!s^;*iȤ/q0!%}LŴh$rMy-.HQj#-ViVBG!{Չ/n)#.u.dxx@a6iAs{a(nh-+LUd#xit8 %q琔V&#.$F~g5"d=/oo8Qoϗ@MK &3v۠\G VA=0 $)(t`E!EQw9d3H@cBQd.2tKGDg[v?_'jK#Ikbpg6Z R0+R\b ~xuUO#, tiwNLϳk[Vtrcwmfsl,|=p68ufp\xAuhG^ntz`"q.xnԻ!aLÀi#'oM0C]z,|eja2~#.C︫f -ŒAPxT& WY^h̍AݔCCC#-=zd)wy)M+rx]CŸmA$èq3ө)kn(3l51ݿn0gpoɤiWW##.S(T#.6הj+ܗ7G"&- ΆȎ KP3@ #-K~3"ʑm'hԃ9oVqwx4B6S9V;0 QYؐM >itӕFsNQ#,ÑARX7C4Gdּ@t=F5wYGkMnLX[ #.nZZEfBHb5C3cvq*˩3Zvd񞀓v3 w&'9IPHs`cJ`i-۝kTҦܦZ]n':cU>ŨԚ#-hLl#.HnA'rqg#rX٦+r,8{777EѹșX(*bBi90XдC={aE:q c-ʱˏ32e̱eʱec.CU{hi eX`IgDKmǬՕwl^9[6Xje[q![VJce£bcv!.Bx؈'ϫWN.^wV9 gZ5L@yb$GE>v>:V{xb (7ҩ(;l@8 sdL|#-4n5dYm2(DI#P1#-J[zmgXqrAgTm*26\)SMMaplp 7(,BU#.S9jԯ!9l8B n o-#,t5IYH$A3a# ԺRf׃>Oo81&k}?{aĄCH4l'iSȘFlk޲t A ;vSn Sg{I{ #,p0{]]<6=V8{J(@b>\ME3`^HB#.24O8Q))2Qxͩ\;X"Sh ''s4pL#.DM} e@a `okI`qAbM9 6SƱ EAbFM#,ګq 붣]_U CoMTt^M7MZ:#q@cn3\#!n%%=z:LsF (\XL%dS-G[.RSU3Pobe6;6L\)d3:{[x^IU44bV;&aO%P[C#2$́rAg#4)fyd30A QFcs2ƋA+ H&mwEb&zʒw٠VuJ}̍\t˵UUU/yˀe#:zP5tkI] .|~)0ڱO%k;SO#- @&Eۘ)* (h$aCm%A=F&hds #,1y3J/h}#.20C5G7{9$3cclx PW=>GM;}v]+s<~ZR D)}-sr? #.y |{y?DVŲVd?Ցu#,ɩL8W{jfX`wۼG>.X`#)"4$J={ #,C[TP)\I@{k:HL:pK(&R"N2!ADdӂk 3.J5H#͆bوT^j]HB n73@{eXi0[y9У-@iv) l"r*lb%N$#,}Fg@K SͦX6ʃ0%}=ۧ^Uł 1ÀP투㛤$@@!#,}Fi#-#,8" r +$=_v#-11ijPS KVjU]m+#-aM#D`VOCW,jU$aR Ƌz뷏JޤWrI/.Xynt1.SyNr{Ihφ$[/{-þRfHP*LMyy~^!q4iK`#.гUPxA=vCMSmx#-2O,x#.XY%pZFJJZkѫIӲ#->Q#Ӓ&Vӝ%!pU]kE" H#,m1##,`HOg#JKu*#.WDț۲>FW31|2*2XqH)NRzk0`BL,Lh6tj}E(QRN;ڲJiK#,XE"?cffWҩÈ0TNQuKtIחQm 9RSfnKdI~nݧ?͖UDFEG| ;ԑJX\.H0PUQMTdTKclm*USl6ضKm#-)*ZkZ߭}:6ʓ8l|Dd ȃ"s[DHbJkOΚ"B0h;.<22!j,uZ;UT/CAɋ)Րw8 wfϪK0XʰEj"lF"f%_g׵7(ݢ#,^۳:#UB$R#-&"f!"NH;A@UP;6m$x8N!#,2 k2#-QS**咹kli4Z+Z#.AMy?|nCX2@ 0Wu GP|n8Fi 8DEzzuC<`v׿ڲ8=ڈBH^=ޏ]r`:V8>""H_w~#-L IF@9F*vޕB OHQGmێãpCS@D@#-p_Đ>Rʩd;Y[nfe! y<%UO[]~9I?E";A#-R gYnB>'rt!OPd0/4171"cHn,VluH7{\`>tGaۼ6Hq@ty` ٠z(531:ᐚ8m=39j᭹P|.~Xye\T헝^x|L-#.Ԛѻ۠$(L$j;FƘD/O­#-gF۳^ej=/`#5!Y9p'h #.,LL0-OZŷ59 -^4H#-.pNhJ|q# &Zی&(c3JײŐ[)V_2|ŽP}.<"#-ڴiz7QXCɏ~¦kSUEJŗu%[z@xt_]YnJbȢf(e#,&'^Oy`9ln[̀#-̟Ppmӑ@D#-gWdY0:lj:x1ԻK8I,OSq$HURF"r#,9׊0AU)AX_oi=hgm0BH1/W#,gՏDZQzHVY433!3̡`4|cAKnxZ4;\`U}c9"d(rr}MƳ1;z-#->*]}eƫ^aŐ?nPȆ9{;7F uϲ6N@z!N5k7Z)dR&ŮԳDjwֳ[)"ZHZQu"L2.HXn r+vd#-E``4n!̈D (zrpCX톒ylxGo5mq3<0 %ʯ!9'粈J$T&äHD;0wQ{ۦnuD8*גݗz|z8w7z#W$ d zݔύY"B%ٌgb)$R Tj#-֧ hn-kF*#. Pd#.(@ŘûY!ZIB"jbt&Pmbi&Qmo)MD2V(CLQ0 u)I+#t}1}Ȥ>\Ņ4^ȪTb4,JBϧ,|abGW"d ##-L͘$9f( $tfZM#{Z+xլSѓYƔq=[$8R`<3(N4#,,F($/de[ڏ\/l.xt> QFȁ̝v#-D[';Z۶ߗ뿕6ĈjSRO׭}B׬JxvQxQ雕)3U)F"*-mR.XсLL7^ #-Q'b1 )‚fZaV,kdGuW#-#,chi H#-n.=LELc#,^`&I (zGT #ы0@!uEDANs`#!vá$X#.:tBFY07֔ᴛwr66죟^r[`e$f"6@Fa݅)8ƀkuHmh#-cQrJ%P̐<m.EeLUۦMG 6=H΅:hch'qPXP8ᡯ鞍m٭e+ Zje^B'L`6FI#,xݨ6e){wy6y~ԗ#-pe^5@諳`а^NmuIu= aۍ$I=k<¬OnL2"7Ws /n7 3ZyR:'u7Ȑ'Gy*\a޼$3"{Wx̹͜2s#f#-l3.Jo1MeC7aH>k:52x_a#.Q9#,Ms)h,2`W%E*q#-"60;#-WA;jEK0bY#, ỳf'˾U}mmNE7)QcH.jUT"aH @"0@>XdG`#,D1k2oU)@#$p`p.Zq> PVKwU :QӜyg_O4dGMO79`+(ƆӹJq$!2X.`q|6@_г2Ⲱg5'c^؂wEH@Q.vλ MDPv(Q$"yչ&Xhصxحm-VP "#-HPO=2b9XM=J+tKvhR'L_7դWL($)#-6)fK1I-&j2d6T!%4F`d%fH%i4hRCf&Q3#,ژM~ SB$RJK Aj2J#-FRX1JFbRl&&Q҃BF(owC;w~rT/FBJ/i{oL=MԈ0z2>mסhꑦD)${ًn 7jf{&)GiJo\],DE6x}$g,&W._OQݦ͝K#,t!R:=q$اSf>df{^yE.%#,{up>xqGL47fE @@sumy:~ q:[ć orAKA$܉ex7ܣ)RόB sP&x߳f#.(?}^:i.H[}fM32:.1f$Y܋#y Gan0|jP31hH#-C6a3#,yExX uVٶS#,7 Ys$X#.kjS =3u6W9y McNUUx&j=]JH5 @zAX Cs#g^9d<$r,fb@ׇ%.`IF6eEd>c_MIeB#,2"d= wvQ &[OC4ȇ#X3Nݬm<8^jRGjO#]7XZqm&]Hnœͼz{XcY[EYKL\MB pa GK"$WQ*TUtZA>3CӢQ2Z$Ľ&mJ#-YL#/&VWEż:YL\>_MjppiփkRSOyD#S!'5mrG5Tf]iؘ/7 9Փ} A 3LjKڝW*M(p!G*M83p1 43yYn!ik8 Qګ9#[hDwL\9z:fBPwdQjdcmHb:|WvGn{duFpzXV3;1 )2;94`&].'8,AñpEj2jZc)∮vhLQ!T-%8:gPēn8Ns{wy_L려kL:Cؑ263sЛ1rAqwhӴ7s1Â,NN͆˄Wۺܮ;tz)y!AkKZ뽴k*+F0Ql݂̎|˫#-g ,cp'Ks \ ϕ1GQ쵵B%BM>0#⢩2XmEj )ׂ:,\CѣblY(jf+H Ta%3 8.cG #.6rp˛ǣ}#-f_ 8DHY踇Fۜ[o`LouB\#B(Zڜj 2sA%ȇ1Pu刺 V-ghnK`lErd:;{һވUE*bAsI`TiBfuk:ms]!u- #.cQ(Ԥ(bdO/8r!<&*BڐRA64#,OZ"Y H+C ƒTbzoڷ:#-f)a0#,pTtvDT"_xF#,(zPp,#.6qC'`d(Q3K#jb JD:91ieu(v)7f6&¥$,4HB*و@cpFD%Ɔ]L3jݐe/u;̌x7!(#-Fb1eP7dƁm"P#-XA#.ửԔ:"i3m۔߉U+]Ɲ]6)sK--]ʡ%(P!`P!a""h.Z lP8F#._~(Ӵ$Si((-}eణR@вE*yd*H2 UP#--#.%QF "iKI,T vZ_WS1LC̢e6T J#-su/DpdXOЋ*z@LMۨ[,\LҵX6: 8\0>&20]0H@pvLρjA#.٫T4p#-fژq78r"H$cШqC%%AA"B6`B"hMw(l"./N:z\:DqE*-UF9yV<@#-y|3l@DWoݴH HB14vg7DضyEM Tz`v#ğH(Myp /"8r2pID—tj+̸ȾdGŌ#.J,~|IQt_m\:zs3Qze>nmHĖT-乪l e ƒ7V2`]PEяDd#.`ZtplVM)2} *Ci{J!i?35A񯇼,TlK#y%̵GIR))iJZ-QTR3Q6hfH w7z3@J=˒d/l>ͦOlEO/Q|5u*:4$i%b"nHro!P.AZ3pL.10[ /#,#,Z%#,4z=xsо0hܢ H)1EʼnlģHT#,E#-F t8CuW2xmFp~45^Ȃf0b-݈ClV#ND8'6#/BlHh2c8p:Bd#-4˖jZ i:fy҆r \$ny1뙂Ua074#.3#'~D QvɝcC9AprL!#0i⦸*ea2M'g:g"K` A=:j4Y%vfыp Cf0M!czB]F!MцfC#,dXi\$e25#,0]#.lXM#,1p##,K\p#-c7 }2\P$Q(Dm]^U#,~UR#R,WeA'.8 #.#,r]NI?h#, ?c] z#dI$^!6$KkFIH9!j|5ǼuV*iX/<"2Wz1HyçV,W+l7-vNhAUlrÖuӆ8i25uD&:JIaj:¬Sf|9L@&I&2`"VPb4e9yۃ)#,1 RR#-&itŌ7k]6Iig'Tni7!FcLlPJ1c:DZسH%1x4RÜtvhg:978"d,8F#.E!ń)O4SҀ:ֹbFP:~(Q4^,mM/ Ds9(}2YgvLq>0y2!GHC$o*%ݳ[IaNhcr#-`YDEQ@ 1&K mkEFQBǙRm$#-j4Z|kF(TF|ۊZdq@&/Ha Hbz#,Vd&f Q-$J#.)8:9_'53mex{jja0]n`L5\n㏗t'լJzmje2#̐3pQVWioXgNم9fWhXtH:ΦZ;@|0j0q#.#-&P̧v"*U0H숩rlh^Iq#.`]j902 JdrpX2KQÍ$ѽ#-Cx&ɮM;E }/7@e^7Ž |iFG3>6$abIݘE%Ԍ6֒h'$6(mݵfM-#dLka+&Օ2lԓ"$QjmKJԮdOZ^ݮ1MVK*lb5_LJVƤ5Z$%RouuME&y׆Lie6f٭ꭚxJ󫧝Ѧbrjx.6%[[k*K}$n RVF;)u}4ݚL"ۼ}7Eld?OO2r"z!-\0S EFH^h&ӦVm*&i3hƬy/;kTEK[ٵM!iij-BhѮX--U"DM)kP֤j֖E1KII{Zn0jVmMVɫ4T̍Q*&ЊS E#-RD3e3,B%RQljMd6*6S(A4(IjT6jcIJd6(6RJI2I)E,ڥf5I5)eVLXҙ5@Ȩ@ TbM6*mmtMRU  D,B@ 1֋e6W+TD#,"D>^/y]8;uCSx#,JxyA>q7ŬPzSuE.xJӯ2ɛłD{!KW<o'T9'Q~'gf._Kd3 #./C謲(ѢZ]::Лfpo$#.#:a'k,1B2ͥ#.r&*9.S@)Lq`{˭-tؚEIp[##,+Q۶*@#,#,*UFdP*ҹh'8Hut@}CW\ǂa{glG ("oU%SAfP#.sښ,cyJ 95x0dvņZYbv%-̄ɂfХ]x\ Ԩ^vJ04G&&PllDhmBR+tb(YYi?RRdUSF(-Q01!ҲlC*`p(Dum!h *"ӉE?5+45kZ#- )!͝Yq#-`Fbфq6Ƌ\#.&n#.?K0\1AUVi[2KC_7]9qᄧ»ONY6>zSlfA#,v:|c!ae6gux6pAF(!66}<'.T\ rN_i{xkۯe0 ƍɄ,W֊hT ?!Ձ`2M5 ڼlDVڹcfbSeushtbob(&5Tkz[)x?jB"PoaBSzh>8c[@d c:mO#-#,f  j`u@,9K#.]C= >㉠j#-`Lų'X#.kE"n6};©HI9j2H lAכ:ݶ#.kLR$"HQP'r p"d3թ/!<*g #,,:"v-M -B#.HXjyIl:fxc]lv[CN~#n0ђ#.UBZ,sF;#-bPqA".φݒ@5m7cs=)koק>oԣ]틾ʛII߅~1]Gix![i$RDEw?pdEbPCK##-o*ʍ܆]&"p?տ 2?PCqF3#.jlAIc_!E#,aD"`TR*DE!QEflyZ-sZt[P8k}w"e iv oՃ0сr#, )FO< BtǩSͼwSDܞOF''NVr+/AHN#.@Rh$!GpZ@0܅E‘QVHW xU%| 4ݧϞ QړGI!hy-ÛGBak:#7XG)~?#,#g@!ՐB#.B%#<FzD#.%2H̅]WT_V[(*ET?e2I3RXfJ/k0oV4JaHBscq)"¿*Bj&V((IH.RlS6ڲ22Cv X(jԺF,1FBCM֫c෥m6"4Z@ƌM.)u5#,0&P)YeXLT -RֈvIb.CQlm62+b \ir#-*Xc\@oYGvouȌo'9\Iwmj2z8Xb`AIP9Zg#-ZuV%o4 3#,aʎz}bZfgnوN" X3-*kD),$[.G@{zED̚mkHVEil`Ɔ*5f)7ՖY DRF1FDELM61cҖ<Ph:taIAc+I!cei4!Ǧ8އrQngql#,TvtK)-b)ѝ A#.l)!H1-J&Y!T4*]T$u4Fd!kd}A$Kv*5Лrmés#,dfٙ' f1X_fSir%:i  !!e1iRAP1D%'6#.|OYύN!Տ#.KmTs}fJ]Jz|(,JY0UD)-S“?Bg)F#.5u52#.,DA^X9٦h/, ,{4 2Ő(@DQRToKU3sM><. I Thko~uM$\MY0r[RPdPĈ##.ɘ1m5JAIvN"bG p !)%(iDb7\8=[?Z G`J8h&jq@83)e2WuP#.;J=#.%C~'oكS^KJy8@EWHlє:IITi$׊N5ړf8kp'0axh%neb0k,#.2Nmj:3:p$A%,Jt 4IMuV^{ٍzt1 o{Szk0,9lY1u[/eDMbbm3B˥j$ .#- V+DI k@Yz{#-T)ElD@Uo6)Rik*J%l{}ERi9NB#-l384Sv>a@ U}Y5_m]ZRm)h*f-__JӺlkh@}intBǍ-66vOmՐ{3k3hU}U5ca^.lh0V‚vtqڜ'wӞ%?k}$ZKV,x˘3Ö3(X UaA(0YmZI8amt#,&pX#,oU z7jpS4zέNCD|.3))`k̡ñɚZofkhZ$ L$#-瓺3΋OjVwiˈzgս'㉄G#.Bg/|)g„{rN3?R<pi#-ggp >Z )DuN M_$Ǿ#'Ĉq1ʦp7#,GLF 'ʕێϨNPIk,e[tf!WQSjb`N)`' o;)>kb<9Yv9MqmIE1#,jHm/4IR(#_(!۪h [غVYeɟQ-Jc;`dv˷Rڝ՞vӽTm Q~ P#,!E&_y^;ΣqGuP>b[wil<ƌ&.v竢L))AVoN'WtL@ڵŔ@<&ZzVL0 5e!;}"+/ðH|R@&AdT#,VNkYeYYmL֙*YkfUUt/$һk_ S1[k)#,'udc:> ?GTC؋)ƢlfI9?)FH7H|SN!#,Z$w~VwGF!5χ6m$_%FD.1Ve=:YĊY )Aiv |~>$%6MJ26_7#-ɕl<\]f`x,#,SWlO[/b'?fķ9_/5+an{_Q!U]A'gaYi]$&qZ CRyn"KuT_-\OK&/i6LRO4R])B;\obKb1ؘ^2$g5oh%{5y#mr#. 3)ST&8\ x;kxU."`-`{M<֚c[9 P ?)jLVC0i ͐ghc"әF0\7 wU *y;`vЪUP~&#-w7/}ÀFw7]w4Ar6|hw=gtS0=@SmLaq%heAd2` ovǠ d/lм#(9"?E}$n#CGS6%li5im&fȝVj o(OCӁɨM mS ^-|n) "DUjHBzN.ѪxERIj@* DE-xFصQkH 6@)Buj;q凣aQ#.F>a&Q/#b8 @}g7DnYz#-+VA!YB y0fޅkYtRt'#,Ɩ&8!o#$Q[4Ɛy[G:4{T%F6=[(o7RPƒp^@d#.#,K<Bx~ɑa)fe2fL*J 6bz5xl$#-+2$8 "m G+"!EEeKʛ1a݄Iܑb>wCŮ7r Ц"Ғ6TNhjH) HIХY)#.Vv:#-auZs`J! [n#8$I3-JT=ze!ciJݥ]#X /Pe&M[ⵋ_}RjB9ej!yMqk91զ7N'n*ahPLP#.^.G:iO]B`$0r(ݙ&`UV%bfi Dž ԰Q>`ھ~S}gն*wE"ɢKΥEXFM$)X-Xs{=kPN^ku店mIkwWlm2Xʋ*w{,Hٔˆ1cMfB6F8h.(Vs`RP1& mUQqѭ ʚD 9C!~F6,pll_?"GVr@"U+ٵfUΑySu24wey6MLh`ү+{ziJsYyySiLQ IV%dj#.Mh-FIRz=N&wwkduۑjAG4ZK #,BD˜`FF2#.ިWRIqBl48u&4FfB`h CumIƂۊʡF 4|Q-ƕ˚ 5QqFD#-7ad6LT(S$p"XI{ZDtZ+cSڭ I,nm2 XdA"+Y%D#.|CEo*\V{J࣐Ɇ+"+#.6&ZV lH4i+cC3 0XLV6N ]&F6PiV&\3JԆHudzl*)GMhzS!_ɚjƞ]}3x1x[E¨d5|h^JMVECx"Y)(4ĆG褙AJTH5!B.mUiAƔk!M#$3i%oH~0І3VB-SYr+)(tZD͛ضp};2#. H;(:-0#SPNll̪JR/R5MK, J( ІUi#-4 1(F!Q),B#,(a.BIt.r\Z#,n5y9ǽ#.sXXpň#-F<&MzNծ®U!~1 K>du# bjA~egi{^Hn8Y#-L!=%C^%,&3yJ)p,M5Sf;{!ލٚٔe #/66ޔWloccr΋He9H8&S w26![c#'gi}7DTH d3$hl2aGa4{$C&Q%ykF'5St!^\08|1ļ. 'iN1zaPJqtqC{~?3\SX0#-`ڿ#.pGGLy2ܧTIm[mF2鼖,O#.9gkxXSUQXҐb]y[K&[pm:MQWRZʻ#3w^+EusK,Y26T8TEi* DzPhƨDJ l#,+I8A,#. X|_."lnLaT,!soU5YpMR-`Y$7eW{ŅO:K–#B K^ݫ#-rv7k{5QbVWS6k@V`SbֈfTkQm-5*)5)&F[* kLXUWUAo: ɰ#,K$!e/Ϸ/CoE^avGs~܀v]:Qvn2500`kIL6hdZq-kn7b%buj]6vZlV2k%n)i^[KIQv ꯗ~@2<*IJU 6PAd#,YȨ o' B*IP\7|+#-wJ2OU0ֶ|`P{5=-G^G)*2F@?#lj0򺌡q٘9oeRAW1L!*CHS0f+"I8^V:[LIe2HybI ϧlBA#cA6 wb"=4q^N#m.jp?Uu%h`gG'̎8gfeڡ`tF:s4MK,KUcߡu,x4#.C⠈#4閫GP(42Hc{p7yPc`B ˴4)fj!?D| *xDZ05CJ޸#on(4V:Sny|!#G#-ц"Ns$S^GjЖCTQ*QФBnwPPDvv"QRC vVLzymߛ耍ﻻO;<ҜቺVC(an@lgGP9ܧw h1D^B#,a;XdQy~E#,y'V@?}ȩ7#-=TuSb ǯ]Mx-oUh?IЁA=v)&ȋTI$T HlB@{ !S9(-Awe#.Nw0jZ%^^_u.RǗEARκ뺖j6(k]KzBU>%fu#-Y&:$jۚJy^Ȇ FAȡ ITVLVc5z"kvϝ.[LH!zhf'+J$/)R1 L`Т @)MGP4#-@UEwTYf6``! VIE5[f5EJ)36#. #,x0#-kU>XՆJRRj0ҙ O"$P pUރ]*5؇(Js j|p  #.k1sl0,1p$b! S/{nWx$a6w0` l 9o Y cKQA8|#-R۱3Ff C{X?aUPkF5QF(W&h8$B(A#-ڲ4on#,&1<#. F`#-7PvdPI0, AQ=zy[Z-GXPah#B1$M:|*1GZOQvr"7,]z-I#-kEQMQhm4+n#lRU#-1B#.!YATT 5 %ѝ]CD-Gz FJ"7ol𳱆Of)168(N5PYT@ #,ĔBT?lSL#.IlAm) \@$!4z;09,)&׏0_IIr6i508JֶRn7U$U#.#)#,*(GwW]wfKՔZx8ƦcvHEJӀiLr8VP+mZ|mtdh#-C#-8kCC K+5WvRM)#A$ QLǕeԐ5pTp_#,2Q>dC5AF\oo,F[7Pm8*ZR`v)\=IF/lKgb95kr֬8~qn5C膍lP~8Q΁XJ IM<7->T_4?7p׹bw}ܖ=Iv/qD;׺]M@D:^uc_=29eECӑxcja>7< #7򃋠)u8C`4#-'82VX ,#ch jo[F63IHagRI"'}l$*0ӌ3zpA(zjzDbi|NqL'g!mŦGDw۷QoqRrm0i]!6oZ VM3O/ַ9lT`!$l @e&BKs-7,Qy )wF6 H#,H⢃@Dc#-8 ̦aLm)pZ+44ap?[#-FxԪ܍ɤr &"4i$gjOʡܝr/ϙaC#.033Ƭ#._彵=a^L>! YyעY-5w*INF,*xQga8Iu^$-?\9r7!3{`09(uz)UGdrʴ ME`#--IݑD³M}jVjf,ӷ~ӯ.Ś #-od1q'BJZO*>s}LZ6TI{#,)d(~ XW͍_o6H$laMCaP'WT^'/h5gl\?09 a_)wڔ MP*h=#.n}Ʒ#,B"]0~¾hGZ*#E7Aj2uMEB&)FP%DIA#-kd#-affF:{Ή*竕Rح7|.dGWXr}q?6N8& *+[]ԎYgL;Ϡ~ #.l0,ݔ>@ D [YO*#,1Ba%BH1e̺Dtݖa$U$J*-TcYb @B,ݲnxR! wg>sQO´hQwiw cbAl IB)bPfF`f%H"Bʔd)'w#,(9 CLJEfUzE)$pٞ16#-%d 82 #-sx8Ah1]GzA+[[%{#OQjch#A` Cpy" q>Sg8B@صYt+3L #-[#-8Q*`d9cq qh=8@ަyֶc7P:K<eC;T"hI SMtT3! vJ֡"I'ajVD``  *1#-"IyH3P\6ZnT9.nۓJ6Ć@DZY2/aVJ!i ] xA ʢ )uzUAjLڙ!D *Е  9n']\6*^XeV07}dgl|@k*#.' :%\7@4HfJ{o)A g4<3z1ڪ|LDnm,VT봡ss`{Jٝ mr1B'`r}㗭t|Ku,E=1[E&>10l>ݛ$frjܴ$N PlP aL6XB:c)Rk##.[Wm֒!HIB5_ު U3W`9Ux]nY=*`e!i2%EhkZ\(7-wbɵ!n׍^6x#.F'#.c '(ޖүc:#-(`Z#-LDt %*#,H8>V#,,DTK;vUq3OU<~hqJP>B9aHa5:Q0I-n-wj*HH'oqp;,FG@BAPHC[uj%t#-Kkכ`In8$Q`IT5mjWy*Ine0`I/ȢyR(:t@%/SA{2f_yPCfOG̢A:)zf͋|D#.?`EffEdJ,ci!CfZf&֋j6[ڛMU+EcXim#-G<-rDV V&0Q'߅. 5226F)"t25W A+DI1ctW#.)`#-eldE1Tr#-LbPa,PT@Ecׄ Q|BD,B}?VvcSKrAڟ]P2h#.ą&SQ#.}@y$`JE{Cs=9َVR*үP>p#,b0=͍ S'Yqocqi +9d-YWb=؍ 9HbHUL-/ MЇRQ/4q<릺\rշ-*s+QV@nD:;69џT *cZrkŐdАr,Jtf&M1sw;sq?&^m廙u Eq#-xKܵ:!/˖w0f}|rJOЎק//EQR s}p6g o5lZttyBIN(.#.%#̾;Z%맼J`ĝiź,Β\["Ό#._^L V㢉C{q u^qXGS:$EEdk|+sʻtH~RJ#;`p{U c'h}Y]hf962qێ|B3j2KG~>Fut$#,^F5. m1:_7jĦإU',<ΙVlώiDPMJC8GSTSfn88“[4A-á1.Dti3WEVz#-s`}O#.Dسu8xs3Lnt9}sm3jMy%IX _#.k*}koO={)&dѐVxuAFm9dڥcʕjJY&rk!fKE>8#Ѫ 48#.cߥbJF)8p8Z"#QSFÿOK\!lf@94(FnP-5l"Dlv7\S::zt-rܑ=01h1J\ #-H4AgI)9Iᱼꠧ5@\,*B"#,9n7̍H6W^޼K-<`;#,Q,iʄ䎮&3$u:354#,/)pĀP6da|hXlULE#.xwWhX]> ghnq'zeD!F24aJ] P~Dm#-/9>*:g=++}˧zmGrZ7%~ZBQ4wݛ n4`cÆa,rNG_HނD#--uw}7pdTӪwfHIk͵,f(I#,2 O'4˳G knThn iS]ЫAR,fJuq#-#Ӧ:Ÿ+k6|vWՅ4g:Wj2y&-Y!sP8+YucunYoH]=q^R:VAr2QH4۩@(֌eSçavY&nY0ٚxVX Z/BP/=A,A,H_-XM O:OIfqkZ*uU(%% A'TayOSMЏkV4z?Y ̗> ^x$پNRV4wN#.QI4PmIXK#,7KYM0o&9Zr#-#O8Y#.8kگaX'p }+pܼ&P:{ `g-B3/eSP 0*2'F`1MY#-ŠelrBF!R(d=+]-HVY$#, hjъ)U·g uHiJM!Cg^:A Gt&#-\+:&S#.$M4 B0g)g>3M $dQ6T`|ԎYUSq"T'+AB`h&b@l)JI"g2s#@F,a2hI%w+mG{l ,#,KDj#2Yj2ZL3^vzq2qLc`b3+n\2CpX7 Phm2,u59377"nov)JԴj6xaIֹ2$câYXCVRSXP Pa#,mk#- c#-M.HʐCP`2!:W3+1%٭!2T6z|k;jVFڻm48&Ap?&O{gjn#-l+LQB*aoi4cyHHܽ8Z8aw"11g3i50um#-7I -`ۺ+fᲐi2pc8ֵ8aer.u!kdAmCFk2iGc9]Ånl.鋡R*#-5 aJd:((i $`T@A#-DFJHv26 ,mJ8maAnBVS37b$b#ԋ]U=8Sҫx8 9Բ㖜koK fR3N6х}Vvw ӺWk4hh-2FHFK3*Nnj!^5E"]ujHa0`J&e ma6".#.`9Q#-%% 0^y)Rh}W:ޯW^#-~CB#.`#fAP)U!߻A̒x>Pb=Eg^dQg" UQz=+^6ЋW> U4{L@#,9H[He#-ۭh#-AlŇ#-pַMLJ#-[u#.Wm\HePUR1EjǦƕFT`Ƴ3<؋gSOPz7D]HfMT?TpBjnyW_"xtS?[)#,I4-6jV70@F {#^_y'/2)Fp1 $QZQId,mLdMfk4ZLY&4إ)-#-4H̕M6JSE4Y2#&dd/wD8=#,9 n|[9n>]Bl{:BotOmz0Nj/LPY ҲHuA-FFe"]!q#-3Qo*@EAFHXs8woN1}ye--yuO?O糤Fu$G|v<7;y q_#,FmFDV5֊>\cXRPE2l@#,B#.SHYA=;KJ$"R >K&*#-Vo\5 5"#9eѢL啜FTEv9Imm4[&-4ƀ6e,`l,P7Mku#.i#-D0Xj"3#- F17Վ-;ޒK"q$A22+F6vqO#.IŇd8odDe#,Nx!S rJ#,a#ebހ-v,=?Vqf 6B|#,@j]ˤ2XJZ#,a:oxk@7=E`F `"$_^W\}Nbm_\fxgvˌ>e0(!fc&#.lMEot9hi3C[KOd+xi=L.#-k4ht2$ #,2Qv"?-s]u8ZB7gYbc_ ˭#-p5cC"Bo>#,YjLD&]4Xb1a[w yFy9ɋ6ecW#-UGqnT<|# U)o *%0F&^).1#-;M~Ht:e\MdV\5vtN"wvw:*Dy\Vː"UCҸXfyN#.tkarq4D(ʔd'ldL #-$:Y򖕉hdKdHTUhpȸtN'PwCKbĂ7G$lDu8Ic#-Q)$֌댌U3ΰ@ifWCUge]}x55aE!1Lrk= )HAN) 92o].<7CɰxaBYD(>p\HQ0ă%-9Ev#-%PHZTjb2#- lE`7'oi|RHms84C8زQ2mgYE`3g1(N+=|\d3t?BADZͼLg.ZoRP]#.@XKl#-cT eC&:V3+:)t[C;Β[.5`p0ۘyv"XBZh#.%*J+@` 5@x#.SqQ2MSJj8ޏ]̮;`1YFbH<#L摝[apm5hʐEq g\=}ShUmŦ#,u:!H`ZDQ,3#\XcXn#-3AkP- A wFH6d: vpkHȟ+f͔@4'vdX܏MנÆuɳPΈxm#,Il3j,&-J /Z!Wq#KJ n zd<7daAↇݐQLȓ<48Jĕ>TJ1nR\SMMr3\9=]X#,#-H%@ҙjs >+RfC/\d<]5 t9fkS(pHp9k!ږ#,.K@юصtMair8MWH.\ËBN!"mf <&#.)6C:i#,s&IC#.@Nc#.GauLlE\B0BnWB$l;Xc‰[5XĕqvwxW5ǜjV*ؗ+," ٬AG/6KGbw'Mp4>9\ib%)bZ>Eս}{#,nPƢ,n[Z y銦MxݓC3& C,e#.AH,0@ΦkVH@Ye(j`:wb/9#x3g#,c㌜խ+]FC?>pGUu>0Y#.@M4&4d 7O(/\]kF6TǶ“@ u,wDD%L ٥GBm,#-ߣ}̝$tM#-s5i#.41uM(IWJSMg)CG`Vh G#,JmdI|0 MlhK}(aP3&fwF*QMFT/U!2#- K.ui#nזQ!]}0:]4(6!]a3!2.adCpN]e%%U"UT b0ub4) k!u hRS@)e`CE6v79B.X"2#-%I#.dX\@0]Y'DBzzO=R%QQ>MuIxq"C5#.,"BAM`#,#,wm[/ePG9wUT#-ԓ,f;,M` 8ӓɆ$nrgc%n#.D8lhL SwMx#ޏ ZPTq k1wp"rw2"$LmY :(vHcnxMl?wd9n1VTML h4J%~c[Y: S̑1!tw;K9kJGw x)KGqnm 誌7DF;aZ'ZNy"*n {Ŗ\֠?1}DD+F\ODp*rCOaw9s>:dGHj!#.^UiK.J;C!;7hJ;9mydFxBaA lyBY!H2<*(cF*#-IUDEl8iQ}N<$#-![r/"(X-AEd$!>O>#,yݫ^i NqQ|RUr4Jъ1*R)nf1Ȃ06&B8S4fY+#.ˤ&:| 76{g9E_e:)ą<>BK3@sEla,MMzȜ`}Q[bb;[3=)鵋b&G1NS"[#,bDBմQ)>2屳"!w2T,UHm56k3qTG$P}@#,HRjdKjJ{$DPT-20C&oK#.AI ] 1P`1#-/dw*l1?Ӓ#.P۬ۻK}}mMQRuG =1'F DQ$AuqV 1An[cS($NbZhB@hD!AD"T(Gjm9UxsFw[EueA,)J"ȡ8!ovfT MX#,0C Yfej李UD`L㴱#.v0#..YlE!e!#,ׯ-aģjYTY_=n[lߪ)+nB7/#.8y,@@Yng׍RL7Y#.qΧȞ7]n4zY#.v#Z־!%.k;EŘ-*0#z{]ih>, pD6C[:OjdACY K6i R1G~c>b`\t}>>p\=@/#.H2if2[1jFҍ~[tJMZ:WCrjP#-s8{&@jԮ@z9o6݂y;I~#,2R9@ ! !O&G8w֦T=#.t 8zv6{N":_fq"ErA>paGKrEsÔ6m!i#.5N(uֹ\×N ;op:oB)$dD:Elv|#.H @}Pb UnΥB U4&$8&Cؤ5|KS#- #,#.JEz݋+YVPKULCYKG6w7EmjV&l&A" H@#,rn5'ȥVG-A=CHt+Fy$@E.'ho9g#.ˤ^/#.˱"#,4{x Z2= Uwjgz!;p`Z=6H ?;9 $oÝ7oyms_I_yU#,n"|l "};d,)_u.M~l>4d&dbV+0)"+Ph-X~ёUBb(aAj݀<0IʢE\*F*(T) m?3ݷ >! ?"=IZALk_Tvgc",H1FItIںk1h&I,Td̖$ھ꿉_"F1FTj%Ee4ҵ~o_[jY#.6ZČBYTh QYc(#-H&`gCXS$< ` Hh6JJX%nMhI6ӥdAD#,WPE`6-e%SG$$H{-a7/5ލE)A8Sx8&= B՜KJF_Ad"l%섭6@K#.#,~iϣuLAxEou W/#-LTdkbJ#.P+~WZJ,HXO:d U,bdƏE4,:ͻ=/t۲ GXD4E6#. m6PHE  m|9|۽#-A _+0嗑EƐ@!,(5C2Wv`Vl#,[Z RIKiQE$F#,B萖4Zbfjf5wV"-"z|29C.2ȨX@P"@_^ҌKH~VQA@P%LipCt셶++M27D)h˫ƼU֙bݚUiJ#-z9XSQFhVD2 X"mL )ZSLevWVWdޘέ*Fây\ AnŅ\%Hr(,C*jٝ1+u^6hbv"M#-Rqt/Y.0lRь)= ugٷD"8w%o@3$"yH\x$!#Db `MbC|: 3Q;0Io7ּu:VOmMm-ȁn4jT`}wReߝ#()Q0IP|iRj#("ys7E?&٤q<%f͑rPnpf\MˉKV#.v94k%Pm0m2!xPӲi?qB/}#-"ȘL9-r2\F) P#,~G~Je?J9Cx5U He~d& 1~袈K#IV3hXǼ0zsF(ۣZ{FE%˺?[ uĵ[ӶN|q.ЃG5 t2*&A7Ci9V*qm-`tj~wlqр[ CT#-Ԡú%B&3p#2za!.x?#*0!#-pU<ؖSq`|7׆weR Y3&4DVx@wۙ~vXm 2C+Bv,DAFE w#E.Y#,ʡ(1$ȍ@(nf#,$b[A}?l,r؂ZݙN`S)AQAJ#.e)l֙2#-+v#.s X'VMUA`œ\L tZCdOUp?jk~]#-1e5#- C%^}Q_vf{嬁'M:03?#.W!֭5\#-pl!S6sol <e&*#.*[C^&Xv&a!+j?!;{E 3W/.-?jk>4l屬Ύވɏmh|wQo=wIoDR#@p xhز?̝ WsAJNyZۿJ$;KN$h]bdgvr`þA:v8N`Zij/8qBב3\&K)MY%r.h$CK~4#-Ƴ;=1.3.1 -xibless>=0.4.1 - diff --git a/qt/run_template.py b/run.py similarity index 96% rename from qt/run_template.py rename to run.py index 7e42a474..4d80dcf2 100644 --- a/qt/run_template.py +++ b/run.py @@ -1,5 +1,5 @@ #!/usr/bin/python3 -# Copyright 2016 Hardcoded Software (http://www.hardcoded.net) +# 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