Browse Source

Initial commit

master
Virgil Dupras 4 years ago
commit
08eac3844e
68 changed files with 4486 additions and 0 deletions
  1. +15
    -0
      .gitignore
  2. +9
    -0
      .gitmodules
  3. +37
    -0
      Makefile
  4. +42
    -0
      README.md
  5. +319
    -0
      build.py
  6. +79
    -0
      cocoa/AppDelegate.h
  7. +394
    -0
      cocoa/AppDelegate.m
  8. +24
    -0
      cocoa/Consts.h
  9. +33
    -0
      cocoa/DeletionOptions.h
  10. +72
    -0
      cocoa/DeletionOptions.m
  11. +31
    -0
      cocoa/DetailsPanel.h
  12. +81
    -0
      cocoa/DetailsPanel.m
  13. +32
    -0
      cocoa/DetailsPanelPicture.h
  14. +96
    -0
      cocoa/DetailsPanelPicture.m
  15. +21
    -0
      cocoa/DirectoryOutline.h
  16. +87
    -0
      cocoa/DirectoryOutline.m
  17. +57
    -0
      cocoa/DirectoryPanel.h
  18. +256
    -0
      cocoa/DirectoryPanel.m
  19. +25
    -0
      cocoa/IgnoreListDialog.h
  20. +51
    -0
      cocoa/IgnoreListDialog.m
  21. +38
    -0
      cocoa/InfoTemplate.plist
  22. +37
    -0
      cocoa/PrioritizeDialog.h
  23. +56
    -0
      cocoa/PrioritizeDialog.m
  24. +16
    -0
      cocoa/PrioritizeList.h
  25. +58
    -0
      cocoa/PrioritizeList.m
  26. +26
    -0
      cocoa/ProblemDialog.h
  27. +44
    -0
      cocoa/ProblemDialog.m
  28. +23
    -0
      cocoa/ResultTable.h
  29. +180
    -0
      cocoa/ResultTable.m
  30. +76
    -0
      cocoa/ResultWindow.h
  31. +406
    -0
      cocoa/ResultWindow.m
  32. +17
    -0
      cocoa/StatsLabel.h
  33. +34
    -0
      cocoa/StatsLabel.m
  34. +19
    -0
      cocoa/dg_cocoa.py
  35. BIN
      cocoa/dupeguru.icns
  36. +140
    -0
      cocoa/en.lproj/Localizable.strings
  37. +0
    -0
      cocoa/inter/__init__.py
  38. +10
    -0
      cocoa/inter/all.py
  39. +252
    -0
      cocoa/inter/app.py
  40. +37
    -0
      cocoa/inter/deletion_options.py
  41. +11
    -0
      cocoa/inter/details_panel.py
  42. +53
    -0
      cocoa/inter/directories.py
  43. +21
    -0
      cocoa/inter/directory_outline.py
  44. +21
    -0
      cocoa/inter/ignore_list_dialog.py
  45. +35
    -0
      cocoa/inter/photo.py
  46. +29
    -0
      cocoa/inter/prioritize_dialog.py
  47. +8
    -0
      cocoa/inter/prioritize_list.py
  48. +9
    -0
      cocoa/inter/problem_dialog.py
  49. +50
    -0
      cocoa/inter/result_table.py
  50. +9
    -0
      cocoa/inter/stats_label.py
  51. +49
    -0
      cocoa/main.m
  52. +10
    -0
      cocoa/run_template.py
  53. +49
    -0
      cocoa/ui/deletion_options.py
  54. +32
    -0
      cocoa/ui/details_panel.py
  55. +70
    -0
      cocoa/ui/details_panel_picture.py
  56. +76
    -0
      cocoa/ui/directory_panel.py
  57. +30
    -0
      cocoa/ui/ignore_list_dialog.py
  58. +77
    -0
      cocoa/ui/main_menu.py
  59. +173
    -0
      cocoa/ui/preferences_panel.py
  60. +65
    -0
      cocoa/ui/prioritize_dialog.py
  61. +35
    -0
      cocoa/ui/problem_dialog.py
  62. +97
    -0
      cocoa/ui/result_window.py
  63. +169
    -0
      cocoa/waf
  64. +71
    -0
      cocoa/wscript
  65. +1
    -0
      cocoalib
  66. +1
    -0
      dupeguru
  67. +1
    -0
      hscommon
  68. +4
    -0
      requirements.txt

+ 15
- 0
.gitignore View File

@@ -0,0 +1,15 @@
.DS_Store
__pycache__
*.so
*.mo
*.waf*
.lock-waf*
/build
/cocoa/build
/env
/cocoa/autogen
/locale

/run.py
/cocoa/*/Info.plist
/cocoa/*/build

+ 9
- 0
.gitmodules View File

@@ -0,0 +1,9 @@
[submodule "dupeguru"]
path = dupeguru
url = https://github.com/hsoft/dupeguru.git
[submodule "hscommon"]
path = hscommon
url = https://github.com/hsoft/hscommon.git
[submodule "cocoalib"]
path = cocoalib
url = https://github.com/hsoft/cocoalib.git

+ 37
- 0
Makefile View File

@@ -0,0 +1,37 @@
PYTHON ?= python3
REQ_MINOR_VERSION = 4

all : | env build
@echo "Build complete! You can run dupeGuru with 'make run'"

# If you're installing into a path that is not going to be the final path prefix (such as a
# sandbox), set DESTDIR to that path.

# Our build scripts are not very "make like" yet and perform their task in a bundle. For now, we
# use one of each file to act as a representative, a target, of these groups.
submodules_target = hscommon/__init__.py

reqs :
@ret=`${PYTHON} -c "import sys; print(int(sys.version_info[:2] >= (3, ${REQ_MINOR_VERSION})))"`; \
if [ $${ret} -ne 1 ]; then \
echo "Python 3.${REQ_MINOR_VERSION}+ required. Aborting."; \
exit 1; \
fi
@${PYTHON} -m venv -h > /dev/null || \
echo "Creation of our virtualenv failed. Something's wrong with your python install."

# Ensure that submodules are initialized
$(submodules_target) :
git submodule init
git submodule update
cd dupeguru; ln -sf ../hscommon .; ln -sf ../cocoalib .

env : | $(submodules_target) reqs
@echo "Creating our virtualenv"
${PYTHON} -m venv env
./env/bin/python -m pip install -r requirements.txt

build:
./env/bin/python build.py

.PHONY : reqs build all

+ 42
- 0
README.md View File

@@ -0,0 +1,42 @@
# dupeguru-cocoa

This is the Cocoa UI for [dupeGuru][dupeguru]. This code was previously directly in the main repo,
but since I'm not planning on supporting MacOS myself any longer, I'm splitting it out.

Also, to make the job easier on a would-be maintainer for the Cocoa UI of dupeGuru, I'm planning
on restoring the XCode/XIB version of the UI from the grave.

### OS X maintainer wanted

