Remove cocoa
The cocoa UI code now lives in dupeguru-cocoa.
This commit is contained in:
parent
f51f94e03d
commit
245ed0ddec
|
@ -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
|
||||
|
|
8
Makefile
8
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
|
||||
|
|
86
README.md
86
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/
|
41
bootstrap.sh
41
bootstrap.sh
|
@ -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"
|
|
@ -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 <Cocoa/Cocoa.h>
|
||||
#import "PyDupeGuru.h"
|
||||
#import "ResultWindow.h"
|
||||
#import "ResultTable.h"
|
||||
#import "DetailsPanel.h"
|
||||
#import "DirectoryPanel.h"
|
||||
#import "IgnoreListDialog.h"
|
||||
#import "ProblemDialog.h"
|
||||
#import "DeletionOptions.h"
|
||||
#import "HSAboutBox.h"
|
||||
#import "HSRecentFiles.h"
|
||||
#import "HSProgressWindow.h"
|
||||
|
||||
@interface AppDelegate : NSObject <NSFileManagerDelegate>
|
||||
{
|
||||
NSMenu *recentResultsMenu;
|
||||
NSMenu *columnsMenu;
|
||||
|
||||
PyDupeGuru *model;
|
||||
ResultWindow *_resultWindow;
|
||||
DirectoryPanel *_directoryPanel;
|
||||
DetailsPanel *_detailsPanel;
|
||||
IgnoreListDialog *_ignoreListDialog;
|
||||
ProblemDialog *_problemDialog;
|
||||
DeletionOptions *_deletionOptions;
|
||||
HSProgressWindow *_progressWindow;
|
||||
NSWindowController *_preferencesPanel;
|
||||
HSAboutBox *_aboutBox;
|
||||
HSRecentFiles *_recentResults;
|
||||
}
|
||||
|
||||
@property (readwrite, retain) NSMenu *recentResultsMenu;
|
||||
@property (readwrite, retain) NSMenu *columnsMenu;
|
||||
|
||||
/* Virtual */
|
||||
+ (NSDictionary *)defaultPreferences;
|
||||
- (PyDupeGuru *)model;
|
||||
- (DetailsPanel *)createDetailsPanel;
|
||||
- (void)setScanOptions;
|
||||
|
||||
/* Public */
|
||||
- (void)finalizeInit;
|
||||
- (ResultWindow *)resultWindow;
|
||||
- (DirectoryPanel *)directoryPanel;
|
||||
- (DetailsPanel *)detailsPanel;
|
||||
- (HSRecentFiles *)recentResults;
|
||||
- (NSInteger)getAppMode;
|
||||
- (void)setAppMode:(NSInteger)appMode;
|
||||
|
||||
/* Delegate */
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
|
||||
- (void)applicationWillBecomeActive:(NSNotification *)aNotification;
|
||||
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
|
||||
- (void)applicationWillTerminate:(NSNotification *)aNotification;
|
||||
- (void)recentFileClicked:(NSString *)path;
|
||||
|
||||
/* Actions */
|
||||
- (void)clearPictureCache;
|
||||
- (void)loadResults;
|
||||
- (void)openWebsite;
|
||||
- (void)openHelp;
|
||||
- (void)showAboutBox;
|
||||
- (void)showDirectoryWindow;
|
||||
- (void)showPreferencesPanel;
|
||||
- (void)showResultWindow;
|
||||
- (void)showIgnoreList;
|
||||
- (void)startScanning;
|
||||
|
||||
/* model --> view */
|
||||
- (void)showMessage:(NSString *)msg;
|
||||
@end
|
|
@ -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
|
|
@ -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
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
|
||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
which should be included with this package. The terms are also available at
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "PyDeletionOptions.h"
|
||||
|
||||
@interface DeletionOptions : NSWindowController
|
||||
{
|
||||
|
||||
PyDeletionOptions *model;
|
||||
|
||||
NSTextField *messageTextField;
|
||||
NSButton *linkButton;
|
||||
NSMatrix *linkTypeRadio;
|
||||
NSButton *directButton;
|
||||
}
|
||||
|
||||
@property (readwrite, retain) NSTextField *messageTextField;
|
||||
@property (readwrite, retain) NSButton *linkButton;
|
||||
@property (readwrite, retain) NSMatrix *linkTypeRadio;
|
||||
@property (readwrite, retain) NSButton *directButton;
|
||||
|
||||
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||
|
||||
- (void)updateOptions;
|
||||
- (void)proceed;
|
||||
- (void)cancel;
|
||||
@end
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
|
||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
which should be included with this package. The terms are also available at
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
*/
|
||||
|
||||
#import "DeletionOptions.h"
|
||||
#import "DeletionOptions_UI.h"
|
||||
#import "HSPyUtil.h"
|
||||
|
||||
@implementation DeletionOptions
|
||||
|
||||
@synthesize messageTextField;
|
||||
@synthesize linkButton;
|
||||
@synthesize linkTypeRadio;
|
||||
@synthesize directButton;
|
||||
|
||||
- (id)initWithPyRef:(PyObject *)aPyRef
|
||||
{
|
||||
self = [super initWithWindow:nil];
|
||||
model = [[PyDeletionOptions alloc] initWithModel:aPyRef];
|
||||
[self setWindow:createDeletionOptions_UI(self)];
|
||||
[model bindCallback:createCallback(@"DeletionOptionsView", self)];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
[model release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)updateOptions
|
||||
{
|
||||
[model setLinkDeleted:[linkButton state] == NSOnState];
|
||||
[model setUseHardlinks:[linkTypeRadio selectedColumn] == 1];
|
||||
[model setDirect:[directButton state] == NSOnState];
|
||||
}
|
||||
|
||||
- (void)proceed
|
||||
{
|
||||
[NSApp stopModalWithCode:NSOKButton];
|
||||
}
|
||||
|
||||
- (void)cancel
|
||||
{
|
||||
[NSApp stopModalWithCode:NSCancelButton];
|
||||
}
|
||||
|
||||
/* model --> view */
|
||||
- (void)updateMsg:(NSString *)msg
|
||||
{
|
||||
[messageTextField setStringValue:msg];
|
||||
}
|
||||
|
||||
- (BOOL)show
|
||||
{
|
||||
[linkButton setState:NSOffState];
|
||||
[directButton setState:NSOffState];
|
||||
[linkTypeRadio selectCellAtRow:0 column:0];
|
||||
NSInteger r = [NSApp runModalForWindow:[self window]];
|
||||
[[self window] close];
|
||||
return r == NSOKButton;
|
||||
}
|
||||
|
||||
- (void)setHardlinkOptionEnabled:(BOOL)enabled
|
||||
{
|
||||
[linkTypeRadio setEnabled:enabled];
|
||||
}
|
||||
@end
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
|
||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
which should be included with this package. The terms are also available at
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Python.h>
|
||||
#import "PyDetailsPanel.h"
|
||||
|
||||
@interface DetailsPanel : NSWindowController <NSTableViewDataSource>
|
||||
{
|
||||
NSTableView *detailsTable;
|
||||
|
||||
PyDetailsPanel *model;
|
||||
}
|
||||
|
||||
@property (readwrite, retain) NSTableView *detailsTable;
|
||||
|
||||
- (id)initWithPyRef:(PyObject *)aPyRef;
|
||||
- (PyDetailsPanel *)model;
|
||||
|
||||
- (NSWindow *)createWindow;
|
||||
- (BOOL)isVisible;
|
||||
- (void)toggleVisibility;
|
||||
|
||||
/* Python --> Cocoa */
|
||||
- (void)refresh;
|
||||
@end
|
|
@ -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
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
|
||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
which should be included with this package. The terms are also available at
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import "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
|
|
@ -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
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
|
||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
which should be included with this package. The terms are also available at
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
*/
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#import <Python.h>
|
||||
#import "HSOutline.h"
|
||||
#import "PyDirectoryOutline.h"
|
||||
|
||||
#define DGAddedFoldersNotification @"DGAddedFoldersNotification"
|
||||
|
||||
@interface DirectoryOutline : HSOutline {}
|
||||
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView;
|
||||
- (PyDirectoryOutline *)model;
|
||||
|
||||
- (void)selectAll;
|
||||
@end;
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
|
||||
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
which should be included with this package. The terms are also available at
|
||||
http://www.gnu.org/licenses/gpl-3.0.html
|
||||
*/
|
||||
|
||||
#import "DirectoryOutline.h"
|
||||
|
||||
@implementation DirectoryOutline
|
||||
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView
|
||||
{
|
||||
self = [super initWithPyRef:aPyRef wrapperClass:[PyDirectoryOutline class]
|
||||
callbackClassName:@"DirectoryOutlineView" view:aOutlineView];
|
||||
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
|
||||
return self;
|
||||
}
|
||||
|
||||
- (PyDirectoryOutline *)model
|
||||
{
|
||||
return (PyDirectoryOutline *)model;
|
||||
}
|
||||
|
||||
/* Public */
|
||||
- (void)selectAll
|
||||
{
|
||||
[[self model] selectAll];
|
||||
}
|
||||
|
||||
/* Delegate */
|
||||
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
|
||||
{
|
||||
NSPasteboard *pboard;
|
||||
NSDragOperation sourceDragMask;
|
||||
sourceDragMask = [info draggingSourceOperationMask];
|
||||
pboard = [info draggingPasteboard];
|
||||
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
|
||||
if (sourceDragMask & NSDragOperationLink)
|
||||
return NSDragOperationLink;
|
||||
}
|
||||
return NSDragOperationNone;
|
||||
}
|
||||
|
||||
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
|
||||
{
|
||||
NSPasteboard *pboard;
|
||||
NSDragOperation sourceDragMask;
|
||||
sourceDragMask = [info draggingSourceOperationMask];
|
||||
pboard = [info draggingPasteboard];
|
||||
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
|
||||
NSArray *foldernames = [pboard propertyListForType:NSFilenamesPboardType];
|
||||
if (!(sourceDragMask & NSDragOperationLink))
|
||||
return NO;
|
||||
for (NSString *foldername in foldernames) {
|
||||
[[self model] addDirectory:foldername];
|
||||
}
|
||||
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:foldernames forKey:@"foldernames"];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:DGAddedFoldersNotification
|
||||
object:self userInfo:userInfo];
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)outlineView:(NSOutlineView *)aOutlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
|
||||
{
|
||||
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
|
||||
NSTextFieldCell *textCell = cell;
|
||||
NSIndexPath *path = item;
|
||||
BOOL selected = [path isEqualTo:[[self view] selectedPath]];
|
||||
if (selected) {
|
||||
[textCell setTextColor:[NSColor blackColor]];
|
||||
return;
|
||||
}
|
||||
NSInteger state = [self intProperty:@"state" valueAtPath:path];
|
||||
if (state == 1) {
|
||||
[textCell setTextColor:[NSColor blueColor]];
|
||||
}
|
||||
else if (state == 2) {
|
||||
[textCell setTextColor:[NSColor redColor]];
|
||||
}
|
||||
else {
|
||||
[textCell setTextColor:[NSColor blackColor]];
|
||||
}
|
||||
}
|
||||
}
|
||||
@end
|
|
@ -1,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 <Cocoa/Cocoa.h>
|
||||
#import "HSOutlineView.h"
|
||||
#import "HSRecentFiles.h"
|
||||
#import "DirectoryOutline.h"
|
||||
#import "PyDupeGuru.h"
|
||||
|
||||
@class AppDelegate;
|
||||
|
||||
@interface DirectoryPanel : NSWindowController <NSOpenSavePanelDelegate>
|
||||
{
|
||||
AppDelegate *_app;
|
||||
PyDupeGuru *model;
|
||||
HSRecentFiles *_recentDirectories;
|
||||
DirectoryOutline *outline;
|
||||
BOOL _alwaysShowPopUp;
|
||||
NSSegmentedControl *appModeSelector;
|
||||
NSPopUpButton *scanTypePopup;
|
||||
NSPopUpButton *addButtonPopUp;
|
||||
NSPopUpButton *loadRecentButtonPopUp;
|
||||
HSOutlineView *outlineView;
|
||||
NSButton *removeButton;
|
||||
NSButton *loadResultsButton;
|
||||
}
|
||||
|
||||
@property (readwrite, retain) NSSegmentedControl *appModeSelector;
|
||||
@property (readwrite, retain) NSPopUpButton *scanTypePopup;
|
||||
@property (readwrite, retain) NSPopUpButton *addButtonPopUp;
|
||||
@property (readwrite, retain) NSPopUpButton *loadRecentButtonPopUp;
|
||||
@property (readwrite, retain) HSOutlineView *outlineView;
|
||||
@property (readwrite, retain) NSButton *removeButton;
|
||||
@property (readwrite, retain) NSButton *loadResultsButton;
|
||||
|
||||
- (id)initWithParentApp:(AppDelegate *)aParentApp;
|
||||
|
||||
- (void)fillPopUpMenu;
|
||||
- (void)fillScanTypeMenu;
|
||||
- (void)adjustUIToLocalization;
|
||||
|
||||
- (void)askForDirectory;
|
||||
- (void)popupAddDirectoryMenu:(id)sender;
|
||||
- (void)popupLoadRecentMenu:(id)sender;
|
||||
- (void)removeSelectedDirectory;
|
||||
- (void)startDuplicateScan;
|
||||
|
||||
- (void)addDirectory:(NSString *)directory;
|
||||
- (void)refreshRemoveButtonText;
|
||||
- (void)markAll;
|
||||
|
||||
@end
|
|
@ -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) {
|
||||