Remove cocoa

The cocoa UI code now lives in dupeguru-cocoa.
This commit is contained in:
Virgil Dupras 2017-03-11 20:41:47 -05:00
parent f51f94e03d
commit 245ed0ddec
68 changed files with 24 additions and 4227 deletions

3
.gitmodules vendored
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,33 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import <Cocoa/Cocoa.h>
#import "PyDeletionOptions.h"
@interface DeletionOptions : NSWindowController
{
PyDeletionOptions *model;
NSTextField *messageTextField;
NSButton *linkButton;
NSMatrix *linkTypeRadio;
NSButton *directButton;
}
@property (readwrite, retain) NSTextField *messageTextField;
@property (readwrite, retain) NSButton *linkButton;
@property (readwrite, retain) NSMatrix *linkTypeRadio;
@property (readwrite, retain) NSButton *directButton;
- (id)initWithPyRef:(PyObject *)aPyRef;
- (void)updateOptions;
- (void)proceed;
- (void)cancel;
@end

View File

@ -1,72 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import "DeletionOptions.h"
#import "DeletionOptions_UI.h"
#import "HSPyUtil.h"
@implementation DeletionOptions
@synthesize messageTextField;
@synthesize linkButton;
@synthesize linkTypeRadio;
@synthesize directButton;
- (id)initWithPyRef:(PyObject *)aPyRef
{
self = [super initWithWindow:nil];
model = [[PyDeletionOptions alloc] initWithModel:aPyRef];
[self setWindow:createDeletionOptions_UI(self)];
[model bindCallback:createCallback(@"DeletionOptionsView", self)];
return self;
}
- (void)dealloc
{
[model release];
[super dealloc];
}
- (void)updateOptions
{
[model setLinkDeleted:[linkButton state] == NSOnState];
[model setUseHardlinks:[linkTypeRadio selectedColumn] == 1];
[model setDirect:[directButton state] == NSOnState];
}
- (void)proceed
{
[NSApp stopModalWithCode:NSOKButton];
}
- (void)cancel
{
[NSApp stopModalWithCode:NSCancelButton];
}
/* model --> view */
- (void)updateMsg:(NSString *)msg
{
[messageTextField setStringValue:msg];
}
- (BOOL)show
{
[linkButton setState:NSOffState];
[directButton setState:NSOffState];
[linkTypeRadio selectCellAtRow:0 column:0];
NSInteger r = [NSApp runModalForWindow:[self window]];
[[self window] close];
return r == NSOKButton;
}
- (void)setHardlinkOptionEnabled:(BOOL)enabled
{
[linkTypeRadio setEnabled:enabled];
}
@end

View File

@ -1,31 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import <Cocoa/Cocoa.h>
#import <Python.h>
#import "PyDetailsPanel.h"
@interface 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

View File

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

View File

@ -1,32 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import <Cocoa/Cocoa.h>
#import "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

View File

@ -1,96 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import "Utils.h"
#import "NSNotificationAdditions.h"
#import "NSImageAdditions.h"
#import "PyDupeGuru.h"
#import "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

View File

@ -1,21 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import <Cocoa/Cocoa.h>
#import <Python.h>
#import "HSOutline.h"
#import "PyDirectoryOutline.h"
#define DGAddedFoldersNotification @"DGAddedFoldersNotification"
@interface DirectoryOutline : HSOutline {}
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView;
- (PyDirectoryOutline *)model;
- (void)selectAll;
@end;

View File

@ -1,87 +0,0 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/
#import "DirectoryOutline.h"
@implementation DirectoryOutline
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView
{
self = [super initWithPyRef:aPyRef wrapperClass:[PyDirectoryOutline class]
callbackClassName:@"DirectoryOutlineView" view:aOutlineView];
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
return self;
}
- (PyDirectoryOutline *)model
{
return (PyDirectoryOutline *)model;
}
/* Public */
- (void)selectAll
{
[[self model] selectAll];
}
/* Delegate */
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [info draggingSourceOperationMask];
pboard = [info draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
if (sourceDragMask & NSDragOperationLink)
return NSDragOperationLink;
}
return NSDragOperationNone;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [info draggingSourceOperationMask];
pboard = [info draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSArray *foldernames = [pboard propertyListForType:NSFilenamesPboardType];
if (!(sourceDragMask & NSDragOperationLink))
return NO;
for (NSString *foldername in foldernames) {
[[self model] addDirectory:foldername];
}
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:foldernames forKey:@"foldernames"];
[[NSNotificationCenter defaultCenter] postNotificationName:DGAddedFoldersNotification
object:self userInfo:userInfo];
}
return YES;
}
- (void)outlineView:(NSOutlineView *)aOutlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
NSTextFieldCell *textCell = cell;
NSIndexPath *path = item;
BOOL selected = [path isEqualTo:[[self view] selectedPath]];
if (selected) {
[textCell setTextColor:[NSColor blackColor]];
return;
}
NSInteger state = [self intProperty:@"state" valueAtPath:path];
if (state == 1) {
[textCell setTextColor:[NSColor blueColor]];
}
else if (state == 2) {
[textCell setTextColor:[NSColor redColor]];
}
else {
[textCell setTextColor:[NSColor blackColor]];
}
}
}
@end

View File

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

View File

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