My Mac Mini is already a couple of years old and is likely to be my last Apple purchase. When it
dies, I will be unable maintain the OS X version of moneyGuru. I've already stopped paying for the
Mac Developer membership so I can't sign the apps anymore (in the "official way" I mean. The
download is still PGP signed) If you're a Mac developer and are interested in taking this task,
[don't hesitate to let me know][contrib-issue].

## How to build dupeGuru from source

### Prerequisites

* Python 3.4+ compiled in "framework mode".
* MacOS 10.10+ with XCode command line tools.

### make

You can build the app with `make`:

$ make
$ make run

### pyenv

[pyenv][pyenv] is a popular way to manage multiple python versions. However, be aware that dupeGuru
will not compile with a pyenv's python unless it's been built with `--enable-framework`. You can do
this with:

$ env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install 3.4.3


[dupeguru]: https://github.com/hsoft/dupeguru
[contrib-issue]: https://github.com/hsoft/dupeguru/issues/300
[pyenv]: https://github.com/yyuu/pyenv

+ 319
- 0
build.py View File

@@ -0,0 +1,319 @@
# Copyright 2017 Virgil Dupras
#
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html

import sys
sys.path.append('dupeguru')
import os
import os.path as op
from optparse import OptionParser
import shutil
import compileall

from setuptools import setup, Extension

from hscommon import sphinxgen
from hscommon.build import (
add_to_pythonpath, print_and_do, copy_packages, filereplace,
get_module_version, move_all, copy_all, OSXAppStructure,
build_cocoalib_xibless, fix_qt_resource_file, build_cocoa_ext, copy_embeddable_python_dylib,
collect_stdlib_dependencies
)
from hscommon import loc
from hscommon.plat import ISOSX
from hscommon.util import ensure_folder, delete_files_with_pattern

def parse_args():
usage = "usage: %prog [options]"
parser = OptionParser(usage=usage)
parser.add_option(
'--clean', action='store_true', dest='clean',
help="Clean build folder before building"
)
parser.add_option(
'--doc', action='store_true', dest='doc',
help="Build only the help file"
)
parser.add_option(
'--dev', action='store_true', dest='dev', default=False,
help="If this flag is set, will configure for dev builds."
)
parser.add_option(
'--loc', action='store_true', dest='loc',
help="Build only localization"
)
parser.add_option(
'--cocoa-ext', action='store_true', dest='cocoa_ext',
help="Build only Cocoa extensions"
)
parser.add_option(
'--cocoa-compile', action='store_true', dest='cocoa_compile',
help="Build only Cocoa executable"
)
parser.add_option(
'--xibless', action='store_true', dest='xibless',
help="Build only xibless UIs"
)
parser.add_option(
'--updatepot', action='store_true', dest='updatepot',
help="Generate .pot files from source code."
)
parser.add_option(
'--mergepot', action='store_true', dest='mergepot',
help="Update all .po files based on .pot files."
)
parser.add_option(
'--normpo', action='store_true', dest='normpo',
help="Normalize all PO files (do this before commit)."
)
(options, args) = parser.parse_args()
return options

def cocoa_app():
app_path = 'build/dupeGuru.app'
return OSXAppStructure(app_path)

def build_xibless(dest='cocoa/autogen'):
import xibless
ensure_folder(dest)
FNPAIRS = [
('ignore_list_dialog.py', 'IgnoreListDialog_UI'),
('deletion_options.py', 'DeletionOptions_UI'),
('problem_dialog.py', 'ProblemDialog_UI'),
('directory_panel.py', 'DirectoryPanel_UI'),
('prioritize_dialog.py', 'PrioritizeDialog_UI'),
('result_window.py', 'ResultWindow_UI'),
('main_menu.py', 'MainMenu_UI'),
('details_panel.py', 'DetailsPanel_UI'),
('details_panel_picture.py', 'DetailsPanelPicture_UI'),
]
for srcname, dstname in FNPAIRS:
xibless.generate(
op.join('cocoa', 'ui', srcname), op.join(dest, dstname),
localizationTable='Localizable'
)
for appmode in ('standard', 'music', 'picture'):
xibless.generate(
op.join('cocoa', 'ui', 'preferences_panel.py'),
op.join(dest, 'PreferencesPanel%s_UI' % appmode.capitalize()),
localizationTable='Localizable',
args={'appmode': appmode},
)

def build_cocoa(dev):
print("Creating OS X app structure")
app = cocoa_app()
app_version = get_module_version('core')
cocoa_project_path = 'cocoa'
filereplace(op.join(cocoa_project_path, 'InfoTemplate.plist'), op.join('build', 'Info.plist'), version=app_version)
app.create(op.join('build', 'Info.plist'))
print("Building localizations")
build_localizations()
print("Building xibless UIs")
build_cocoalib_xibless()
build_xibless()
print("Building Python extensions")
build_cocoa_proxy_module()
build_cocoa_bridging_interfaces()
print("Building the cocoa layer")
copy_embeddable_python_dylib('build')
pydep_folder = op.join(app.resources, 'py')
if not op.exists(pydep_folder):
os.mkdir(pydep_folder)
shutil.copy(op.join(cocoa_project_path, 'dg_cocoa.py'), 'build')
tocopy = [
'dupeguru/core', 'hscommon', 'cocoa/inter', 'cocoalib/cocoa', 'objp', 'send2trash', 'hsaudiotag',
]
copy_packages(tocopy, pydep_folder, create_links=dev)
sys.path.insert(0, 'build')
# ModuleFinder can't seem to correctly detect the multiprocessing dependency, so we have
# to manually specify it.
extra_deps = ['multiprocessing']
collect_stdlib_dependencies('build/dg_cocoa.py', pydep_folder, extra_deps=extra_deps)
del sys.path[0]
# Views are not referenced by python code, so they're not found by the collector.
copy_all('build/inter/*.so', op.join(pydep_folder, 'inter'))
if not dev:
# Important: Don't ever run delete_files_with_pattern('*.py') on dev builds because you'll
# be deleting all py files in symlinked folders.
compileall.compile_dir(pydep_folder, force=True, legacy=True)
delete_files_with_pattern(pydep_folder, '*.py')
delete_files_with_pattern(pydep_folder, '__pycache__')
print("Compiling with WAF")
os.chdir('cocoa')
print_and_do('{0} waf configure && {0} waf'.format(sys.executable))
os.chdir('..')
app.copy_executable('cocoa/build/dupeGuru')
build_help()
print("Copying resources and frameworks")
image_path = 'cocoa/dupeguru.icns'
resources = [image_path, 'cocoa/dsa_pub.pem', 'build/dg_cocoa.py', 'build/help']
app.copy_resources(*resources, use_symlinks=dev)
app.copy_frameworks('build/Python')
print("Creating the run.py file")
tmpl = open('cocoa/run_template.py', 'rt').read()
run_contents = tmpl.replace('{{app_path}}', app.dest)
open('run.py', 'wt').write(run_contents)

def build_help():
print("Generating Help")
current_path = op.abspath('dupeguru')
help_basepath = op.join(current_path, 'help', 'en')
help_destpath = op.join(current_path, '..', 'build', 'help')
changelog_path = op.join(current_path, 'help', 'changelog')
tixurl = "https://github.com/hsoft/dupeguru/issues/{}"
confrepl = {'language': 'en'}
changelogtmpl = op.join(current_path, 'help', 'changelog.tmpl')
conftmpl = op.join(current_path, 'help', 'conf.tmpl')
sphinxgen.gen(help_basepath, help_destpath, changelog_path, tixurl, confrepl, conftmpl, changelogtmpl)

def build_localizations():
if not op.exists('locale'):
os.symlink('dupeguru/locale', 'locale')
loc.compile_all_po('locale')
app = cocoa_app()
loc.build_cocoa_localizations(app, en_stringsfile=op.join('cocoa', 'en.lproj', 'Localizable.strings'))
locale_dest = op.join(app.resources, 'locale')
if op.exists(locale_dest):
shutil.rmtree(locale_dest)
shutil.copytree('locale', locale_dest, ignore=shutil.ignore_patterns('*.po', '*.pot'))

def build_updatepot():
print("Updating Cocoa strings file.")
build_cocoalib_xibless('cocoalib/autogen')
loc.generate_cocoa_strings_from_code('cocoalib', 'cocoalib/en.lproj')
build_xibless()
loc.generate_cocoa_strings_from_code('cocoa', 'cocoa/en.lproj')

def build_mergepot():
print("Updating .po files using .pot files")
loc.merge_pots_into_pos(op.join('cocoalib', 'locale'))

def build_normpo():
loc.normalize_all_pos(op.join('cocoalib', 'locale'))

def build_cocoa_proxy_module():
print("Building Cocoa Proxy")
import objp.p2o
objp.p2o.generate_python_proxy_code('cocoalib/cocoa/CocoaProxy.h', 'build/CocoaProxy.m')
build_cocoa_ext(
"CocoaProxy", 'cocoalib/cocoa',
[
'cocoalib/cocoa/CocoaProxy.m', 'build/CocoaProxy.m', 'build/ObjP.m',
'cocoalib/HSErrorReportWindow.m', 'cocoa/autogen/HSErrorReportWindow_UI.m'
],
['AppKit', 'CoreServices'],
['cocoalib', 'cocoa/autogen']
)

def build_cocoa_bridging_interfaces():
print("Building Cocoa Bridging Interfaces")
import objp.o2p
import objp.p2o
add_to_pythonpath('cocoa')
add_to_pythonpath('cocoalib')
from cocoa.inter import (
PyGUIObject, GUIObjectView, PyColumns, ColumnsView, PyOutline,
OutlineView, PySelectableList, SelectableListView, PyTable, TableView, PyBaseApp,
PyTextField, ProgressWindowView, PyProgressWindow
)
from inter.deletion_options import PyDeletionOptions, DeletionOptionsView
from inter.details_panel import PyDetailsPanel, DetailsPanelView
from inter.directory_outline import PyDirectoryOutline, DirectoryOutlineView
from inter.prioritize_dialog import PyPrioritizeDialog, PrioritizeDialogView
from inter.prioritize_list import PyPrioritizeList, PrioritizeListView
from inter.problem_dialog import PyProblemDialog
from inter.ignore_list_dialog import PyIgnoreListDialog, IgnoreListDialogView
from inter.result_table import PyResultTable, ResultTableView
from inter.stats_label import PyStatsLabel, StatsLabelView
from inter.app import PyDupeGuru, DupeGuruView
allclasses = [
PyGUIObject, PyColumns, PyOutline, PySelectableList, PyTable, PyBaseApp,
PyDetailsPanel, PyDirectoryOutline, PyPrioritizeDialog, PyPrioritizeList, PyProblemDialog,
PyIgnoreListDialog, PyDeletionOptions, PyResultTable, PyStatsLabel, PyDupeGuru,
PyTextField, PyProgressWindow
]
for class_ in allclasses:
objp.o2p.generate_objc_code(class_, 'cocoa/autogen', inherit=True)
allclasses = [
GUIObjectView, ColumnsView, OutlineView, SelectableListView, TableView,
DetailsPanelView, DirectoryOutlineView, PrioritizeDialogView, PrioritizeListView,
IgnoreListDialogView, DeletionOptionsView, ResultTableView, StatsLabelView,
ProgressWindowView, DupeGuruView
]
clsspecs = [objp.o2p.spec_from_python_class(class_) for class_ in allclasses]
objp.p2o.generate_python_proxy_code_from_clsspec(clsspecs, 'build/CocoaViews.m')
build_cocoa_ext('CocoaViews', 'cocoa/inter', ['build/CocoaViews.m', 'build/ObjP.m'])

def build_pe_modules():
print("Building PE Modules")
exts = [
Extension(
"_block",
[op.join('dupeguru', 'core', 'pe', 'modules', 'block.c'), op.join('dupeguru', 'core', 'pe', 'modules', 'common.c')]
),
Extension(
"_cache",
[op.join('dupeguru', 'core', 'pe', 'modules', 'cache.c'), op.join('dupeguru', 'core', 'pe', 'modules', 'common.c')]
),
]
exts.append(Extension(
"_block_osx",
[op.join('dupeguru', 'core', 'pe', 'modules', 'block_osx.m'), op.join('dupeguru', 'core', 'pe', 'modules', 'common.c')],
extra_link_args=[
"-framework", "CoreFoundation",
"-framework", "Foundation",
"-framework", "ApplicationServices",
]
))
setup(
script_args=['build_ext', '--inplace'],
ext_modules=exts,
)
move_all('_block*', op.join('dupeguru', 'core', 'pe'))
move_all('_cache*', op.join('dupeguru', 'core', 'pe'))

def build_normal(dev):
print("Building dupeGuru with UI cocoa")
add_to_pythonpath('.')
build_pe_modules()
build_cocoa(dev)

def main():
options = parse_args()
if options.dev:
print("Building in Dev mode")
if options.clean:
for path in ['build', op.join('cocoa', 'build'), op.join('cocoa', 'autogen')]:
if op.exists(path):
shutil.rmtree(path)
if not op.exists('build'):
os.mkdir('build')
if options.doc:
build_help()
elif options.loc:
build_localizations()
elif options.updatepot:
build_updatepot()
elif options.mergepot:
build_mergepot()
elif options.normpo:
build_normpo()
elif options.cocoa_ext:
build_cocoa_proxy_module()
build_cocoa_bridging_interfaces()
elif options.cocoa_compile:
os.chdir('cocoa')
print_and_do('{0} waf configure && {0} waf'.format(sys.executable))
os.chdir('..')
cocoa_app().copy_executable('cocoa/build/dupeGuru')
elif options.xibless:
build_cocoalib_xibless()
build_xibless()
else:
build_normal(options.dev)

if __name__ == '__main__':
main()

+ 79
- 0
cocoa/AppDelegate.h View File

@@ -0,0 +1,79 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import "PyDupeGuru.h"
#import "ResultWindow.h"
#import "ResultTable.h"
#import "DetailsPanel.h"
#import "DirectoryPanel.h"
#import "IgnoreListDialog.h"
#import "ProblemDialog.h"
#import "DeletionOptions.h"
#import "HSAboutBox.h"
#import "HSRecentFiles.h"
#import "HSProgressWindow.h"

@interface AppDelegate : NSObject <NSFileManagerDelegate>
{
NSMenu *recentResultsMenu;
NSMenu *columnsMenu;
PyDupeGuru *model;
ResultWindow *_resultWindow;
DirectoryPanel *_directoryPanel;
DetailsPanel *_detailsPanel;
IgnoreListDialog *_ignoreListDialog;
ProblemDialog *_problemDialog;
DeletionOptions *_deletionOptions;
HSProgressWindow *_progressWindow;
NSWindowController *_preferencesPanel;
HSAboutBox *_aboutBox;
HSRecentFiles *_recentResults;
}

@property (readwrite, retain) NSMenu *recentResultsMenu;
@property (readwrite, retain) NSMenu *columnsMenu;

/* Virtual */
+ (NSDictionary *)defaultPreferences;
- (PyDupeGuru *)model;
- (DetailsPanel *)createDetailsPanel;
- (void)setScanOptions;

/* Public */
- (void)finalizeInit;
- (ResultWindow *)resultWindow;
- (DirectoryPanel *)directoryPanel;
- (DetailsPanel *)detailsPanel;
- (HSRecentFiles *)recentResults;
- (NSInteger)getAppMode;
- (void)setAppMode:(NSInteger)appMode;

/* Delegate */
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (void)applicationWillBecomeActive:(NSNotification *)aNotification;
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender;
- (void)applicationWillTerminate:(NSNotification *)aNotification;
- (void)recentFileClicked:(NSString *)path;

/* Actions */
- (void)clearPictureCache;
- (void)loadResults;
- (void)openWebsite;
- (void)openHelp;
- (void)showAboutBox;
- (void)showDirectoryWindow;
- (void)showPreferencesPanel;
- (void)showResultWindow;
- (void)showIgnoreList;
- (void)startScanning;

/* model --> view */
- (void)showMessage:(NSString *)msg;
@end

+ 394
- 0
cocoa/AppDelegate.m View File

@@ -0,0 +1,394 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "AppDelegate.h"
#import "ProgressController.h"
#import "HSPyUtil.h"
#import "Consts.h"
#import "Dialogs.h"
#import "Utils.h"
#import "ValueTransformers.h"
#import "DetailsPanelPicture.h"
#import "PreferencesPanelStandard_UI.h"
#import "PreferencesPanelMusic_UI.h"
#import "PreferencesPanelPicture_UI.h"

@implementation AppDelegate

@synthesize recentResultsMenu;
@synthesize columnsMenu;

+ (NSDictionary *)defaultPreferences
{
NSMutableDictionary *d = [NSMutableDictionary dictionary];
[d setObject:i2n(1) forKey:@"scanTypeStandard"];
[d setObject:i2n(3) forKey:@"scanTypeMusic"];
[d setObject:i2n(0) forKey:@"scanTypePicture"];
[d setObject:i2n(95) forKey:@"minMatchPercentage"];
[d setObject:i2n(30) forKey:@"smallFileThreshold"];
[d setObject:b2n(YES) forKey:@"wordWeighting"];
[d setObject:b2n(NO) forKey:@"matchSimilarWords"];
[d setObject:b2n(YES) forKey:@"ignoreSmallFiles"];
[d setObject:b2n(NO) forKey:@"scanTagTrack"];
[d setObject:b2n(YES) forKey:@"scanTagArtist"];
[d setObject:b2n(YES) forKey:@"scanTagAlbum"];
[d setObject:b2n(YES) forKey:@"scanTagTitle"];
[d setObject:b2n(NO) forKey:@"scanTagGenre"];
[d setObject:b2n(NO) forKey:@"scanTagYear"];
[d setObject:b2n(NO) forKey:@"matchScaled"];
[d setObject:i2n(1) forKey:@"recreatePathType"];
[d setObject:i2n(11) forKey:TableFontSize];
[d setObject:b2n(YES) forKey:@"mixFileKind"];
[d setObject:b2n(NO) forKey:@"useRegexpFilter"];
[d setObject:b2n(NO) forKey:@"ignoreHardlinkMatches"];
[d setObject:b2n(NO) forKey:@"removeEmptyFolders"];
[d setObject:b2n(NO) forKey:@"DebugMode"];
[d setObject:@"" forKey:@"CustomCommand"];
[d setObject:[NSArray array] forKey:@"recentDirectories"];
[d setObject:[NSArray array] forKey:@"columnsOrder"];
[d setObject:[NSDictionary dictionary] forKey:@"columnsWidth"];
return d;
}

+ (void)initialize
{
HSVTAdd *vt = [[[HSVTAdd alloc] initWithValue:4] autorelease];
[NSValueTransformer setValueTransformer:vt forName:@"vtRowHeightOffset"];
NSDictionary *d = [self defaultPreferences];
[[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:d];
[[NSUserDefaults standardUserDefaults] registerDefaults:d];
}

- (id)init
{
self = [super init];
model = [[PyDupeGuru alloc] init];
[model bindCallback:createCallback(@"DupeGuruView", self)];
NSMutableIndexSet *contentsIndexes = [NSMutableIndexSet indexSet];
[contentsIndexes addIndex:1];
[contentsIndexes addIndex:2];
VTIsIntIn *vt = [[[VTIsIntIn alloc] initWithValues:contentsIndexes reverse:YES] autorelease];
[NSValueTransformer setValueTransformer:vt forName:@"vtScanTypeIsNotContent"];
NSMutableIndexSet *i = [NSMutableIndexSet indexSetWithIndex:0];
VTIsIntIn *vtScanTypeIsFuzzy = [[[VTIsIntIn alloc] initWithValues:i reverse:NO] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsFuzzy forName:@"vtScanTypeIsFuzzy"];
i = [NSMutableIndexSet indexSetWithIndex:4];
VTIsIntIn *vtScanTypeIsNotContent = [[[VTIsIntIn alloc] initWithValues:i reverse:YES] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsNotContent forName:@"vtScanTypeMusicIsNotContent"];
VTIsIntIn *vtScanTypeIsTag = [[[VTIsIntIn alloc] initWithValues:[NSIndexSet indexSetWithIndex:3] reverse:NO] autorelease];
[NSValueTransformer setValueTransformer:vtScanTypeIsTag forName:@"vtScanTypeIsTag"];
return self;
}

- (void)finalizeInit
{
// We can only finalize initialization once the main menu has been created, which cannot happen
// before AppDelegate is created.
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
_recentResults = [[HSRecentFiles alloc] initWithName:@"recentResults" menu:recentResultsMenu];
[_recentResults setDelegate:self];
_directoryPanel = [[DirectoryPanel alloc] initWithParentApp:self];
_ignoreListDialog = [[IgnoreListDialog alloc] initWithPyRef:[model ignoreListDialog]];
_problemDialog = [[ProblemDialog alloc] initWithPyRef:[model problemDialog]];
_deletionOptions = [[DeletionOptions alloc] initWithPyRef:[model deletionOptions]];
_progressWindow = [[HSProgressWindow alloc] initWithPyRef:[[self model] progressWindow] view:nil];
[_progressWindow setParentWindow:[_directoryPanel window]];
// Lazily loaded
_aboutBox = nil;
_preferencesPanel = nil;
_resultWindow = nil;
_detailsPanel = nil;
[[[self directoryPanel] window] makeKeyAndOrderFront:self];
}

/* Virtual */

- (PyDupeGuru *)model
{
return model;
}

- (DetailsPanel *)createDetailsPanel
{
NSInteger appMode = [self getAppMode];
if (appMode == AppModePicture) {
return [[DetailsPanelPicture alloc] initWithApp:model];
}
else {
return [[DetailsPanel alloc] initWithPyRef:[model detailsPanel]];
}
}

- (void)setScanOptions
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSString *scanTypeOptionName;
NSInteger appMode = [self getAppMode];
if (appMode == AppModePicture) {
scanTypeOptionName = @"scanTypePicture";
}
else if (appMode == AppModeMusic) {
scanTypeOptionName = @"scanTypeMusic";
}
else {
scanTypeOptionName = @"scanTypeStandard";
}
[model setScanType:n2i([ud objectForKey:scanTypeOptionName])];
[model setMinMatchPercentage:n2i([ud objectForKey:@"minMatchPercentage"])];
[model setWordWeighting:n2b([ud objectForKey:@"wordWeighting"])];
[model setMixFileKind:n2b([ud objectForKey:@"mixFileKind"])];
[model setIgnoreHardlinkMatches:n2b([ud objectForKey:@"ignoreHardlinkMatches"])];
[model setMatchSimilarWords:n2b([ud objectForKey:@"matchSimilarWords"])];
int smallFileThreshold = [ud integerForKey:@"smallFileThreshold"]; // In KB
int sizeThreshold = [ud boolForKey:@"ignoreSmallFiles"] ? smallFileThreshold * 1024 : 0; // The py side wants bytes
[model setSizeThreshold:sizeThreshold];
[model enable:n2b([ud objectForKey:@"scanTagTrack"]) scanForTag:@"track"];
[model enable:n2b([ud objectForKey:@"scanTagArtist"]) scanForTag:@"artist"];
[model enable:n2b([ud objectForKey:@"scanTagAlbum"]) scanForTag:@"album"];
[model enable:n2b([ud objectForKey:@"scanTagTitle"]) scanForTag:@"title"];
[model enable:n2b([ud objectForKey:@"scanTagGenre"]) scanForTag:@"genre"];
[model enable:n2b([ud objectForKey:@"scanTagYear"]) scanForTag:@"year"];
[model setMatchScaled:n2b([ud objectForKey:@"matchScaled"])];
}

/* Public */
- (ResultWindow *)resultWindow
{
return _resultWindow;
}

- (DirectoryPanel *)directoryPanel
{
return _directoryPanel;
}

- (DetailsPanel *)detailsPanel
{
return _detailsPanel;
}

- (HSRecentFiles *)recentResults
{
return _recentResults;
}

- (NSInteger)getAppMode
{
return [model getAppMode];
}

- (void)setAppMode:(NSInteger)appMode
{
[model setAppMode:appMode];
if (_preferencesPanel != nil) {
[_preferencesPanel release];
_preferencesPanel = nil;
}
}

/* Actions */
- (void)clearPictureCache
{
NSString *msg = NSLocalizedString(@"Do you really want to remove all your cached picture analysis?", @"");
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) // NO
return;
[model clearPictureCache];
}

- (void)loadResults
{
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setCanChooseFiles:YES];
[op setCanChooseDirectories:NO];
[op setCanCreateDirectories:NO];
[op setAllowsMultipleSelection:NO];
[op setAllowedFileTypes:[NSArray arrayWithObject:@"dupeguru"]];
[op setTitle:NSLocalizedString(@"Select a results file to load", @"")];
if ([op runModal] == NSOKButton) {
NSString *filename = [[[op URLs] objectAtIndex:0] path];
[model loadResultsFrom:filename];
[[self recentResults] addFile:filename];
}
}

- (void)openWebsite
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://www.hardcoded.net/dupeguru/"]];
}

- (void)openHelp
{
NSBundle *b = [NSBundle mainBundle];
NSString *p = [b pathForResource:@"index" ofType:@"html" inDirectory:@"help"];
NSURL *u = [NSURL fileURLWithPath:p];
[[NSWorkspace sharedWorkspace] openURL:u];
}

- (void)showAboutBox
{
if (_aboutBox == nil) {
_aboutBox = [[HSAboutBox alloc] initWithApp:model];
}
[[_aboutBox window] makeKeyAndOrderFront:nil];
}

- (void)showDirectoryWindow
{
[[[self directoryPanel] window] makeKeyAndOrderFront:nil];
}

- (void)showPreferencesPanel
{
if (_preferencesPanel == nil) {
NSWindow *window;
NSInteger appMode = [model getAppMode];
if (appMode == AppModePicture) {
window = createPreferencesPanelPicture_UI(nil);
}
else if (appMode == AppModeMusic) {
window = createPreferencesPanelMusic_UI(nil);
}
else {
window = createPreferencesPanelStandard_UI(nil);
}
_preferencesPanel = [[NSWindowController alloc] initWithWindow:window];
}
[_preferencesPanel showWindow:nil];
}

- (void)showResultWindow
{
[[[self resultWindow] window] makeKeyAndOrderFront:nil];
}

- (void)showIgnoreList
{
[model showIgnoreList];
}

- (void)startScanning
{
[[self directoryPanel] startDuplicateScan];
}


/* Delegate */
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[model loadSession];
}

- (void)applicationWillBecomeActive:(NSNotification *)aNotification
{
if (![[[self directoryPanel] window] isVisible]) {
[[self directoryPanel] showWindow:NSApp];
}
}

- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender
{
if ([model resultsAreModified]) {
NSString *msg = NSLocalizedString(@"You have unsaved results, do you really want to quit?", @"");
if ([Dialogs askYesNo:msg] == NSAlertSecondButtonReturn) { // NO
return NSTerminateCancel;
}
}
return NSTerminateNow;
}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
NSInteger sc = [ud integerForKey:@"sessionCountSinceLastIgnorePurge"];
if (sc >= 10) {
sc = -1;
[model purgeIgnoreList];
}
sc++;
[model saveSession];
[ud setInteger:sc forKey:@"sessionCountSinceLastIgnorePurge"];
// NSApplication does not release nib instances objects, we must do it manually
// Well, it isn't needed because the memory is freed anyway (we are quitting the application
// But I need to release HSRecentFiles so it saves the user defaults
[_directoryPanel release];
[_recentResults release];
}

- (void)recentFileClicked:(NSString *)path
{
[model loadResultsFrom:path];
}


/* model --> view */
- (void)showMessage:(NSString *)msg
{
[Dialogs showMessage:msg];
}

- (BOOL)askYesNoWithPrompt:(NSString *)prompt
{
return [Dialogs askYesNo:prompt] == NSAlertFirstButtonReturn;
}

- (void)createResultsWindow
{
if (_resultWindow != nil) {
[_resultWindow release];
}
if (_detailsPanel != nil) {
[_detailsPanel release];
}
// Warning: creation order is important
// If the details panel is not created first and that there are some results in the model
// (happens if we load results), a dupe selection event triggers a details refresh in the
// core before we have the chance to initialize it, and then we crash.
_detailsPanel = [self createDetailsPanel];
_resultWindow = [[ResultWindow alloc] initWithParentApp:self];
}
- (void)showResultsWindow
{
[[[self resultWindow] window] makeKeyAndOrderFront:nil];
}

- (void)showProblemDialog
{
[_problemDialog showWindow:self];
}

- (NSString *)selectDestFolderWithPrompt:(NSString *)prompt
{
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setCanChooseFiles:NO];
[op setCanChooseDirectories:YES];
[op setCanCreateDirectories:YES];
[op setAllowsMultipleSelection:NO];
[op setTitle:prompt];
if ([op runModal] == NSOKButton) {
return [[[op URLs] objectAtIndex:0] path];
}
else {
return nil;
}
}

- (NSString *)selectDestFileWithPrompt:(NSString *)prompt extension:(NSString *)extension
{
NSSavePanel *sp = [NSSavePanel savePanel];
[sp setCanCreateDirectories:YES];
[sp setAllowedFileTypes:[NSArray arrayWithObject:extension]];
[sp setTitle:prompt];
if ([sp runModal] == NSOKButton) {
return [[sp URL] path];
}
else {
return nil;
}
}

@end

+ 24
- 0
cocoa/Consts.h View File

@@ -0,0 +1,24 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#define JobStarted @"JobStarted"
#define JobInProgress @"JobInProgress"
#define TableFontSize @"TableFontSize"

#define jobLoad @"job_load"
#define jobScan @"job_scan"
#define jobCopy @"job_copy"
#define jobMove @"job_move"
#define jobDelete @"job_delete"

#define DGPrioritizeIndexPasteboardType @"DGPrioritizeIndexPasteboardType"
#define ImageLoadedNotification @"ImageLoadedNotification"

#define AppModeStandard 0
#define AppModeMusic 1
#define AppModePicture 2

+ 33
- 0
cocoa/DeletionOptions.h View File

@@ -0,0 +1,33 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import "PyDeletionOptions.h"

@interface DeletionOptions : NSWindowController
{
PyDeletionOptions *model;
NSTextField *messageTextField;
NSButton *linkButton;
NSMatrix *linkTypeRadio;
NSButton *directButton;
}

@property (readwrite, retain) NSTextField *messageTextField;
@property (readwrite, retain) NSButton *linkButton;
@property (readwrite, retain) NSMatrix *linkTypeRadio;
@property (readwrite, retain) NSButton *directButton;

- (id)initWithPyRef:(PyObject *)aPyRef;

- (void)updateOptions;
- (void)proceed;
- (void)cancel;
@end

+ 72
- 0
cocoa/DeletionOptions.m View File

@@ -0,0 +1,72 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "DeletionOptions.h"
#import "DeletionOptions_UI.h"
#import "HSPyUtil.h"

@implementation DeletionOptions

@synthesize messageTextField;
@synthesize linkButton;
@synthesize linkTypeRadio;
@synthesize directButton;

- (id)initWithPyRef:(PyObject *)aPyRef
{
self = [super initWithWindow:nil];
model = [[PyDeletionOptions alloc] initWithModel:aPyRef];
[self setWindow:createDeletionOptions_UI(self)];
[model bindCallback:createCallback(@"DeletionOptionsView", self)];
return self;
}

- (void)dealloc
{
[model release];
[super dealloc];
}

- (void)updateOptions
{
[model setLinkDeleted:[linkButton state] == NSOnState];
[model setUseHardlinks:[linkTypeRadio selectedColumn] == 1];
[model setDirect:[directButton state] == NSOnState];
}

- (void)proceed
{
[NSApp stopModalWithCode:NSOKButton];
}

- (void)cancel
{
[NSApp stopModalWithCode:NSCancelButton];
}

/* model --> view */
- (void)updateMsg:(NSString *)msg
{
[messageTextField setStringValue:msg];
}

- (BOOL)show
{
[linkButton setState:NSOffState];
[directButton setState:NSOffState];
[linkTypeRadio selectCellAtRow:0 column:0];
NSInteger r = [NSApp runModalForWindow:[self window]];
[[self window] close];
return r == NSOKButton;
}

- (void)setHardlinkOptionEnabled:(BOOL)enabled
{
[linkTypeRadio setEnabled:enabled];
}
@end

+ 31
- 0
cocoa/DetailsPanel.h View File

@@ -0,0 +1,31 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import <Python.h>
#import "PyDetailsPanel.h"

@interface DetailsPanel : NSWindowController <NSTableViewDataSource>
{
NSTableView *detailsTable;
PyDetailsPanel *model;
}

@property (readwrite, retain) NSTableView *detailsTable;

- (id)initWithPyRef:(PyObject *)aPyRef;
- (PyDetailsPanel *)model;

- (NSWindow *)createWindow;
- (BOOL)isVisible;
- (void)toggleVisibility;

/* Python --> Cocoa */
- (void)refresh;
@end

+ 81
- 0
cocoa/DetailsPanel.m View File

@@ -0,0 +1,81 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "DetailsPanel.h"
#import "HSPyUtil.h"
#import "DetailsPanel_UI.h"

@implementation DetailsPanel

@synthesize detailsTable;

- (id)initWithPyRef:(PyObject *)aPyRef
{
self = [super initWithWindow:nil];
[self setWindow:[self createWindow]];
model = [[PyDetailsPanel alloc] initWithModel:aPyRef];
[model bindCallback:createCallback(@"DetailsPanelView", self)];
return self;
}

- (void)dealloc
{
[model release];
[super dealloc];
}

- (PyDetailsPanel *)model
{
return (PyDetailsPanel *)model;
}

- (NSWindow *)createWindow
{
return createDetailsPanel_UI(self);
}

- (void)refreshDetails
{
[detailsTable reloadData];
}

- (BOOL)isVisible
{
return [[self window] isVisible];
}

- (void)toggleVisibility
{
if ([self isVisible]) {
[[self window] close];
}
else {
[self refreshDetails]; // selection might have changed since last time
[[self window] orderFront:nil];
}
}

/* NSTableView Delegate */
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [[self model] numberOfRows];
}

- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row
{
return [[self model] valueForColumn:[column identifier] row:row];
}

/* Python --> Cocoa */
- (void)refresh
{
if ([[self window] isVisible]) {
[self refreshDetails];
}
}
@end

+ 32
- 0
cocoa/DetailsPanelPicture.h View File

@@ -0,0 +1,32 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import "DetailsPanel.h"
#import "PyDupeGuru.h"

@interface DetailsPanelPicture : DetailsPanel
{
NSImageView *dupeImage;
NSProgressIndicator *dupeProgressIndicator;
NSImageView *refImage;
NSProgressIndicator *refProgressIndicator;
PyDupeGuru *pyApp;
BOOL _needsRefresh;
NSString *_dupePath;
NSString *_refPath;
}

@property (readwrite, retain) NSImageView *dupeImage;
@property (readwrite, retain) NSProgressIndicator *dupeProgressIndicator;
@property (readwrite, retain) NSImageView *refImage;
@property (readwrite, retain) NSProgressIndicator *refProgressIndicator;

- (id)initWithApp:(PyDupeGuru *)aApp;
@end

+ 96
- 0
cocoa/DetailsPanelPicture.m View File

@@ -0,0 +1,96 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "Utils.h"
#import "NSNotificationAdditions.h"
#import "NSImageAdditions.h"
#import "PyDupeGuru.h"
#import "DetailsPanelPicture.h"
#import "Consts.h"
#import "DetailsPanelPicture_UI.h"

@implementation DetailsPanelPicture

@synthesize dupeImage;
@synthesize dupeProgressIndicator;
@synthesize refImage;
@synthesize refProgressIndicator;

- (id)initWithApp:(PyDupeGuru *)aApp
{
self = [super initWithPyRef:[aApp detailsPanel]];
pyApp = aApp;
_needsRefresh = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(imageLoaded:) name:ImageLoadedNotification object:self];
return self;
}

- (NSWindow *)createWindow
{
return createDetailsPanelPicture_UI(self);
}

- (void)loadImageAsync:(NSString *)imagePath
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSImage *image = [[NSImage alloc] initByReferencingFile:imagePath];
NSImage *thumbnail = [image imageByScalingProportionallyToSize:NSMakeSize(512,512)];
[image release];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
[params setValue:imagePath forKey:@"imagePath"];
[params setValue:thumbnail forKey:@"image"];
[[NSNotificationCenter defaultCenter] postNotificationOnMainThreadWithName:ImageLoadedNotification object:self userInfo:params waitUntilDone:YES];
[pool release];
}

- (void)refreshDetails
{
if (!_needsRefresh)
return;
[detailsTable reloadData];
NSString *refPath = [pyApp getSelectedDupeRefPath];
if (_refPath != nil)
[_refPath autorelease];
_refPath = [refPath retain];
[NSThread detachNewThreadSelector:@selector(loadImageAsync:) toTarget:self withObject:refPath];
NSString *dupePath = [pyApp getSelectedDupePath];
if (_dupePath != nil)
[_dupePath autorelease];
_dupePath = [dupePath retain];
if (![dupePath isEqual: refPath])
[NSThread detachNewThreadSelector:@selector(loadImageAsync:) toTarget:self withObject:dupePath];
[refProgressIndicator startAnimation:nil];
[dupeProgressIndicator startAnimation:nil];
_needsRefresh = NO;
}

/* Notifications */
- (void)imageLoaded:(NSNotification *)aNotification
{
NSString *imagePath = [[aNotification userInfo] valueForKey:@"imagePath"];
NSImage *image = [[aNotification userInfo] valueForKey:@"image"];
if ([imagePath isEqual: _refPath])
{
[refImage setImage:image];
[refProgressIndicator stopAnimation:nil];
}
if ([imagePath isEqual: _dupePath])
{
[dupeImage setImage:image];
[dupeProgressIndicator stopAnimation:nil];
}
}

/* Python --> Cocoa */
- (void)refresh
{
_needsRefresh = YES;
[super refresh];
}
@end

+ 21
- 0
cocoa/DirectoryOutline.h View File

@@ -0,0 +1,21 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import <Python.h>
#import "HSOutline.h"
#import "PyDirectoryOutline.h"

#define DGAddedFoldersNotification @"DGAddedFoldersNotification"

@interface DirectoryOutline : HSOutline {}
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView;
- (PyDirectoryOutline *)model;

- (void)selectAll;
@end;

+ 87
- 0
cocoa/DirectoryOutline.m View File

@@ -0,0 +1,87 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "DirectoryOutline.h"

@implementation DirectoryOutline
- (id)initWithPyRef:(PyObject *)aPyRef outlineView:(HSOutlineView *)aOutlineView
{
self = [super initWithPyRef:aPyRef wrapperClass:[PyDirectoryOutline class]
callbackClassName:@"DirectoryOutlineView" view:aOutlineView];
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
return self;
}

- (PyDirectoryOutline *)model
{
return (PyDirectoryOutline *)model;
}

/* Public */
- (void)selectAll
{
[[self model] selectAll];
}

/* Delegate */
- (NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id < NSDraggingInfo >)info proposedItem:(id)item proposedChildIndex:(NSInteger)index
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [info draggingSourceOperationMask];
pboard = [info draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
if (sourceDragMask & NSDragOperationLink)
return NSDragOperationLink;
}
return NSDragOperationNone;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id < NSDraggingInfo >)info item:(id)item childIndex:(NSInteger)index
{
NSPasteboard *pboard;
NSDragOperation sourceDragMask;
sourceDragMask = [info draggingSourceOperationMask];
pboard = [info draggingPasteboard];
if ([[pboard types] containsObject:NSFilenamesPboardType]) {
NSArray *foldernames = [pboard propertyListForType:NSFilenamesPboardType];
if (!(sourceDragMask & NSDragOperationLink))
return NO;
for (NSString *foldername in foldernames) {
[[self model] addDirectory:foldername];
}
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:foldernames forKey:@"foldernames"];
[[NSNotificationCenter defaultCenter] postNotificationName:DGAddedFoldersNotification
object:self userInfo:userInfo];
}
return YES;
}

- (void)outlineView:(NSOutlineView *)aOutlineView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn item:(id)item
{
if ([cell isKindOfClass:[NSTextFieldCell class]]) {
NSTextFieldCell *textCell = cell;
NSIndexPath *path = item;
BOOL selected = [path isEqualTo:[[self view] selectedPath]];
if (selected) {
[textCell setTextColor:[NSColor blackColor]];
return;
}
NSInteger state = [self intProperty:@"state" valueAtPath:path];
if (state == 1) {
[textCell setTextColor:[NSColor blueColor]];
}
else if (state == 2) {
[textCell setTextColor:[NSColor redColor]];
}
else {
[textCell setTextColor:[NSColor blackColor]];
}
}
}
@end

+ 57
- 0
cocoa/DirectoryPanel.h View File

@@ -0,0 +1,57 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import "HSOutlineView.h"
#import "HSRecentFiles.h"
#import "DirectoryOutline.h"
#import "PyDupeGuru.h"

@class AppDelegate;

@interface DirectoryPanel : NSWindowController <NSOpenSavePanelDelegate>
{
AppDelegate *_app;
PyDupeGuru *model;
HSRecentFiles *_recentDirectories;
DirectoryOutline *outline;
BOOL _alwaysShowPopUp;
NSSegmentedControl *appModeSelector;
NSPopUpButton *scanTypePopup;
NSPopUpButton *addButtonPopUp;
NSPopUpButton *loadRecentButtonPopUp;
HSOutlineView *outlineView;
NSButton *removeButton;
NSButton *loadResultsButton;
}

@property (readwrite, retain) NSSegmentedControl *appModeSelector;
@property (readwrite, retain) NSPopUpButton *scanTypePopup;
@property (readwrite, retain) NSPopUpButton *addButtonPopUp;
@property (readwrite, retain) NSPopUpButton *loadRecentButtonPopUp;
@property (readwrite, retain) HSOutlineView *outlineView;
@property (readwrite, retain) NSButton *removeButton;
@property (readwrite, retain) NSButton *loadResultsButton;

- (id)initWithParentApp:(AppDelegate *)aParentApp;

- (void)fillPopUpMenu;
- (void)fillScanTypeMenu;
- (void)adjustUIToLocalization;

- (void)askForDirectory;
- (void)popupAddDirectoryMenu:(id)sender;
- (void)popupLoadRecentMenu:(id)sender;
- (void)removeSelectedDirectory;
- (void)startDuplicateScan;

- (void)addDirectory:(NSString *)directory;
- (void)refreshRemoveButtonText;
- (void)markAll;

@end

+ 256
- 0
cocoa/DirectoryPanel.m View File

@@ -0,0 +1,256 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "DirectoryPanel.h"
#import "DirectoryPanel_UI.h"
#import "Dialogs.h"
#import "Utils.h"
#import "AppDelegate.h"
#import "Consts.h"

@implementation DirectoryPanel

@synthesize appModeSelector;
@synthesize scanTypePopup;
@synthesize addButtonPopUp;
@synthesize loadRecentButtonPopUp;
@synthesize outlineView;
@synthesize removeButton;
@synthesize loadResultsButton;

- (id)initWithParentApp:(AppDelegate *)aParentApp
{
self = [super initWithWindow:nil];
[self setWindow:createDirectoryPanel_UI(self)];
_app = aParentApp;
model = [_app model];
[[self window] setTitle:[model appName]];
self.appModeSelector.selectedSegment = 0;
[self fillScanTypeMenu];
_alwaysShowPopUp = NO;
[self fillPopUpMenu];
_recentDirectories = [[HSRecentFiles alloc] initWithName:@"recentDirectories" menu:[addButtonPopUp menu]];
[_recentDirectories setDelegate:self];
outline = [[DirectoryOutline alloc] initWithPyRef:[model directoryTree] outlineView:outlineView];
[self refreshRemoveButtonText];
[self adjustUIToLocalization];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(directorySelectionChanged:)
name:NSOutlineViewSelectionDidChangeNotification object:outlineView];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(outlineAddedFolders:)
name:DGAddedFoldersNotification object:outline];
return self;
}

- (void)dealloc
{
[outline release];
[_recentDirectories release];
[super dealloc];
}

/* Private */

- (void)fillPopUpMenu
{
NSMenu *m = [addButtonPopUp menu];
NSMenuItem *mi = [m addItemWithTitle:NSLocalizedString(@"Add New Folder...", @"") action:@selector(askForDirectory) keyEquivalent:@""];
[mi setTarget:self];
[m addItem:[NSMenuItem separatorItem]];
}

- (void)fillScanTypeMenu
{
[[self scanTypePopup] unbind:@"selectedIndex"];
[[self scanTypePopup] removeAllItems];
[[self scanTypePopup] addItemsWithTitles:[[_app model] getScanOptions]];
NSString *keypath;
NSInteger appMode = [_app getAppMode];
if (appMode == AppModePicture) {
keypath = @"values.scanTypePicture";
}
else if (appMode == AppModeMusic) {
keypath = @"values.scanTypeMusic";
}
else {
keypath = @"values.scanTypeStandard";
}
[[self scanTypePopup] bind:@"selectedIndex" toObject:[NSUserDefaultsController sharedUserDefaultsController] withKeyPath:keypath options:nil];
}

- (void)adjustUIToLocalization
{
NSString *lang = [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
NSInteger loadResultsWidthDelta = 0;
if ([lang isEqual:@"ru"]) {
loadResultsWidthDelta = 50;
}
else if ([lang isEqual:@"uk"]) {
loadResultsWidthDelta = 70;
}
else if ([lang isEqual:@"hy"]) {
loadResultsWidthDelta = 30;
}
if (loadResultsWidthDelta) {
NSRect r = [loadResultsButton frame];
r.size.width += loadResultsWidthDelta;
r.origin.x -= loadResultsWidthDelta;
[loadResultsButton setFrame:r];
}
}

/* Actions */

- (void)askForDirectory
{
NSOpenPanel *op = [NSOpenPanel openPanel];
[op setCanChooseFiles:YES];
[op setCanChooseDirectories:YES];
[op setAllowsMultipleSelection:YES];
[op setTitle:NSLocalizedString(@"Select a folder to add to the scanning list", @"")];
[op setDelegate:self];
if ([op runModal] == NSOKButton) {
for (NSURL *directoryURL in [op URLs]) {
[self addDirectory:[directoryURL path]];
}
}
}

- (void)changeAppMode:(id)sender
{
NSInteger appMode;
NSUInteger selectedSegment = self.appModeSelector.selectedSegment;
if (selectedSegment == 2) {
appMode = AppModePicture;
}
else if (selectedSegment == 1) {
appMode = AppModeMusic;
}
else {
appMode = AppModeStandard;
}
[_app setAppMode:appMode];
[self fillScanTypeMenu];
}

- (void)popupAddDirectoryMenu:(id)sender
{
if ((!_alwaysShowPopUp) && ([[_recentDirectories filepaths] count] == 0)) {
[self askForDirectory];
}
else {
[addButtonPopUp selectItem:nil];
[[addButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
}
}

- (void)popupLoadRecentMenu:(id)sender
{
if ([[[_app recentResults] filepaths] count] > 0) {
NSMenu *m = [loadRecentButtonPopUp menu];
while ([m numberOfItems] > 0) {
[m removeItemAtIndex:0];
}
NSMenuItem *mi = [m addItemWithTitle:NSLocalizedString(@"Load from file...", @"") action:@selector(loadResults) keyEquivalent:@""];
[mi setTarget:_app];
[m addItem:[NSMenuItem separatorItem]];
[[_app recentResults] fillMenu:m];
[loadRecentButtonPopUp selectItem:nil];
[[loadRecentButtonPopUp cell] performClickWithFrame:[sender frame] inView:[sender superview]];
}
else {
[_app loadResults];
}
}

- (void)removeSelectedDirectory
{
[[self window] makeKeyAndOrderFront:nil];
[[outline model] removeSelectedDirectory];
[self refreshRemoveButtonText];
}

- (void)startDuplicateScan
{
if ([model resultsAreModified]) {
if ([Dialogs askYesNo:NSLocalizedString(@"You have unsaved results, do you really want to continue?", @"")] == NSAlertSecondButtonReturn) // NO
return;
}
[_app setScanOptions];
[model doScan];
}

/* Public */
- (void)addDirectory:(NSString *)directory
{
[model addDirectory:directory];
[_recentDirectories addFile:directory];
[[self window] makeKeyAndOrderFront:nil];
}

- (void)refreshRemoveButtonText
{
if ([outlineView selectedRow] < 0) {
[removeButton setEnabled:NO];
return;
}
[removeButton setEnabled:YES];
NSIndexPath *path = [outline selectedIndexPath];
if (path != nil) {
NSInteger state = [outline intProperty:@"state" valueAtPath:path];
BOOL shouldDisplayArrow = ([path length] > 1) && (state == 2);
NSString *imgName = shouldDisplayArrow ? @"NSGoLeftTemplate" : @"NSRemoveTemplate";
[removeButton setImage:[NSImage imageNamed:imgName]];
}
}

- (void)markAll
{
/* markAll isn't very descriptive of what we do, but since we re-use the Mark All button from
the result window, we don't have much choice.
*/
[outline selectAll];
}

/* Delegate */
- (BOOL)panel:(id)sender shouldShowFilename:(NSString *)path
{
BOOL isdir;
[[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isdir];
return isdir;
}

- (void)recentFileClicked:(NSString *)path
{
[self addDirectory:path];
}

- (BOOL)validateMenuItem:(NSMenuItem *)item
{
if ([item action] == @selector(markAll)) {
[item setTitle:NSLocalizedString(@"Select All", @"")];
}
return YES;
}

/* Notifications */

- (void)directorySelectionChanged:(NSNotification *)aNotification
{
[self refreshRemoveButtonText];
}

- (void)outlineAddedFolders:(NSNotification *)aNotification
{
NSArray *foldernames = [[aNotification userInfo] objectForKey:@"foldernames"];
for (NSString *foldername in foldernames) {
[_recentDirectories addFile:foldername];
}
}

@end

+ 25
- 0
cocoa/IgnoreListDialog.h View File

@@ -0,0 +1,25 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import "PyIgnoreListDialog.h"
#import "HSTable.h"

@interface IgnoreListDialog : NSWindowController
{
PyIgnoreListDialog *model;
HSTable *ignoreListTable;
NSTableView *ignoreListTableView;
}

@property (readwrite, retain) PyIgnoreListDialog *model;
@property (readwrite, retain) NSTableView *ignoreListTableView;

- (id)initWithPyRef:(PyObject *)aPyRef;
- (void)initializeColumns;
@end

+ 51
- 0
cocoa/IgnoreListDialog.m View File

@@ -0,0 +1,51 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "IgnoreListDialog.h"
#import "IgnoreListDialog_UI.h"
#import "HSPyUtil.h"

@implementation IgnoreListDialog

@synthesize model;
@synthesize ignoreListTableView;

- (id)initWithPyRef:(PyObject *)aPyRef
{
self = [super initWithWindow:nil];
self.model = [[[PyIgnoreListDialog alloc] initWithModel:aPyRef] autorelease];
[self.model bindCallback:createCallback(@"IgnoreListDialogView", self)];
[self setWindow:createIgnoreListDialog_UI(self)];
ignoreListTable = [[HSTable alloc] initWithPyRef:[model ignoreListTable] tableView:ignoreListTableView];
[self initializeColumns];
return self;
}

- (void)dealloc
{
[ignoreListTable release];
[super dealloc];
}

- (void)initializeColumns
{
HSColumnDef defs[] = {
{@"path1", 240, 40, 0, NO, nil},
{@"path2", 240, 40, 0, NO, nil},
nil
};
[[ignoreListTable columns] initializeColumns:defs];
[[ignoreListTable columns] setColumnsAsReadOnly];
}

/* model --> view */
- (void)show
{
[self showWindow:self];
}
@end

+ 38
- 0
cocoa/InfoTemplate.plist View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>dupeGuru</string>
<key>CFBundleHelpBookFolder</key>
<string>dupeguru_help</string>
<key>CFBundleHelpBookName</key>
<string>dupeGuru Help</string>
<key>CFBundleIconFile</key>
<string>dupeguru</string>
<key>CFBundleIdentifier</key>
<string>com.hardcoded-software.dupeguru</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>dupeGuru</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>hsft</string>
<key>CFBundleShortVersionString</key>
<string>{version}</string>
<key>CFBundleVersion</key>
<string>{version}</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHumanReadableCopyright</key>
<string>© Hardcoded Software, 2016</string>
<key>SUFeedURL</key>
<string>https://www.hardcoded.net/updates/dupeguru.appcast</string>
<key>SUPublicDSAKeyFile</key>
<string>dsa_pub.pem</string>
</dict>
</plist>

+ 37
- 0
cocoa/PrioritizeDialog.h View File

@@ -0,0 +1,37 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import "PyPrioritizeDialog.h"
#import "HSPopUpList.h"
#import "HSSelectableList.h"
#import "PrioritizeList.h"
#import "PyDupeGuru.h"

@interface PrioritizeDialog : NSWindowController
{
NSPopUpButton *categoryPopUpView;
NSTableView *criteriaTableView;
NSTableView *prioritizationTableView;
PyPrioritizeDialog *model;
HSPopUpList *categoryPopUp;
HSSelectableList *criteriaList;
PrioritizeList *prioritizationList;
}

@property (readwrite, retain) NSPopUpButton *categoryPopUpView;
@property (readwrite, retain) NSTableView *criteriaTableView;
@property (readwrite, retain) NSTableView *prioritizationTableView;

- (id)initWithApp:(PyDupeGuru *)aApp;
- (PyPrioritizeDialog *)model;

- (void)ok;
- (void)cancel;
@end;

+ 56
- 0
cocoa/PrioritizeDialog.m View File

@@ -0,0 +1,56 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "PrioritizeDialog.h"
#import "PrioritizeDialog_UI.h"
#import "HSPyUtil.h"

@implementation PrioritizeDialog

@synthesize categoryPopUpView;
@synthesize criteriaTableView;
@synthesize prioritizationTableView;

- (id)initWithApp:(PyDupeGuru *)aApp
{
self = [super initWithWindowNibName:@"PrioritizeDialog"];
model = [[PyPrioritizeDialog alloc] initWithApp:[aApp pyRef]];
[self setWindow:createPrioritizeDialog_UI(self)];
categoryPopUp = [[HSPopUpList alloc] initWithPyRef:[[self model] categoryList] popupView:categoryPopUpView];
criteriaList = [[HSSelectableList alloc] initWithPyRef:[[self model] criteriaList] tableView:criteriaTableView];
prioritizationList = [[PrioritizeList alloc] initWithPyRef:[[self model] prioritizationList] tableView:prioritizationTableView];
[model bindCallback:createCallback(@"PrioritizeDialogView", self)];
return self;
}

- (void)dealloc
{
[categoryPopUp release];
[criteriaList release];
[prioritizationList release];
[model release];
[super dealloc];
}

- (PyPrioritizeDialog *)model
{
return (PyPrioritizeDialog *)model;
}

- (void)ok
{
[NSApp stopModal];
[self close];
}

- (void)cancel
{
[NSApp abortModal];
[self close];
}
@end

+ 16
- 0
cocoa/PrioritizeList.h View File

@@ -0,0 +1,16 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import "HSSelectableList.h"
#import "PyPrioritizeList.h"

@interface PrioritizeList : HSSelectableList {}
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView;
- (PyPrioritizeList *)model;
@end

+ 58
- 0
cocoa/PrioritizeList.m View File

@@ -0,0 +1,58 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "PrioritizeList.h"
#import "Utils.h"
#import "Consts.h"

@implementation PrioritizeList
- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView
{
self = [super initWithPyRef:aPyRef wrapperClass:[PyPrioritizeList class]
callbackClassName:@"PrioritizeListView" view:aTableView];
return self;
}

- (PyPrioritizeList *)model
{
return (PyPrioritizeList *)model;
}

- (void)setView:(NSTableView *)aTableView
{
[super setView:aTableView];
[[self view] registerForDraggedTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType]];
}

- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
[pboard declareTypes:[NSArray arrayWithObject:DGPrioritizeIndexPasteboardType] owner:self];
[pboard setData:data forType:DGPrioritizeIndexPasteboardType];
return YES;
}

- (NSDragOperation)tableView:(NSTableView*)tv validateDrop:(id <NSDraggingInfo>)info proposedRow:(NSInteger)row
proposedDropOperation:(NSTableViewDropOperation)op
{
if (op == NSTableViewDropAbove) {
return NSDragOperationMove;
}
return NSDragOperationNone;
}

- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id <NSDraggingInfo>)info
row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
{
NSPasteboard* pboard = [info draggingPasteboard];
NSData* rowData = [pboard dataForType:DGPrioritizeIndexPasteboardType];
NSIndexSet* rowIndexes = [NSKeyedUnarchiver unarchiveObjectWithData:rowData];
[[self model] moveIndexes:[Utils indexSet2Array:rowIndexes] toIndex:row];
return YES;
}
@end

+ 26
- 0
cocoa/ProblemDialog.h View File

@@ -0,0 +1,26 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import <Cocoa/Cocoa.h>
#import "PyProblemDialog.h"
#import "HSTable.h"

@interface ProblemDialog : NSWindowController
{
PyProblemDialog *model;
HSTable *problemTable;
NSTableView *problemTableView;
}

@property (readwrite, retain) PyProblemDialog *model;
@property (readwrite, retain) NSTableView *problemTableView;

- (id)initWithPyRef:(PyObject *)aPyRef;

- (void)initializeColumns;
@end

+ 44
- 0
cocoa/ProblemDialog.m View File

@@ -0,0 +1,44 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
which should be included with this package. The terms are also available at
http://www.gnu.org/licenses/gpl-3.0.html
*/

#import "ProblemDialog.h"
#import "ProblemDialog_UI.h"
#import "Utils.h"

@implementation ProblemDialog

@synthesize model;
@synthesize problemTableView;

- (id)initWithPyRef:(PyObject *)aPyRef
{
self = [super initWithWindow:nil];
self.model = [[PyProblemDialog alloc] initWithModel:aPyRef];
[self setWindow:createProblemDialog_UI(self)];
problemTable = [[HSTable alloc] initWithPyRef:[self.model problemTable] tableView:problemTableView];
[self initializeColumns];
return self;
}

- (void)dealloc
{
[problemTable release];
[super dealloc];
}

- (void)initializeColumns
{
HSColumnDef defs[] = {
{@"path", 202, 40, 0, NO, nil},
{@"msg", 228, 40, 0, NO, nil},
nil
};
[[problemTable columns] initializeColumns:defs];
[[problemTable columns] setColumnsAsReadOnly];
}
@end

+ 23
- 0
cocoa/ResultTable.h View File

@@ -0,0 +1,23 @@
/*
Copyright 2015 Hardcoded Software (http://www.hardcoded.net)

This software is licensed under the "GPLv3" License as described in the "LICENSE" file,