diff --git a/.gitmodules b/.gitmodules index 8fe9536..7acda0c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "hscommon"] path = hscommon url = https://github.com/hsoft/hscommon.git -[submodule "cocoalib"] - path = cocoalib - url = https://github.com/hsoft/cocoalib.git diff --git a/cocoalib b/cocoalib deleted file mode 160000 index 8ce3727..0000000 --- a/cocoalib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8ce3727c704f824f167a36c72c58b4b16d1ab6a3 diff --git a/cocoalib/.gitignore b/cocoalib/.gitignore new file mode 100644 index 0000000..a2ae670 --- /dev/null +++ b/cocoalib/.gitignore @@ -0,0 +1,6 @@ +.DS_Store +__pycache__ +autogen +*.so +/*.lproj/*.strings +!/Base.lproj/Localizable.strings diff --git a/cocoalib/.tx/config b/cocoalib/.tx/config new file mode 100644 index 0000000..9ffd2ec --- /dev/null +++ b/cocoalib/.tx/config @@ -0,0 +1,8 @@ +[main] +host = https://www.transifex.com + +[hscommon.cocoalib] +file_filter = locale//LC_MESSAGES/cocoalib.po +source_file = locale/cocoalib.pot +source_lang = en +type = PO diff --git a/cocoalib/Base.lproj/ErrorReportWindow.xib b/cocoalib/Base.lproj/ErrorReportWindow.xib new file mode 100644 index 0000000..3a5968b --- /dev/null +++ b/cocoalib/Base.lproj/ErrorReportWindow.xib @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!

Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application. + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum Et harumd und lookum like Greek to me, dereud facilis est er expedit distinct. Nam liber te conscient to factor tum poen legum odioque civiuda + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cocoalib/Base.lproj/about.xib b/cocoalib/Base.lproj/about.xib new file mode 100644 index 0000000..ab261c8 --- /dev/null +++ b/cocoalib/Base.lproj/about.xib @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cocoalib/Base.lproj/progress.xib b/cocoalib/Base.lproj/progress.xib new file mode 100644 index 0000000..2939093 --- /dev/null +++ b/cocoalib/Base.lproj/progress.xib @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/cocoalib/Dialogs.h b/cocoalib/Dialogs.h new file mode 100644 index 0000000..5a55972 --- /dev/null +++ b/cocoalib/Dialogs.h @@ -0,0 +1,14 @@ +/* +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 + +@interface Dialogs : NSObject ++ (void)showMessage:(NSString *)message; ++ (NSInteger)askYesNo:(NSString *)message; +@end diff --git a/cocoalib/Dialogs.m b/cocoalib/Dialogs.m new file mode 100644 index 0000000..254d87c --- /dev/null +++ b/cocoalib/Dialogs.m @@ -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 "Dialogs.h" + +@implementation Dialogs ++ (void)showMessage:(NSString *)message +{ + NSAlert *a = [[NSAlert alloc] init]; + [a addButtonWithTitle:NSLocalizedStringFromTable(@"OK", @"cocoalib", @"")]; + [a setMessageText:message]; + [a runModal]; + [a release]; +} + ++ (NSInteger)askYesNo:(NSString *)message +{ + NSAlert *a = [[NSAlert alloc] init]; + [a addButtonWithTitle:NSLocalizedStringFromTable(@"Yes", @"cocoalib", @"")]; + [[a addButtonWithTitle:NSLocalizedStringFromTable(@"No", @"cocoalib", @"")] setKeyEquivalent:@"\E"]; + [a setMessageText:message]; + NSInteger r = [a runModal]; + [a release]; + return r; +} +@end diff --git a/cocoalib/HSAboutBox.h b/cocoalib/HSAboutBox.h new file mode 100644 index 0000000..80b0a69 --- /dev/null +++ b/cocoalib/HSAboutBox.h @@ -0,0 +1,27 @@ +/* +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 +#import "PyBaseApp.h" + +@interface HSAboutBox : NSWindowController +{ + IBOutlet NSTextField *titleTextField; + IBOutlet NSTextField *versionTextField; + IBOutlet NSTextField *copyrightTextField; + + PyBaseApp *app; +} + +@property (readwrite, retain) NSTextField *titleTextField; +@property (readwrite, retain) NSTextField *versionTextField; +@property (readwrite, retain) NSTextField *copyrightTextField; + +- (id)initWithApp:(PyBaseApp *)app; +- (void)updateFields; +@end \ No newline at end of file diff --git a/cocoalib/HSAboutBox.m b/cocoalib/HSAboutBox.m new file mode 100644 index 0000000..a064daa --- /dev/null +++ b/cocoalib/HSAboutBox.m @@ -0,0 +1,41 @@ +/* +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 "HSAboutBox.h" + +@implementation HSAboutBox + +@synthesize titleTextField; +@synthesize versionTextField; +@synthesize copyrightTextField; + +- (id)initWithApp:(PyBaseApp *)aApp +{ + self = [super initWithWindowNibName:@"about"]; + [self window]; + app = [aApp retain]; + [self updateFields]; + return self; +} + +- (void)dealloc +{ + [app release]; + [super dealloc]; +} + +- (void)updateFields +{ + [titleTextField setStringValue:[app appLongName]]; + NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; + [versionTextField setStringValue:[NSString stringWithFormat:@"Version: %@",version]]; + NSString *copyright = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSHumanReadableCopyright"]; + [copyrightTextField setStringValue:copyright]; +} + +@end diff --git a/cocoalib/HSErrorReportWindow.h b/cocoalib/HSErrorReportWindow.h new file mode 100644 index 0000000..00597ae --- /dev/null +++ b/cocoalib/HSErrorReportWindow.h @@ -0,0 +1,26 @@ +/* +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 + +@interface HSErrorReportWindow : NSWindowController +{ + IBOutlet NSTextView *contentTextView; + NSString *githubUrl; +} + +@property (readwrite, retain) NSTextView *contentTextView; +@property (readwrite, retain) NSString *githubUrl; + +// True if the user wants to send the report ++ (void)showErrorReportWithContent:(NSString *)content githubUrl:(NSString *)githubUrl; +- (id)initWithContent:(NSString *)content githubUrl:(NSString *)githubUrl; + +- (IBAction)goToGithub:(id)sender; +- (IBAction)close:(id)sender; +@end \ No newline at end of file diff --git a/cocoalib/HSErrorReportWindow.m b/cocoalib/HSErrorReportWindow.m new file mode 100644 index 0000000..bbe5280 --- /dev/null +++ b/cocoalib/HSErrorReportWindow.m @@ -0,0 +1,43 @@ +/* +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 "HSErrorReportWindow.h" + +@implementation HSErrorReportWindow + +@synthesize contentTextView; +@synthesize githubUrl; + ++ (void)showErrorReportWithContent:(NSString *)content githubUrl:(NSString *)githubUrl +{ + HSErrorReportWindow *report = [[HSErrorReportWindow alloc] initWithContent:content githubUrl:githubUrl]; + [NSApp runModalForWindow:[report window]]; + [report release]; +} + +- (id)initWithContent:(NSString *)content githubUrl:(NSString *)aGithubUrl +{ + self = [super initWithWindowNibName:@"ErrorReportWindow"]; + [self window]; + [contentTextView alignLeft:nil]; + [[[contentTextView textStorage] mutableString] setString:content]; + self.githubUrl = aGithubUrl; + return self; +} + +- (IBAction)goToGithub:(id)sender +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:self.githubUrl]]; +} + +- (IBAction)close:(id)sender +{ + [[self window] orderOut:self]; + [NSApp stopModalWithCode:NSOKButton]; +} +@end \ No newline at end of file diff --git a/cocoalib/HSGeometry.h b/cocoalib/HSGeometry.h new file mode 100644 index 0000000..fd9a224 --- /dev/null +++ b/cocoalib/HSGeometry.h @@ -0,0 +1,15 @@ +/* +Copyright 2015 Hardcoded Software (http://www.hardcoded.net) + +This software is licensed under the "GPLv3" License as described in the "LICENSE" file, +which should be included with this package. The terms are also available at +http://www.gnu.org/licenses/gpl-3.0.html +*/ + +#import +#import + +CGFloat deg2rad(CGFloat deg); +CGFloat distance(NSPoint p1, NSPoint p2); +NSPoint pointInCircle(NSPoint center, CGFloat radius, CGFloat angle); +CGFloat angleFromPoints(NSPoint pt1, NSPoint pt2); \ No newline at end of file diff --git a/cocoalib/HSGeometry.m b/cocoalib/HSGeometry.m new file mode 100644 index 0000000..2aa9fda --- /dev/null +++ b/cocoalib/HSGeometry.m @@ -0,0 +1,71 @@ +/* +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 "HSGeometry.h" + +CGFloat deg2rad(CGFloat deg) +{ + return deg * M_PI / 180; +} + +CGFloat distance(NSPoint p1, NSPoint p2) +{ + CGFloat dX = p1.x - p2.x; + CGFloat dY = p1.y - p2.y; + return sqrt(dX * dX + dY * dY); +} + +NSPoint pointInCircle(NSPoint center, CGFloat radius, CGFloat angle) +{ + // a/sin(A) = b/sin(B) = c/sin(C) = 2R + // the start point it (center.x + radius, center.y) and goes counterclockwise + angle = fmod(angle, M_PI*2); + CGFloat C = M_PI/2; + CGFloat A = fmod(angle, M_PI/2); + CGFloat B = C - A; + CGFloat c = radius; + CGFloat ratio = c / sin(C); + CGFloat b = ratio * sin(B); + CGFloat a = ratio * sin(A); + if (angle >= M_PI * 1.5) + return NSMakePoint(center.x + a, center.y - b); + else if (angle >= M_PI) + return NSMakePoint(center.x - b, center.y - a); + else if (angle >= M_PI/2) + return NSMakePoint(center.x - a, center.y + b); + else + return NSMakePoint(center.x + b, center.y + a); +} + +CGFloat angleFromPoints(NSPoint pt1, NSPoint pt2) +{ + // Returns the angle (radian) formed by the line pt1-pt2. The angle follows the same logic + // as in pointInCircle. + // What we do here is that we take the line and reduce it to fit a "unit circle" (circle with + // a radius of 1). Then, either asin(adjusted_dy) or acos(adjusted_dx) will give us our angle. + // We'll use asin(adjusted_dy). + CGFloat length = distance(pt1, pt2); + CGFloat dx = pt2.x - pt1.x; + CGFloat dy = pt2.y - pt1.y; + CGFloat ajdusted_dy = ABS(dy) / length; + CGFloat angle = asin(ajdusted_dy); + + if ((dx < 0) && (dy >= 0)) { + // top-left quadrant + angle = M_PI - angle; + } + else if ((dx < 0) && (dy < 0)) { + // bottom-left quadrant + angle = M_PI + angle; + } + else if ((dx >= 0) && (dy < 0)) { + // bottom-right quadrant + angle = (2 * M_PI) - angle; + } + return angle; +} \ No newline at end of file diff --git a/cocoalib/HSPyUtil.h b/cocoalib/HSPyUtil.h new file mode 100644 index 0000000..f4dc422 --- /dev/null +++ b/cocoalib/HSPyUtil.h @@ -0,0 +1,13 @@ +/* +Copyright 2015 Hardcoded Software (http://www.hardcoded.net) + +This software is licensed under the "GPLv3" License as described in the "LICENSE" file, +which should be included with this package. The terms are also available at +http://www.gnu.org/licenses/gpl-3.0.html +*/ + +#import +#import + +void setCocoaViewsModuleName(NSString *moduleName); +PyObject* createCallback(NSString *aViewClassName, id aViewRef); diff --git a/cocoalib/HSPyUtil.m b/cocoalib/HSPyUtil.m new file mode 100644 index 0000000..ff3e4e3 --- /dev/null +++ b/cocoalib/HSPyUtil.m @@ -0,0 +1,34 @@ +/* +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 "HSPyUtil.h" +#import "ObjP.h" + +static NSString *gCocoaViewsModuleName; +void setCocoaViewsModuleName(NSString *moduleName) +{ + if (gCocoaViewsModuleName != nil) { + [gCocoaViewsModuleName release]; + } + gCocoaViewsModuleName = [moduleName retain]; +} + +PyObject* createCallback(NSString *aViewClassName, id aViewRef) +{ + NSString *moduleName; + if (gCocoaViewsModuleName != nil) { + moduleName = gCocoaViewsModuleName; + } + else { + moduleName = @"inter.CocoaViews"; + } + PyGILState_STATE gilState = PyGILState_Ensure(); + PyObject *pCallback = ObjP_classInstanceWithRef(aViewClassName, moduleName, aViewRef); + PyGILState_Release(gilState); + return pCallback; +} diff --git a/cocoalib/HSQuicklook.h b/cocoalib/HSQuicklook.h new file mode 100644 index 0000000..60f4a91 --- /dev/null +++ b/cocoalib/HSQuicklook.h @@ -0,0 +1,18 @@ +/* +Copyright 2015 Hardcoded Software (http://www.hardcoded.net) + +This software is licensed under the "GPLv3" License as described in the "LICENSE" file, +which should be included with this package. The terms are also available at +http://www.gnu.org/licenses/gpl-3.0.html +*/ + +#import +#import + +@interface HSQLPreviewItem : NSObject +{ + NSURL *url; + NSString *title; +} +- (id)initWithUrl:(NSURL *)aUrl title:(NSString *)aTitle; +@end \ No newline at end of file diff --git a/cocoalib/HSQuicklook.m b/cocoalib/HSQuicklook.m new file mode 100644 index 0000000..d097510 --- /dev/null +++ b/cocoalib/HSQuicklook.m @@ -0,0 +1,36 @@ +/* +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 "HSQuicklook.h" + +@implementation HSQLPreviewItem +- (id)initWithUrl:(NSURL *)aUrl title:(NSString *)aTitle +{ + self = [super init]; + url = [aUrl retain]; + title = [aTitle retain]; + return self; +} + +- (void)dealloc +{ + [url release]; + [title release]; + [super dealloc]; +} + +- (NSURL *)previewItemURL +{ + return url; +} + +- (NSString *)previewItemTitle +{ + return title; +} +@end \ No newline at end of file diff --git a/cocoalib/HSRecentFiles.h b/cocoalib/HSRecentFiles.h new file mode 100644 index 0000000..0c214f2 --- /dev/null +++ b/cocoalib/HSRecentFiles.h @@ -0,0 +1,35 @@ +/* +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 + +@interface HSRecentFiles : NSObject +{ + id delegate; + NSMenu *menu; + NSString *name; + NSMutableArray *filepaths; + NSInteger numberOfMenuItemsToPreserve; +} +- (id)initWithName:(NSString *)aName menu:(NSMenu *)aMenu; + +- (void)addFile:(NSString *)path; +- (void)rebuildMenu; +- (void)fillMenu:(NSMenu *)menu; +- (void)clearMenu:(id)sender; +- (void)menuClick:(id)sender; + +- (NSMenu *)menu; +- (id)delegate; +- (void)setDelegate:(id)aDelegate; +- (NSArray *)filepaths; +@end + +@protocol HSRecentFilesDelegate +- (void)recentFileClicked:(NSString *)path; +@end diff --git a/cocoalib/HSRecentFiles.m b/cocoalib/HSRecentFiles.m new file mode 100644 index 0000000..231ebe6 --- /dev/null +++ b/cocoalib/HSRecentFiles.m @@ -0,0 +1,89 @@ +/* +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 "HSRecentFiles.h" + +@implementation HSRecentFiles +- (id)initWithName:(NSString *)aName menu:(NSMenu *)aMenu +{ + self = [super init]; + name = aName; + menu = [aMenu retain]; + numberOfMenuItemsToPreserve = [menu numberOfItems]; + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + filepaths = [[NSMutableArray alloc] initWithArray:[ud arrayForKey:name]]; + NSFileManager *fm = [NSFileManager defaultManager]; + for (NSInteger i=[filepaths count]-1;i>=0;i--) { + NSString *path = [filepaths objectAtIndex:i]; + // We check for path class because we might be fed with garbage from the prefs. + if ((![path isKindOfClass:[NSString class]]) || (![fm fileExistsAtPath:path])) { + [filepaths removeObjectAtIndex:i]; + } + } + [self rebuildMenu]; + return self; +} + +- (void)dealloc +{ + NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; + [ud setObject:filepaths forKey:name]; + [ud synchronize]; + [filepaths release]; + [menu release]; + [super dealloc]; +} + +- (void)addFile:(NSString *)path +{ + [filepaths removeObject:path]; + [filepaths insertObject:path atIndex:0]; + [self rebuildMenu]; +} + +- (void)rebuildMenu +{ + while ([menu numberOfItems] > numberOfMenuItemsToPreserve) + [menu removeItemAtIndex:[menu numberOfItems]-1]; + [self fillMenu:menu]; + if ([filepaths count] > 0) { + [menu addItem:[NSMenuItem separatorItem]]; + NSMenuItem *mi = [menu addItemWithTitle:NSLocalizedStringFromTable(@"Clear List", @"cocoalib", @"") action:@selector(clearMenu:) keyEquivalent:@""]; + [mi setTarget:self]; + } +} + +- (void)fillMenu:(NSMenu *)menuToFill +{ + for (int i=0;i<[filepaths count];i++) { + NSMenuItem *mi = [menuToFill addItemWithTitle:[filepaths objectAtIndex:i] action:@selector(menuClick:) keyEquivalent:@""]; + [mi setTag:i]; + [mi setTarget:self]; + } +} + +- (void)clearMenu:(id)sender +{ + [filepaths removeAllObjects]; + [self rebuildMenu]; +} + +- (void)menuClick:(id)sender +{ + if (delegate == nil) + return; + if ([delegate respondsToSelector:@selector(recentFileClicked:)]) + [delegate recentFileClicked:[filepaths objectAtIndex:[sender tag]]]; +} + +/* Properties */ +- (NSMenu *)menu {return menu;} +- (id)delegate { return delegate; } +- (void)setDelegate:(id)aDelegate { delegate = aDelegate; } +- (NSArray *)filepaths {return filepaths;} +@end diff --git a/cocoalib/LICENSE b/cocoalib/LICENSE new file mode 100644 index 0000000..5a8d3ce --- /dev/null +++ b/cocoalib/LICENSE @@ -0,0 +1,10 @@ +Copyright 2014, Hardcoded Software Inc., http://www.hardcoded.net +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + * Neither the name of Hardcoded Software Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/cocoalib/NSEventAdditions.h b/cocoalib/NSEventAdditions.h new file mode 100644 index 0000000..e566173 --- /dev/null +++ b/cocoalib/NSEventAdditions.h @@ -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 +*/ + +#import + +@interface NSEvent(NSEventAdditions) +- (unichar)firstCharacter; +- (NSUInteger)flags; +- (NSUInteger)modifierKeysFlags; +- (BOOL)isDeleteOrBackspace; +- (BOOL)isReturnOrEnter; +- (BOOL)isTab; +- (BOOL)isBackTab; +- (BOOL)isSpace; +- (BOOL)isUp; +- (BOOL)isDown; +- (BOOL)isLeft; +- (BOOL)isRight; +@end diff --git a/cocoalib/NSEventAdditions.m b/cocoalib/NSEventAdditions.m new file mode 100644 index 0000000..03f8b00 --- /dev/null +++ b/cocoalib/NSEventAdditions.m @@ -0,0 +1,85 @@ +/* +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 "NSEventAdditions.h" + +@implementation NSEvent(NSEventAdditions) + +- (unichar)firstCharacter +{ + NSString *characters = [self characters]; + if ([characters length] == 0) + { + return '\0'; + } + return [characters characterAtIndex:0]; +} + +- (NSUInteger)flags +{ + // get flags and strip the lower 16 (device dependant) bits + // See modifierFlags's doc for details + return [self modifierFlags] & NSDeviceIndependentModifierFlagsMask; +} + +- (NSUInteger)modifierKeysFlags +{ + // This is modifierFlags with only Command, Opt, Ctrl and Shift, without the rest of the flags + // to pollute. + return [self flags] & (NSShiftKeyMask | NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask); +} + +- (BOOL)isDeleteOrBackspace +{ + unichar firstChar = [self firstCharacter]; + return firstChar == NSDeleteFunctionKey || firstChar == NSDeleteCharFunctionKey || + firstChar == NSDeleteCharacter || firstChar == NSBackspaceCharacter; +} + +- (BOOL)isReturnOrEnter +{ + unichar firstChar = [self firstCharacter]; + return firstChar == NSCarriageReturnCharacter || firstChar == NSEnterCharacter; +} + +- (BOOL)isTab +{ + return [self firstCharacter] == NSTabCharacter; +} + +- (BOOL)isBackTab +{ + return [self firstCharacter] == NSBackTabCharacter; +} + +- (BOOL)isSpace +{ + return ([self firstCharacter] == 0x20) && (![self flags]); +} + +- (BOOL)isUp +{ + return [self firstCharacter] == NSUpArrowFunctionKey; +} + +- (BOOL)isDown +{ + return [self firstCharacter] == NSDownArrowFunctionKey; +} + +- (BOOL)isLeft +{ + return [self firstCharacter] == NSLeftArrowFunctionKey; +} + +- (BOOL)isRight +{ + return [self firstCharacter] == NSRightArrowFunctionKey; +} + +@end diff --git a/cocoalib/NSImageAdditions.h b/cocoalib/NSImageAdditions.h new file mode 100644 index 0000000..410d62d --- /dev/null +++ b/cocoalib/NSImageAdditions.h @@ -0,0 +1,21 @@ +// Created by Scott Stevenson on 9/28/07. +// +// Personal site: http://theocacao.com/ +// Post for this sample: http://theocacao.com/document.page/497 +// +// The code in this project is intended to be used as a learning +// tool for Cocoa programmers. You may freely use the code in +// your own programs, but please do not use the code as-is in +// other tutorials. + +#import + + +@interface NSImage (Extras) + +// creates a copy of the current image while maintaining +// proportions. also centers image, if necessary + +- (NSImage*)imageByScalingProportionallyToSize:(NSSize)aSize; + +@end \ No newline at end of file diff --git a/cocoalib/NSImageAdditions.m b/cocoalib/NSImageAdditions.m new file mode 100644 index 0000000..ad5fedd --- /dev/null +++ b/cocoalib/NSImageAdditions.m @@ -0,0 +1,114 @@ +// Created by Scott Stevenson on 9/28/07. +// +// Personal site: http://theocacao.com/ +// Post for this sample: http://theocacao.com/document.page/497 +// +// The code in this project is intended to be used as a learning +// tool for Cocoa programmers. You may freely use the code in +// your own programs, but please do not use the code as-is in +// other tutorials. + +#import "NSImageAdditions.h" + + +@implementation NSImage (Extras) + +- (NSImage*)imageByScalingProportionallyToSize:(NSSize)targetSize +{ + NSImage* sourceImage = self; + NSImage* newImage = nil; + + if ([sourceImage isValid]) + { + NSSize imageSize = [sourceImage size]; + CGFloat width = imageSize.width; + CGFloat height = imageSize.height; + + CGFloat targetWidth = targetSize.width; + CGFloat targetHeight = targetSize.height; + + // scaleFactor will be the fraction that we'll + // use to adjust the size. For example, if we shrink + // an image by half, scaleFactor will be 0.5. the + // scaledWidth and scaledHeight will be the original, + // multiplied by the scaleFactor. + // + // IMPORTANT: the "targetHeight" is the size of the space + // we're drawing into. The "scaledHeight" is the height that + // the image actually is drawn at, once we take into + // account the ideal of maintaining proportions + + CGFloat scaleFactor = 0.0; + CGFloat scaledWidth = targetWidth; + CGFloat scaledHeight = targetHeight; + + NSPoint thumbnailPoint = NSMakePoint(0,0); + + // since not all images are square, we want to scale + // proportionately. To do this, we find the longest + // edge and use that as a guide. + + if ( NSEqualSizes( imageSize, targetSize ) == NO ) + { + // use the longeset edge as a guide. if the + // image is wider than tall, we'll figure out + // the scale factor by dividing it by the + // intended width. Otherwise, we'll use the + // height. + + CGFloat widthFactor = targetWidth / width; + CGFloat heightFactor = targetHeight / height; + + if ( widthFactor < heightFactor ) + scaleFactor = widthFactor; + else + scaleFactor = heightFactor; + + // ex: 500 * 0.5 = 250 (newWidth) + + scaledWidth = width * scaleFactor; + scaledHeight = height * scaleFactor; + + // center the thumbnail in the frame. if + // wider than tall, we need to adjust the + // vertical drawing point (y axis) + + if ( widthFactor < heightFactor ) + thumbnailPoint.y = (targetHeight - scaledHeight) * 0.5; + + else if ( widthFactor > heightFactor ) + thumbnailPoint.x = (targetWidth - scaledWidth) * 0.5; + } + + + // create a new image to draw into + newImage = [[NSImage alloc] initWithSize:targetSize]; + + // once focus is locked, all drawing goes into this NSImage instance + // directly, not to the screen. It also receives its own graphics + // context. + // + // Also, keep in mind that we're doing this in a background thread. + // You only want to draw to the screen in the main thread, but + // drawing to an offscreen image is (apparently) okay. + + [newImage lockFocus]; + + NSRect thumbnailRect; + thumbnailRect.origin = thumbnailPoint; + thumbnailRect.size.width = scaledWidth; + thumbnailRect.size.height = scaledHeight; + + [sourceImage drawInRect: thumbnailRect + fromRect: NSZeroRect + operation: NSCompositeSourceOver + fraction: 1.0]; + + [newImage unlockFocus]; + + } + + return [newImage autorelease]; +} + +@end \ No newline at end of file diff --git a/cocoalib/NSNotificationAdditions.h b/cocoalib/NSNotificationAdditions.h new file mode 100644 index 0000000..0b8c2a0 --- /dev/null +++ b/cocoalib/NSNotificationAdditions.h @@ -0,0 +1,10 @@ +// from http://www.cocoadev.com/index.pl?NotificationsAcrossThreads +#import +@interface NSNotificationCenter (NSNotificationCenterAdditions) +- (void) postNotificationOnMainThread:(NSNotification *) notification; +- (void) postNotificationOnMainThread:(NSNotification *) notification waitUntilDone:(BOOL) wait; + +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object; +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo; +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo waitUntilDone:(BOOL) wait; +@end diff --git a/cocoalib/NSNotificationAdditions.m b/cocoalib/NSNotificationAdditions.m new file mode 100644 index 0000000..c6081d0 --- /dev/null +++ b/cocoalib/NSNotificationAdditions.m @@ -0,0 +1,48 @@ +#import "NSNotificationAdditions.h" +#import + +@implementation NSNotificationCenter (NSNotificationCenterAdditions) +- (void) postNotificationOnMainThread:(NSNotification *) notification { + if( pthread_main_np() ) return [self postNotification:notification]; + [self postNotificationOnMainThread:notification waitUntilDone:NO]; +} + +- (void) postNotificationOnMainThread:(NSNotification *) notification waitUntilDone:(BOOL) wait { + if( pthread_main_np() ) return [self postNotification:notification]; + [[self class] performSelectorOnMainThread:@selector( _postNotification: ) withObject:notification waitUntilDone:wait]; +} + ++ (void) _postNotification:(NSNotification *) notification { + [[self defaultCenter] postNotification:notification]; +} + +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object { + if( pthread_main_np() ) return [self postNotificationName:name object:object userInfo:nil]; + [self postNotificationOnMainThreadWithName:name object:object userInfo:nil waitUntilDone:NO]; +} + +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo { + if( pthread_main_np() ) return [self postNotificationName:name object:object userInfo:userInfo]; + [self postNotificationOnMainThreadWithName:name object:object userInfo:nil waitUntilDone:NO]; +} + +- (void) postNotificationOnMainThreadWithName:(NSString *) name object:(id) object userInfo:(NSDictionary *) userInfo waitUntilDone:(BOOL) wait { + if( pthread_main_np() ) return [self postNotificationName:name object:object userInfo:userInfo]; + + NSMutableDictionary *info = [[NSMutableDictionary allocWithZone:nil] init]; + [info setObject:name forKey:@"name"]; + if( object ) [info setObject:object forKey:@"object"]; + if( userInfo ) [info setObject:userInfo forKey:@"userInfo"]; + + [[self class] performSelectorOnMainThread:@selector( _postNotificationName: ) withObject:info waitUntilDone:wait]; + [info release]; +} + ++ (void) _postNotificationName:(NSDictionary *) info { + NSString *name = [info objectForKey:@"name"]; + id object = [info objectForKey:@"object"]; + NSDictionary *userInfo = [info objectForKey:@"userInfo"]; + + [[self defaultCenter] postNotificationName:name object:object userInfo:userInfo]; +} +@end \ No newline at end of file diff --git a/cocoalib/ProgressController.h b/cocoalib/ProgressController.h new file mode 100644 index 0000000..c7e7332 --- /dev/null +++ b/cocoalib/ProgressController.h @@ -0,0 +1,50 @@ +/* +Copyright 2015 Hardcoded Software (http://www.hardcoded.net) + +This software is licensed under the "GPLv3" License as described in the "LICENSE" file, +which should be included with this package. The terms are also available at +http://www.gnu.org/licenses/gpl-3.0.html +*/ + +#import +#import "Worker.h" + +extern NSString *JobCompletedNotification; +extern NSString *JobCancelledNotification; + +@interface ProgressController : NSWindowController +{ + IBOutlet NSButton *cancelButton; + IBOutlet NSProgressIndicator *progressBar; + IBOutlet NSTextField *statusText; + IBOutlet NSTextField *descText; + + id _jobId; + BOOL _running; + NSObject *_worker; +} + +@property (readwrite, retain) NSButton *cancelButton; +@property (readwrite, retain) NSProgressIndicator *progressBar; +@property (readwrite, retain) NSTextField *statusText; +@property (readwrite, retain) NSTextField *descText; + ++ (ProgressController *)mainProgressController; + +- (id)init; + +- (IBAction)cancel:(id)sender; + +- (void)hide; +- (void)show; +- (void)showWithCancelButton:(BOOL)cancelEnabled; +- (void)showSheetForParent:(NSWindow *) parentWindow; +- (void)showSheetForParent:(NSWindow *) parentWindow withCancelButton:(BOOL)cancelEnabled; + +/* Properties */ +- (BOOL)isShown; +- (id)jobId; +- (void)setJobId:(id)jobId; +- (void)setJobDesc:(NSString *)desc; +- (void)setWorker:(NSObject *)worker; +@end diff --git a/cocoalib/ProgressController.m b/cocoalib/ProgressController.m new file mode 100644 index 0000000..922942d --- /dev/null +++ b/cocoalib/ProgressController.m @@ -0,0 +1,159 @@ +/* +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 "ProgressController.h" +#import "Utils.h" + +NSString *JobCompletedNotification = @"JobCompletedNotification"; +NSString *JobCancelledNotification = @"JobCancelledNotification"; +static ProgressController *_mainPC = nil; + +@implementation ProgressController + +@synthesize cancelButton; +@synthesize progressBar; +@synthesize statusText; +@synthesize descText; + ++ (ProgressController *)mainProgressController +{ + if (_mainPC == nil) + _mainPC = [[ProgressController alloc] init]; + return _mainPC; +} + +- (id)init +{ + self = [super initWithWindowNibName:@"progress"]; + [self window]; + [progressBar setUsesThreadedAnimation:YES]; + _worker = nil; + _running = NO; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:NSApplicationDidBecomeActiveNotification object:nil]; + return self; +} + +- (IBAction)cancel:(id)sender +{ + [self hide]; +} + +- (void)hide +{ + if (_worker != nil) + [_worker cancelJob]; + [[NSNotificationCenter defaultCenter] postNotificationName:JobCancelledNotification object:self]; + _running = NO; + [NSApp endSheet:[self window] returnCode:NSRunAbortedResponse]; + /* There's this really strange thing where when the app is inactive at the point we want to hide + the progress dialog, it becomes impossible to close it. I guess it's due to some strange + thread-related crap. Anyway, *DO NOT HIDE THE SHEET WHILE THE APP IS INACTIVE*. Do it later, + when the app becomes active again. + */ + if ([NSApp isActive]) { + [[self window] orderOut:nil]; + } +} + +- (void)show +{ + [self showWithCancelButton:YES]; +} + +- (void)showWithCancelButton:(BOOL)cancelEnabled +{ + [progressBar setIndeterminate:YES]; + [[self window] makeKeyAndOrderFront:nil]; + [progressBar setUsesThreadedAnimation:YES]; + [progressBar startAnimation:nil]; + [cancelButton setEnabled:cancelEnabled]; + _running = YES; + [NSThread detachNewThreadSelector:@selector(threadedWorkerProbe) toTarget:self withObject:nil]; +} + +- (void)showSheetForParent:(NSWindow *) parentWindow +{ + [self showSheetForParent:parentWindow withCancelButton:YES]; +} + +- (void)showSheetForParent:(NSWindow *) parentWindow withCancelButton:(BOOL)cancelEnabled +{ + [progressBar setIndeterminate:YES]; + [progressBar startAnimation:nil]; + [cancelButton setEnabled:cancelEnabled]; + _running = YES; + [NSThread detachNewThreadSelector:@selector(threadedWorkerProbe) toTarget:self withObject:nil]; + [NSApp beginSheet:[self window] modalForWindow:parentWindow modalDelegate:nil didEndSelector:nil contextInfo:nil]; +} + +- (void)updateProgress +{ + if (!_running) + return; + NSNumber *progress = [_worker getJobProgress]; + NSString *status = [_worker getJobDesc]; + if ((status != nil) && ([status length] > 0)) + { + [statusText setStringValue:status]; + } + if (progress != nil) + { + [progressBar setDoubleValue:n2i(progress)]; + [progressBar setIndeterminate: n2i(progress) < 0]; + } + else + { + [self hide]; + [_worker jobCompleted:_jobId]; + [[NSNotificationCenter defaultCenter] postNotificationName:JobCompletedNotification object:self]; + } +} + +- (void)threadedWorkerProbe +{ + while (_running && (_worker != nil)) + { + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]]; + [self performSelectorOnMainThread:@selector(updateProgress) withObject:nil waitUntilDone:YES]; + [pool release]; + } +} + +/* Properties */ +- (BOOL)isShown +{ + return _running; +} + +- (id)jobId {return _jobId;} +- (void)setJobId:(id)jobId +{ + [_jobId autorelease]; + _jobId = [jobId retain]; +} + +- (void)setJobDesc:(NSString *)desc +{ + [descText setStringValue:desc]; + [statusText setStringValue:NSLocalizedStringFromTable(@"Please wait...", @"cocoalib", @"")]; +} + +- (void)setWorker:(NSObject *)worker +{ + _worker = worker; +} + +/* Delegate and Notifs */ +- (void)applicationDidBecomeActive:(NSNotification *)notification +{ + if (!_running) { + [[self window] orderOut:nil]; + } +} +@end diff --git a/cocoalib/Utils.h b/cocoalib/Utils.h new file mode 100644 index 0000000..233e034 --- /dev/null +++ b/cocoalib/Utils.h @@ -0,0 +1,36 @@ +/* +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 + +//Useful shortcuts +#define i2n(i) [NSNumber numberWithInteger:i] +#define n2i(n) [n integerValue] +#define b2n(b) [NSNumber numberWithBool:b] +#define n2b(n) [n boolValue] +#if __LP64__ + #define f2n(d) [NSNumber numberWithDouble:d] + #define n2f(n) [n doubleValue] +#else + #define f2n(f) [NSNumber numberWithFloat:f] + #define n2f(n) [n floatValue] +#endif +#define p2a(p) [Utils indexPath2Array:p] +#define a2p(a) [Utils array2IndexPath:a] +#define fmt(x,...) [NSString stringWithFormat:x,__VA_ARGS__] + +@interface Utils : NSObject ++ (NSArray *)indexSet2Array:(NSIndexSet *)aIndexSet; ++ (NSIndexSet *)array2IndexSet:(NSArray *)numberArray; ++ (NSArray *)indexPath2Array:(NSIndexPath *)aIndexPath; ++ (NSIndexPath *)array2IndexPath:(NSArray *)indexArray; ++ (NSString *)indexPath2String:(NSIndexPath *)aIndexPath; ++ (NSIndexPath *)string2IndexPath:(NSString *)aString; +@end + +void replacePlaceholderInView(NSView *placeholder, NSView *replaceWith); \ No newline at end of file diff --git a/cocoalib/Utils.m b/cocoalib/Utils.m new file mode 100644 index 0000000..024a095 --- /dev/null +++ b/cocoalib/Utils.m @@ -0,0 +1,92 @@ +/* +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 + +@implementation Utils +//This is to pass index sets to python as arrays (so it can be converted to native lists) ++ (NSArray *)indexSet2Array:(NSIndexSet *)aIndexSet +{ + NSMutableArray *r = [NSMutableArray array]; + NSInteger i = [aIndexSet firstIndex]; + while (i != NSNotFound) + { + [r addObject:[NSNumber numberWithInteger:i]]; + i = [aIndexSet indexGreaterThanIndex:i]; + } + return r; +} + +// numberArray is an array of NSNumber ++ (NSIndexSet *)array2IndexSet:(NSArray *)numberArray +{ + NSMutableIndexSet *set = [NSMutableIndexSet indexSet]; + NSEnumerator *e = [numberArray objectEnumerator]; + NSNumber *n; + while (n = [e nextObject]) + [set addIndex:n2i(n)]; + return set; +} + +//Changes an NSIndexPath into an NSArray ++ (NSArray *)indexPath2Array:(NSIndexPath *)aIndexPath +{ + NSMutableArray *r = [NSMutableArray array]; + if (!aIndexPath) + return r; + for (int i=0;i<[aIndexPath length];i++) + [r addObject:i2n([aIndexPath indexAtPosition:i])]; + return r; +} + +// Changes a NSArray of numbers into a NSIndexPath +// indexArray must have at least one item ++ (NSIndexPath *)array2IndexPath:(NSArray *)indexArray +{ + if (![indexArray count]) + { + return nil; + } + NSEnumerator *e = [indexArray objectEnumerator]; + NSNumber *n = [e nextObject]; + NSIndexPath *ip = [NSIndexPath indexPathWithIndex:n2i(n)]; + while (n = [e nextObject]) + ip = [ip indexPathByAddingIndex:n2i(n)]; + return ip; +} + ++ (NSString *)indexPath2String:(NSIndexPath *)aIndexPath +{ + NSMutableArray *components = [NSMutableArray array]; + for (int i=0; i<[aIndexPath length]; i++) + [components addObject:i2n([aIndexPath indexAtPosition:i])]; + return [components componentsJoinedByString:@"_"]; +} + ++ (NSIndexPath *)string2IndexPath:(NSString *)aString +{ + if (aString == nil) + { + return nil; + } + NSArray *components = [aString componentsSeparatedByString:@"_"]; + NSMutableArray *indexes = [NSMutableArray array]; + for (int i=0; i<[components count]; i++) + [indexes addObject:i2n([[components objectAtIndex:i] intValue])]; + return [Utils array2IndexPath:indexes]; +} +@end + +void replacePlaceholderInView(NSView *placeholder, NSView *replaceWith) +{ + NSView *parent = [placeholder superview]; + [replaceWith setFrame:[placeholder frame]]; + [replaceWith setAutoresizingMask:[placeholder autoresizingMask]]; + [parent replaceSubview:placeholder with:replaceWith]; +} diff --git a/cocoalib/ValueTransformers.h b/cocoalib/ValueTransformers.h new file mode 100644 index 0000000..e1ef288 --- /dev/null +++ b/cocoalib/ValueTransformers.h @@ -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 + + +@interface VTIsIntIn : NSValueTransformer +{ + NSIndexSet *ints; + BOOL reverse; +} +- (id)initWithValues:(NSIndexSet *)values; +- (id)initWithValues:(NSIndexSet *)values reverse:(BOOL)doReverse; +@end + +@interface HSVTAdd : NSValueTransformer +{ + int toAdd; +} +- (id)initWithValue:(int)value; +@end diff --git a/cocoalib/ValueTransformers.m b/cocoalib/ValueTransformers.m new file mode 100644 index 0000000..24428f5 --- /dev/null +++ b/cocoalib/ValueTransformers.m @@ -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 "ValueTransformers.h" +#import "Utils.h" + +@implementation VTIsIntIn +- (id)initWithValues:(NSIndexSet *)values +{ + return [self initWithValues:values reverse:NO]; +} + +- (id)initWithValues:(NSIndexSet *)values reverse:(BOOL)doReverse +{ + self = [super init]; + ints = values; + [ints retain]; + reverse = doReverse; + return self; +} + +- (void)dealloc +{ + [ints release]; + [super dealloc]; +} + ++ (Class)transformedValueClass +{ + return [NSNumber class]; //Boolean +} + ++ (BOOL)allowsReverseTransformation +{ + return NO; +} + +- (id)transformedValue:(id)value +{ + if (value == nil) + return nil; + NSNumber *i = value; + BOOL r = [ints containsIndex:[i intValue]]; + if (reverse) + r = !r; + return [NSNumber numberWithBool:r]; +} +@end + +@implementation HSVTAdd +- (id)initWithValue:(int)value +{ + self = [super init]; + toAdd = value; + return self; +} + ++ (Class)transformedValueClass +{ + return [NSNumber class]; +} + ++ (BOOL)allowsReverseTransformation +{ + return NO; +} + +- (id)transformedValue:(id)value +{ + if (value == nil) + return nil; + return i2n(n2i(value) + toAdd); +} +@end diff --git a/cocoalib/Worker.h b/cocoalib/Worker.h new file mode 100644 index 0000000..65130ff --- /dev/null +++ b/cocoalib/Worker.h @@ -0,0 +1,14 @@ +#import + +//The worker should work in a separate thread or have it's own mechanism to keep the GUI updated as ProgressController +//provides none. +@protocol Worker +// -1: Indeterminate. nil: Not working. 0-100: Progressing +- (NSNumber *)getJobProgress; +- (NSString *)getJobDesc; +- (void)cancelJob; +/* This might seem a little stupid, but it's the simplest way to get a **sync** call to the python +side after a job. Because the python-side app is not an NSObject subclass, it can't listen to +notifications. */ +- (void)jobCompleted:(NSString *)jobid; +@end \ No newline at end of file diff --git a/cocoalib/cocoa/CocoaProxy.h b/cocoalib/cocoa/CocoaProxy.h new file mode 100644 index 0000000..0646e9b --- /dev/null +++ b/cocoalib/cocoa/CocoaProxy.h @@ -0,0 +1,34 @@ +#import + +@interface CocoaProxy : NSObject +{ + NSAutoreleasePool *currentPool; +} +- (void)openPath:(NSString *)path; +- (void)openURL:(NSString *)url; +- (void)revealPath:(NSString *)path; +- (NSString *)getUTI:(NSString *)path; +- (BOOL)type:(NSString *)type conformsToType:(NSString *)refType; +- (NSString *)getAppdataPath; +- (NSString *)getCachePath; +- (NSString *)getResourcePath; +- (NSString *)systemLang; +- (NSString *)systemShortDateFormat; +- (NSString *)systemNumberDecimalSeparator; +- (NSString *)systemNumberGroupingSeparator; +- (NSString *)systemCurrency; +- (NSString *)bundleIdentifier; +- (NSString *)appVersion; +- (NSString *)osxVersion; +- (NSString *)bundleInfo:(NSString *)key; +- (void)postNotification:(NSString *)name userInfo:(NSDictionary *)userInfo; +- (id)prefValue:(NSString *)prefname; +- (void)setPrefValue:(NSString *)prefname value:(id)value; +- (id)prefValue:(NSString *)prefname inDomain:(NSString *)domain; +- (NSString *)url2path:(NSString *)url; +- (void)createPool; +- (void)destroyPool; +- (void)reportCrash:(NSString *)crashReport withGithubUrl:(NSString *)githubUrl; +- (void)log:(NSString *)s; +- (NSDictionary *)readExifData:(NSString *)imagePath; +@end \ No newline at end of file diff --git a/cocoalib/cocoa/CocoaProxy.m b/cocoalib/cocoa/CocoaProxy.m new file mode 100644 index 0000000..7051a27 --- /dev/null +++ b/cocoalib/cocoa/CocoaProxy.m @@ -0,0 +1,171 @@ +#import "CocoaProxy.h" +#import "HSErrorReportWindow.h" + +@implementation CocoaProxy +- (void)openPath:(NSString *)path +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:path isDirectory:NO]]; +} + +- (void)openURL:(NSString *)url +{ + [[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]]; +} + +- (void)revealPath:(NSString *)path +{ + [[NSWorkspace sharedWorkspace] selectFile:path inFileViewerRootedAtPath:@""]; +} + +- (NSString *)getUTI:(NSString *)path +{ + NSError *error; + return [[NSWorkspace sharedWorkspace] typeOfFile:path error:&error]; +} + +- (BOOL)type:(NSString *)type conformsToType:(NSString *)refType +{ + return [[NSWorkspace sharedWorkspace] type:type conformsToType:refType]; +} + +- (NSString *)getAppdataPath +{ + return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0]; +} +- (NSString *)getCachePath +{ + return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]; +} + +- (NSString *)getResourcePath +{ + return [[[NSBundle mainBundle] resourceURL] path]; +} + +- (NSString *)systemLang +{ + return [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0]; +} + +- (NSString *)systemShortDateFormat +{ + [NSDateFormatter setDefaultFormatterBehavior:NSDateFormatterBehavior10_4]; + NSDateFormatter *f = [[NSDateFormatter alloc] init]; + [f setDateStyle:NSDateFormatterShortStyle]; + [f setTimeStyle:NSDateFormatterNoStyle]; + NSString *result = [[f dateFormat] retain]; + [f release]; + return [result autorelease]; +} + +- (NSString *)systemNumberDecimalSeparator +{ + [NSNumberFormatter setDefaultFormatterBehavior:NSNumberFormatterBehavior10_4]; + NSNumberFormatter *f = [[NSNumberFormatter alloc] init]; + NSString *result = [[f decimalSeparator] retain]; + [f release]; + return [result autorelease]; +} + +- (NSString *)systemNumberGroupingSeparator +{ + [NSNumberFormatter setDefaultFormatterBehavior:NSNumberFormatterBehavior10_4]; + NSNumberFormatter *f = [[NSNumberFormatter alloc] init]; + NSString *result = [[f groupingSeparator] retain]; + [f release]; + return [result autorelease]; +} + +- (NSString *)systemCurrency +{ + return [[NSLocale currentLocale] objectForKey:NSLocaleCurrencyCode]; +} + +- (NSString *)bundleIdentifier +{ + return [[NSBundle mainBundle] bundleIdentifier]; +} + +- (NSString *)appVersion +{ + return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; +} + +- (NSString *)bundleInfo:(NSString *)key +{ + return [[NSBundle mainBundle] objectForInfoDictionaryKey:key]; +} + +- (NSString *)osxVersion +{ + return [[NSProcessInfo processInfo] operatingSystemVersionString]; +} + +- (void)postNotification:(NSString *)name userInfo:(NSDictionary *)userInfo +{ + [[NSNotificationCenter defaultCenter] postNotificationName:name object:nil userInfo:userInfo]; +} + +- (id)prefValue:(NSString *)prefname +{ + return [[NSUserDefaults standardUserDefaults] objectForKey:prefname]; +} + +- (void)setPrefValue:(NSString *)prefname value:(id)value +{ + [[NSUserDefaults standardUserDefaults] setObject:value forKey:prefname]; +} + +- (id)prefValue:(NSString *)prefname inDomain:(NSString *)domain +{ + NSDictionary *dict = [[NSUserDefaults standardUserDefaults] persistentDomainForName:domain]; + return [dict objectForKey:prefname]; +} + +// Changes a file:/// path into a normal path +- (NSString *)url2path:(NSString *)url +{ + NSURL *u = [NSURL URLWithString:url]; + return [u path]; +} + +// Create a pool for use into a separate thread. +- (void)createPool +{ + [self destroyPool]; + currentPool = [[NSAutoreleasePool alloc] init]; +} +- (void)destroyPool +{ + if (currentPool != nil) { + [currentPool release]; + currentPool = nil; + } +} + +- (void)reportCrash:(NSString *)crashReport withGithubUrl:(NSString *)githubUrl +{ + return [HSErrorReportWindow showErrorReportWithContent:crashReport githubUrl:githubUrl]; +} + +- (void)log:(NSString *)s +{ + NSLog(@"%@", s); +} + +- (NSDictionary *)readExifData:(NSString *)imagePath +{ + NSDictionary *result = nil; + NSURL* url = [NSURL fileURLWithPath:imagePath]; + CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, nil); + if (source != nil) { + CFDictionaryRef metadataRef = CGImageSourceCopyPropertiesAtIndex (source, 0, nil); + if (metadataRef != nil) { + result = [NSDictionary dictionaryWithDictionary:(NSDictionary *)metadataRef]; + CFRelease(metadataRef); + } + CFRelease(source); + } + return result; +} +@end \ No newline at end of file diff --git a/cocoalib/cocoa/__init__.py b/cocoalib/cocoa/__init__.py new file mode 100644 index 0000000..5a23bf2 --- /dev/null +++ b/cocoalib/cocoa/__init__.py @@ -0,0 +1,118 @@ +# Created By: Virgil Dupras +# Created On: 2007-10-06 +# 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 logging +import time +import traceback +import sys + +from .CocoaProxy import CocoaProxy + +proxy = CocoaProxy() + +def autoreleasepool(func): + def wrapper(*args, **kwargs): + proxy.createPool() + try: + func(*args, **kwargs) + finally: + proxy.destroyPool() + return wrapper + +def as_fetch(as_list, as_type, step_size=1000): + """When fetching items from a very big list through applescript, the connection with the app + will timeout. This function is to circumvent that. 'as_type' is the type of the items in the + list (found in appscript.k). If we don't pass it to the 'each' arg of 'count()', it doesn't work. + applescript is rather stupid...""" + result = [] + # no timeout. default timeout is 60 secs, and it is reached for libs > 30k songs + item_count = as_list.count(each=as_type, timeout=0) + steps = item_count // step_size + if item_count % step_size: + steps += 1 + logging.info('Fetching %d items in %d steps' % (item_count, steps)) + # Don't forget that the indexes are 1-based and that the upper limit is included + for step in range(steps): + begin = step * step_size + 1 + end = min(item_count, begin + step_size - 1) + if end > begin: + result += as_list[begin:end](timeout=0) + else: # When there is only one item, the stupid fuck gives it directly instead of putting it in a list. + result.append(as_list[begin:end](timeout=0)) + time.sleep(.1) + logging.info('%d items fetched' % len(result)) + return result + +def extract_tb_noline(tb): + # Same as traceback.extract_tb(), but without line fetching + limit = 100 + list = [] + n = 0 + while tb is not None and (limit is None or n < limit): + f = tb.tb_frame + lineno = tb.tb_lineno + co = f.f_code + filename = co.co_filename + name = co.co_name + list.append((filename, lineno, name, None)) + tb = tb.tb_next + n = n+1 + return list + +def safe_format_exception(type, value, tb): + """Format exception from type, value and tb and fallback if there's a problem. + + In some cases in threaded exceptions under Cocoa, I get tracebacks targeting pyc files instead + of py files, which results in traceback.format_exception() trying to print lines from pyc files + and then crashing when trying to interpret that binary data as utf-8. We want a fallback in + these cases. + """ + try: + return traceback.format_exception(type, value, tb) + except Exception: + result = ['Traceback (most recent call last):\n'] + result.extend(traceback.format_list(extract_tb_noline(tb))) + result.extend(traceback.format_exception_only(type, value)) + return result + +def install_exception_hook(github_url): + def report_crash(type, value, tb): + app_identifier = proxy.bundleIdentifier() + app_version = proxy.appVersion() + osx_version = proxy.osxVersion() + s = "Application Identifier: {}\n".format(app_identifier) + s += "Application Version: {}\n".format(app_version) + s += "Mac OS X Version: {}\n\n".format(osx_version) + s += ''.join(safe_format_exception(type, value, tb)) + if LOG_BUFFER: + s += '\nRelevant Console logs:\n\n' + s += '\n'.join(LOG_BUFFER) + proxy.reportCrash_withGithubUrl_(s, github_url) + + sys.excepthook = report_crash + +# A global log buffer to use for error reports +LOG_BUFFER = [] + +class CocoaHandler(logging.Handler): + def emit(self, record): + msg = record.getMessage() + proxy.log_(msg) + LOG_BUFFER.append(msg) + del LOG_BUFFER[:-20] + +def install_cocoa_logger(): + logging.getLogger().addHandler(CocoaHandler()) + +def patch_threaded_job_performer(): + # _async_run, under cocoa, has to be run within an autorelease pool to prevent leaks. + # You only need this patch is you use one of CocoaProxy's function (which allocate objc + # structures) inside a threaded job. + from hscommon.jobprogress.performer import ThreadedJobPerformer + ThreadedJobPerformer._async_run = autoreleasepool(ThreadedJobPerformer._async_run) + diff --git a/cocoalib/cocoa/inter.py b/cocoalib/cocoa/inter.py new file mode 100644 index 0000000..01d7e77 --- /dev/null +++ b/cocoalib/cocoa/inter.py @@ -0,0 +1,300 @@ +import logging +from objp.util import pyref, dontwrap +from . import proxy + +class GUIObjectView: + def refresh(self): pass + +class PyGUIObject: + def __init__(self, model: pyref): + self.model = model + self.callback = None + + # This *has* to be called right after initialization. + def bindCallback_(self, callback: pyref): + self.callback = callback + self.model.view = self + + # Call this before the ObjC callback is deallocated to avoid calls to that deallocated instance. + def free(self): + self.model.view = None + self.callback = None + + def modelRef(self) -> pyref: + return self.model + + #--- Python -> Cocoa + @dontwrap + def refresh(self): + self.callback.refresh() + +class PyTextField(PyGUIObject): + def text(self) -> str: + return self.model.text + + def setText_(self, newtext: str): + self.model.text = newtext + + +class SelectableListView(GUIObjectView): + def updateSelection(self): pass + +class PySelectableList(PyGUIObject): + def items(self) -> list: + # Should normally always return strings + return self.model[:] + + def selectIndex_(self, index: int): + self.model.select(index) + + def selectedIndex(self) -> int: + result = self.model.selected_index + if result is None: + result = -1 + return result + + def selectedIndexes(self) -> list: + return self.model.selected_indexes + + def selectIndexes_(self, indexes: list): + self.model.select(indexes) + + def searchByPrefix_(self, prefix: str) -> int: + return self.model.search_by_prefix(prefix) + + #--- model --> view + @dontwrap + def update_selection(self): + self.callback.updateSelection() + +class ColumnsView: + def restoreColumns(self): pass + def setColumn_visible_(self, colname: str, visible: bool): pass + +class PyColumns(PyGUIObject): + def columnNamesInOrder(self) -> list: + return self.model.colnames + + def columnDisplay_(self, colname: str) -> str: + return self.model.column_display(colname) + + def columnIsVisible_(self, colname: str) -> bool: + return self.model.column_is_visible(colname) + + def columnWidth_(self, colname: str) -> int: + return self.model.column_width(colname) + + def moveColumn_toIndex_(self, colname: str, index: int): + self.model.move_column(colname, index) + + def resizeColumn_toWidth_(self, colname: str, newwidth: int): + self.model.resize_column(colname, newwidth) + + def setColumn_defaultWidth_(self, colname: str, width: int): + self.model.set_default_width(colname, width) + + def menuItems(self) -> list: + return self.model.menu_items() + + def toggleMenuItem_(self, index: int) -> bool: + return self.model.toggle_menu_item(index) + + def resetToDefaults(self): + self.model.reset_to_defaults() + + #--- Python --> Cocoa + @dontwrap + def restore_columns(self): + self.callback.restoreColumns() + + @dontwrap + def set_column_visible(self, colname: str, visible): + self.callback.setColumn_visible_(colname, visible) + +class OutlineView(GUIObjectView): + def startEditing(self): pass + def stopEditing(self): pass + def updateSelection(self): pass + +class PyOutline(PyGUIObject): + def cancelEdits(self): + self.model.cancel_edits() + + def canEditProperty_atPath_(self, property: str, path: list) -> bool: + node = self.model.get_node(path) + assert node is self.model.selected_node + return getattr(node, 'can_edit_' + property, False) + + def saveEdits(self): + self.model.save_edits() + + def selectedPath(self) -> list: + return self.model.selected_path + + def setSelectedPath_(self, path: list): + self.model.selected_path = path + + def selectedPaths(self) -> list: + return self.model.selected_paths + + def setSelectedPaths_(self, paths: list): + self.model.selected_paths = paths + + def property_valueAtPath_(self, property: str, path: list) -> object: + try: + return getattr(self.model.get_node(path), property) + except IndexError: + logging.warning("%r doesn't have a node at path %r", self.model, path) + return '' + + def setProperty_value_atPath_(self, property: str, value: object, path: list): + setattr(self.model.get_node(path), property, value) + + #--- Python -> Cocoa + @dontwrap + def start_editing(self): + self.callback.startEditing() + + @dontwrap + def stop_editing(self): + self.callback.stopEditing() + + @dontwrap + def update_selection(self): + self.callback.updateSelection() + +class TableView(GUIObjectView): + def showSelectedRow(self): pass + def startEditing(self): pass + def stopEditing(self): pass + def updateSelection(self): pass + +class PyTable(PyGUIObject): + #--- Helpers + @dontwrap + def _getrow(self, row): + try: + return self.model[row] + except IndexError: + msg = "Trying to get an out of bounds row ({} / {}) on table {}" + logging.warning(msg.format(row, len(self.model), self.model.__class__.__name__)) + + #--- Cocoa --> Python + def columns(self) -> pyref: + return self.model.columns + + def add(self): + self.model.add() + + def cancelEdits(self): + self.model.cancel_edits() + + def canEditColumn_atRow_(self, column: str, row: int) -> object: + return self.model.can_edit_cell(column, row) + + def deleteSelectedRows(self): + self.model.delete() + + def numberOfRows(self) -> int: + return len(self.model) + + def saveEdits(self): + self.model.save_edits() + + def selectRows_(self, rows: list): + self.model.select(list(rows)) + + def selectedRows(self) -> list: + return self.model.selected_indexes + + def selectionAsCSV(self) -> str: + return self.model.selection_as_csv() + + def setValue_forColumn_row_(self, value: object, column: str, row: int): + # this try except is important for the case while a row is in edition mode and the delete + # button is clicked. + try: + self._getrow(row).set_cell_value(column, value) + except AttributeError: + msg = "Trying to set an attribute that can't: {} with value {} at row {} on table {}" + logging.warning(msg.format(column, value, row, self.model.__class__.__name__)) + raise + + def sortByColumn_desc_(self, column: str, desc: bool): + self.model.sort_by(column, desc=desc) + + def valueForColumn_row_(self, column: str, row: int) -> object: + return self._getrow(row).get_cell_value(column) + + #--- Python -> Cocoa + @dontwrap + def show_selected_row(self): + self.callback.showSelectedRow() + + @dontwrap + def start_editing(self): + self.callback.startEditing() + + @dontwrap + def stop_editing(self): + self.callback.stopEditing() + + @dontwrap + def update_selection(self): + self.callback.updateSelection() + +class ProgressWindowView(GUIObjectView): + def setProgress_(self, progress: int): pass + def showWindow(self): pass + def closeWindow(self): pass + +class PyProgressWindow(PyGUIObject): + def jobdescTextField(self) -> pyref: + return self.model.jobdesc_textfield + + def progressdescTextField(self) -> pyref: + return self.model.progressdesc_textfield + + def pulse(self): + self.model.pulse() + + def cancel(self): + self.model.cancel() + + #--- Python -> Cocoa + @dontwrap + def set_progress(self, last_progress): + self.callback.setProgress_(last_progress) + + @dontwrap + def show(self): + self.callback.showWindow() + + @dontwrap + def close(self): + self.callback.closeWindow() + + +class BaseAppView: + def showMessage_(self, msg: str): pass + +class PyBaseApp(PyGUIObject): + def appName(self) -> str: + return self.model.PROMPT_NAME + + def appLongName(self) -> str: + return self.model.NAME + + #--- Python --> Cocoa + @dontwrap + def get_default(self, key_name): + return proxy.prefValue_(key_name) + + @dontwrap + def set_default(self, key_name, value): + proxy.setPrefValue_value_(key_name, value) + + @dontwrap + def show_message(self, msg): + self.callback.showMessage_(msg) + diff --git a/cocoalib/controllers/HSColumns.h b/cocoalib/controllers/HSColumns.h new file mode 100644 index 0000000..10f5e39 --- /dev/null +++ b/cocoalib/controllers/HSColumns.h @@ -0,0 +1,38 @@ +/* +Copyright 2015 Hardcoded Software (http://www.hardcoded.net) + +This software is licensed under the "GPLv3" License as described in the "LICENSE" file, +which should be included with this package. The terms are also available at +http://www.gnu.org/licenses/gpl-3.0.html +*/ + +#import +#import +#import "HSGUIController.h" +#import "PyColumns.h" + +/* + This structure is to define constants describing table columns (it's easier to maintain in code + than in XIB files). +*/ +typedef struct { + NSString *attrname; + NSUInteger defaultWidth; + NSUInteger minWidth; + NSUInteger maxWidth; + BOOL sortable; + Class cellClass; +} HSColumnDef; + +@interface HSColumns : HSGUIController {} +- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView; +- (PyColumns *)model; +- (NSTableView *)view; +- (void)connectNotifications; +- (void)disconnectNotifications; +- (void)initializeColumns:(HSColumnDef *)columns; +- (void)initializeColumnsFromModel:(HSColumnDef)columnModel; +- (void)setColumnsAsReadOnly; +- (void)restoreColumns; +- (void)setColumn:(NSString *)colname visible:(BOOL)visible; +@end \ No newline at end of file diff --git a/cocoalib/controllers/HSColumns.m b/cocoalib/controllers/HSColumns.m new file mode 100644 index 0000000..58d8b32 --- /dev/null +++ b/cocoalib/controllers/HSColumns.m @@ -0,0 +1,198 @@ +/* +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 "HSColumns.h" +#import "Utils.h" +#import "HSTableView.h" // To prevent warning on stopEditing + +@implementation HSColumns +- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView +{ + self = [super initWithPyRef:aPyRef wrapperClass:[PyColumns class] + callbackClassName:@"ColumnsView" view:aTableView]; + [self connectNotifications]; + return self; +} + +- (void)dealloc +{ + [self disconnectNotifications]; + [super dealloc]; +} + +- (PyColumns *)model +{ + return (PyColumns *)model; +} + +- (NSTableView *)view +{ + return (NSTableView *)view; +} + +- (void)connectNotifications +{ + if ([self view] == nil) { + /* This can happen if there something broken somewhere, and even though when that happens, + it means that something serious is going on, the fact that we connect to all columnMoved: + events messes thigs up even MORE. Don't connect when tableView is nil! + */ + return; + } + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(columnMoved:) + name:NSTableViewColumnDidMoveNotification object:[self view]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(columnMoved:) + name:NSOutlineViewColumnDidMoveNotification object:[self view]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(columnResized:) + name:NSTableViewColumnDidResizeNotification object:[self view]]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(columnResized:) + name:NSOutlineViewColumnDidResizeNotification object:[self view]]; +} + +- (void)disconnectNotifications +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +/* + It is assumed, when this method is used, that the table/outline is empty *OR* that it is not + defined in the column list. + + Special note about NSOutlineView. You can use HSColumns on outline views, but you be aware that + the "main" column (the one having the tree disclosure buttons) cannot be removed. Therefore, + it has to be defined in the XIB and it must *not* be in column defs. +*/ +- (void)initializeColumns:(HSColumnDef *)columns +{ + /* We don't want default widths to overwrite stored with in the core code */ + [self disconnectNotifications]; + /* Translate the title of columns (needed for outlines) present already */ + for (NSTableColumn *c in [[self view] tableColumns]) { + NSString *title = NSLocalizedStringFromTable([[c headerCell] stringValue], @"columns", @""); + [[c headerCell] setStringValue:title]; + } + NSUserDefaultsController *udc = [NSUserDefaultsController sharedUserDefaultsController]; + HSColumnDef *cdef = columns; + while (cdef->attrname != nil) { + if ([[self view] tableColumnWithIdentifier:cdef->attrname] != nil) { + cdef++; + continue; + } + NSTableColumn *c = [[[NSTableColumn alloc] initWithIdentifier:cdef->attrname] autorelease]; + [c setResizingMask:NSTableColumnUserResizingMask]; + /* If the column is not added right away, it causes glitches under 10.5 (minwidths instead of default widths) */ + [[self view] addTableColumn:c]; + NSString *title = [[self model] columnDisplay:cdef->attrname]; + [[c headerCell] setStringValue:title]; + if (cdef->sortable) { + NSSortDescriptor *d = [[[NSSortDescriptor alloc] initWithKey:cdef->attrname ascending:YES] autorelease]; + [c setSortDescriptorPrototype:d]; + } + [c setWidth:cdef->defaultWidth]; + [[self model] setColumn:cdef->attrname defaultWidth:cdef->defaultWidth]; + [c setMinWidth:cdef->minWidth]; + NSUInteger maxWidth = cdef->maxWidth; + if (maxWidth == 0) { + maxWidth = 0xffffff; + } + [c setMaxWidth:maxWidth]; + if (cdef->cellClass != nil) { + id cell = [[[cdef->cellClass alloc] initTextCell:@""] autorelease]; + [cell setEditable:YES]; + [c setDataCell:cell]; + } + [c bind:@"fontSize" toObject:udc withKeyPath:@"values.TableFontSize" options:nil]; + cdef++; + } + [self connectNotifications]; +} + +/* + Here, instead of having all our column defs, we have one column model, which we use to create + our column defs using column names in [[self model] columnNamesInOrder]. +*/ +- (void)initializeColumnsFromModel:(HSColumnDef)columnModel +{ + NSArray *colnames = [[self model] columnNamesInOrder]; + HSColumnDef *defs = (HSColumnDef *)malloc(([colnames count]+1)*sizeof(HSColumnDef)); + HSColumnDef *def = defs; + for (NSString *colname in colnames) { + def->attrname = colname; + def->defaultWidth = columnModel.defaultWidth; + def->minWidth = columnModel.minWidth; + def->maxWidth = columnModel.maxWidth; + def->sortable = columnModel.sortable; + def->cellClass = columnModel.cellClass; + def++; + } + def->attrname = nil; // Sentinel + [self initializeColumns:defs]; + free(defs); +} + +- (void)setColumnsAsReadOnly +{ + for (NSTableColumn *col in [[self view] tableColumns]) { + [col setEditable:NO]; + } +} + +/* Notifications */ +- (void)columnMoved:(NSNotification *)notification +{ + /* We only get this call after the move. Although there's "NSOldColumn" and "NSNewColumn", + the old index is irrelevant since we have to find the moved column's name. + */ + NSInteger index = n2i([[notification userInfo] objectForKey:@"NSNewColumn"]); + NSTableColumn *c = [[[self view] tableColumns] objectAtIndex:index]; + NSString *colName = [c identifier]; + [[self model] moveColumn:colName toIndex:index]; +} + +- (void)columnResized:(NSNotification *)notification +{ + NSTableColumn *c = [[notification userInfo] objectForKey:@"NSTableColumn"]; + [[self model] resizeColumn:[c identifier] toWidth:[c width]]; +} + +/* Python --> Cocoa */ +- (void)restoreColumns +{ + [self disconnectNotifications]; + NSArray *columnOrder = [[self model] columnNamesInOrder]; + for (NSInteger i=0; i<[columnOrder count]; i++) { + NSString *colName = [columnOrder objectAtIndex:i]; + NSInteger index = [[self view] columnWithIdentifier:colName]; + if ((index != -1) && (index != i)) { + [[self view] moveColumn:index toColumn:i]; + } + } + for (NSTableColumn *c in [[self view] tableColumns]) { + NSInteger width = [[self model] columnWidth:[c identifier]]; + if (width > 0) { + [c setWidth:width]; + } + BOOL isVisible = [[self model] columnIsVisible:[c identifier]]; + [c setHidden:!isVisible]; + } + [self connectNotifications]; +} + +- (void)setColumn:(NSString *)colname visible:(BOOL)visible +{ + NSTableColumn *col = [[self view] tableColumnWithIdentifier:colname]; + if (col == nil) + return; + if ([col isHidden] == !visible) + return; + if ([[self view] respondsToSelector:@selector(stopEditing)]) { + [(id)[self view] stopEditing]; + } + [col setHidden:!visible]; +} +@end diff --git a/cocoalib/controllers/HSComboBox.h b/cocoalib/controllers/HSComboBox.h new file mode 100644 index 0000000..57c3114 --- /dev/null +++ b/cocoalib/controllers/HSComboBox.h @@ -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 +#import "HSGUIController.h" +#import "PySelectableList.h" + +@interface HSComboBox : HSGUIController +{ + NSArray *items; +} +- (id)initWithPyRef:(PyObject *)aPyRef view:(NSComboBox *)aView; +- (NSComboBox *)view; +- (void)setView:(NSComboBox *)aComboboxView; +- (PySelectableList *)model; + +- (void)comboboxViewSelectionChanged; +- (void)refresh; +- (void)updateSelection; +@end \ No newline at end of file diff --git a/cocoalib/controllers/HSComboBox.m b/cocoalib/controllers/HSComboBox.m new file mode 100644 index 0000000..5e8dea9 --- /dev/null +++ b/cocoalib/controllers/HSComboBox.m @@ -0,0 +1,119 @@ +/* +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 "HSComboBox.h" +#import "HSPyUtil.h" + +@implementation HSComboBox +- (id)initWithPyRef:(PyObject *)aPyRef view:(NSComboBox *)aView +{ + PySelectableList *m = [[PySelectableList alloc] initWithModel:aPyRef]; + self = [super initWithModel:m]; + [m bindCallback:createCallback(@"SelectableListView", self)]; + [m release]; + [self setView:aView]; + return self; +} + +- (void)dealloc +{ + [[self view] setTarget:nil]; + [[self view] setDataSource:nil]; + [items release]; + [super dealloc]; +} + +- (NSComboBox *)view +{ + return (NSComboBox *)view; +} + +- (void)setView:(NSComboBox *)aComboboxView +{ + if ([self view] != nil) { + [[self view] setDataSource:nil]; + [[self view] setTarget:nil]; + } + [super setView:aComboboxView]; + if (aComboboxView != nil) { + [aComboboxView setUsesDataSource:YES]; + [aComboboxView setDataSource:self]; + [aComboboxView setAction:@selector(comboboxViewSelectionChanged)]; + [aComboboxView setTarget:self]; + /* This is required for the combobox to send its action whenever it's changed. Normally, it's + already set, but then the combobox is created programmatically (xibless), it's not. We + make sure it is here. + */ + [[aComboboxView cell] setSendsActionOnEndEditing:YES]; + [self refresh]; + } +} + +- (PySelectableList *)model +{ + return (PySelectableList *)model; +} + +- (void)comboboxViewSelectionChanged +{ + NSInteger index = [[self view] indexOfSelectedItem]; + if (index >= 0) { + [[self model] selectIndex:index]; + } +} + +/* data source */ +- (id)comboBox:(NSComboBox *)aComboBox objectValueForItemAtIndex:(NSInteger)index +{ + if (index < 0) { + return nil; + } + return [items objectAtIndex:index]; +} + +- (NSInteger)numberOfItemsInComboBox:(NSComboBox *)aComboBox +{ + return [items count]; +} + +- (NSUInteger)comboBox:(NSComboBox *)aComboBox indexOfItemWithStringValue:(NSString *)aString +{ + NSInteger index = [[self model] searchByPrefix:aString]; + if (index >= 0) { + return index; + } + else { + return NSNotFound; + } +} + +- (NSString *)comboBox:(NSComboBox *)aComboBox completedString:(NSString *)uncompletedString +{ + NSInteger index = [[self model] searchByPrefix:uncompletedString]; + if (index >= 0) { + return [items objectAtIndex:index]; + } + else { + return nil; + } +} + +/* model --> view */ +- (void)refresh +{ + [items release]; + items = [[[self model] items] retain]; + [[self view] reloadData]; + [self updateSelection]; +} + +- (void)updateSelection +{ + [[self view] selectItemAtIndex:[[self model] selectedIndex]]; +} +@end \ No newline at end of file diff --git a/cocoalib/controllers/HSGUIController.h b/cocoalib/controllers/HSGUIController.h new file mode 100644 index 0000000..5042ce1 --- /dev/null +++ b/cocoalib/controllers/HSGUIController.h @@ -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, +which should be included with this package. The terms are also available at +http://www.gnu.org/licenses/gpl-3.0.html +*/ + +#import +#import "PyGUIObject.h" + +@interface HSGUIController : NSObject +{ + PyGUIObject *model; + NSView *view; +} +- (id)initWithModel:(PyGUIObject *)aPy; +- (id)initWithModel:(PyGUIObject *)aPy view:(NSView *)aView; +- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(NSView *)aView; +- (PyGUIObject *)model; +- (NSView *)view; +- (void)setView:(NSView *)aView; +@end diff --git a/cocoalib/controllers/HSGUIController.m b/cocoalib/controllers/HSGUIController.m new file mode 100644 index 0000000..b7658e2 --- /dev/null +++ b/cocoalib/controllers/HSGUIController.m @@ -0,0 +1,62 @@ +/* +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 "HSGUIController.h" +#import "HSPyUtil.h" + +@implementation HSGUIController +- (id)initWithModel:(PyGUIObject *)aModel +{ + self = [super init]; + model = [aModel retain]; + view = nil; + return self; +} + +- (id)initWithModel:(PyGUIObject *)aModel view:(NSView *)aView +{ + self = [super init]; + model = [aModel retain]; + [self setView:aView]; + return self; +} + +- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(NSView *)aView +{ + PyGUIObject *m = [[aWrapperClass alloc] initWithModel:aPyRef]; + self = [self initWithModel:m view:aView]; + [m bindCallback:createCallback(aCallbackClassName, self)]; + [m release]; + return self; +} + +- (void)dealloc +{ + // NSLog([NSString stringWithFormat:@"%@ dealloc",[[self class] description]]); + [self setView:nil]; + [model free]; + [model release]; + [super dealloc]; +} + +- (PyGUIObject *)model +{ + return model; +} + +- (NSView *)view +{ + return view; +} + +- (void)setView:(NSView *)aView +{ + [view release]; + view = [aView retain]; +} +@end diff --git a/cocoalib/controllers/HSOutline.h b/cocoalib/controllers/HSOutline.h new file mode 100644 index 0000000..b7f54f8 --- /dev/null +++ b/cocoalib/controllers/HSOutline.h @@ -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 +#import "HSGUIController.h" +#import "HSOutlineView.h" +#import "PyOutline.h" +#import "NSIndexPathAdditions.h" + +@interface HSOutline : HSGUIController { + NSMutableDictionary *itemData; + NSMutableSet *itemRetainer; +} +- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(HSOutlineView *)aView; +- (PyOutline *)model; +- (HSOutlineView *)view; + +/* Public */ +- (void)refresh; +- (NSIndexPath *)selectedIndexPath; +- (NSArray *)selectedIndexPaths; +- (NSString *)dataForCopyToPasteboard; +- (void)startEditing; +- (void)stopEditing; +- (void)updateSelection; +- (void)expandItem:(NSIndexPath *)item; +- (NSIndexPath *)internalizedPath:(NSIndexPath *)path; + +/* Caching */ +- (id)property:(NSString *)property valueAtPath:(NSIndexPath *)path; +- (void)setProperty:(NSString *)property value:(id)value atPath:(NSIndexPath *)path; +- (NSString *)stringProperty:(NSString *)property valueAtPath:(NSIndexPath *)path; +- (void)setStringProperty:(NSString *)property value:(NSString *)value atPath:(NSIndexPath *)path; +- (BOOL)boolProperty:(NSString *)property valueAtPath:(NSIndexPath *)path; +- (void)setBoolProperty:(NSString *)property value:(BOOL)value atPath:(NSIndexPath *)path; +- (NSInteger)intProperty:(NSString *)property valueAtPath:(NSIndexPath *)path; +- (void)setIntProperty:(NSString *)property value:(int)value atPath:(NSIndexPath *)path; +- (void)refreshItemAtPath:(NSIndexPath *)path; +@end \ No newline at end of file diff --git a/cocoalib/controllers/HSOutline.m b/cocoalib/controllers/HSOutline.m new file mode 100644 index 0000000..5bb32c1 --- /dev/null +++ b/cocoalib/controllers/HSOutline.m @@ -0,0 +1,286 @@ +/* +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 "HSOutline.h" +#import "Utils.h" + +#define CHILDREN_COUNT_PROPERTY @"children_count" + +@implementation HSOutline +- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(HSOutlineView *)aView +{ + self = [super initWithPyRef:aPyRef wrapperClass:aWrapperClass callbackClassName:aCallbackClassName view:aView]; + itemData = [[NSMutableDictionary dictionary] retain]; + /* Dictionaries don't retain its keys because it copies them. Our items are NSIndexPath and when + an index path has the same value, it's the same instance. Before OS X 10.7, all these instances + stayed in memory, so we didn't need to worry about retaining them. Hoever, it seems now that + index path instances are sometimes released. Oops. So, we now need to retain our index path + instances and that's why we use itemRetainer. + + In fact, it seems that unlike what the doc says, it's not true that two index paths with the + same value will always be the same instance. + */ + itemRetainer = [[NSMutableSet set] retain]; + if (([[self view] outlineTableColumn] == nil) && ([[[self view] tableColumns] count] > 0)) { + [[self view] setOutlineTableColumn:[[[self view] tableColumns] objectAtIndex:0]]; + } + return self; +} + +- (void)dealloc +{ + [itemData release]; + [itemRetainer release]; + [super dealloc]; +} + +- (HSOutlineView *)view +{ + return (HSOutlineView *)view; +} + +- (void)setView:(HSOutlineView *)aOutlineView +{ + if ([self view] != nil) { + [[self view] setDataSource:nil]; + [[self view] setDelegate:nil]; + } + [super setView:aOutlineView]; + if (aOutlineView != nil) { + [aOutlineView setDataSource:self]; + [aOutlineView setDelegate:self]; + } +} + +- (PyOutline *)model +{ + return (PyOutline *)model; +} + +/* Private */ +- (void)setPySelection +{ + NSMutableArray *paths = [NSMutableArray array]; + NSIndexSet *indexes = [[self view] selectedRowIndexes]; + NSInteger i = [indexes firstIndex]; + while (i != NSNotFound) { + NSIndexPath *path = [[self view] itemAtRow:i]; + [paths addObject:p2a(path)]; + i = [indexes indexGreaterThanIndex:i]; + } + [[self model] setSelectedPaths:paths]; +} + +- (NSIndexPath *)internalizedPath:(NSIndexPath *)path +{ + /* Because NSIndexPath stopped guaranteeing that the same paths always were represented by the + same instances, we have to make sure, when we manipulate paths, that we manipulate the same + instances as those that were given by outlineView:child:ofItem: + */ + NSIndexPath *result = [itemRetainer member:path]; + if (result == nil) { + result = path; + [itemData setObject:[NSMutableDictionary dictionary] forKey:result]; + [itemRetainer addObject:result]; + } + return result; +} + +/* Public */ +- (void)refresh +{ + [itemData removeAllObjects]; + // We can't get rid of our instances just yet, we have to wait until after reloadData + NSSet *oldRetainer = itemRetainer; + itemRetainer = [[NSMutableSet set] retain]; + [[self view] setDelegate:nil]; + [[self view] reloadData]; + [[self view] setDelegate:self]; + /* Item retainer and releasing + + In theory, [oldRetainer release] should work, but in practice, doing so causes occasional + crashes during drag & drop, which I guess keep the reference of an item a bit longer than it + should. This is why we autorelease here. See #354. + */ + [oldRetainer autorelease]; + [self updateSelection]; +} + +- (NSArray *)selectedIndexPaths +{ + NSArray *arrayPaths = [[self model] selectedPaths]; + NSMutableArray *result = [NSMutableArray array]; + for (NSArray *arrayPath in arrayPaths) { + [result addObject:[self internalizedPath:a2p(arrayPath)]]; + } + return result; +} + +- (void)startEditing +{ + [[self view] startEditing]; +} + +- (void)stopEditing +{ + [[self view] stopEditing]; +} + +- (void)updateSelection +{ + [[self view] updateSelection]; +} + +- (void)expandItem:(NSIndexPath *)item +{ + [[self view] ensureExpanded:item]; +} + +/* Caching */ +- (id)property:(NSString *)property valueAtPath:(NSIndexPath *)path +{ + NSMutableDictionary *props = [itemData objectForKey:path]; + id value = [props objectForKey:property]; + if (value == nil) { + value = [[self model] property:property valueAtPath:p2a(path)]; + if (value == nil) { + value = [NSNull null]; + } + [props setObject:value forKey:property]; + } + if (value == [NSNull null]) { + value = nil; + } + return value; +} + +- (void)setProperty:(NSString *)property value:(id)value atPath:(NSIndexPath *)path +{ + NSMutableDictionary *props = [itemData objectForKey:path]; + [props removeObjectForKey:property]; + [[self model] setProperty:property value:value atPath:p2a(path)]; +} + +- (NSString *)stringProperty:(NSString *)property valueAtPath:(NSIndexPath *)path +{ + return [self property:property valueAtPath:path]; +} + +- (void)setStringProperty:(NSString *)property value:(NSString *)value atPath:(NSIndexPath *)path +{ + [self setProperty:property value:value atPath:path]; +} + +- (BOOL)boolProperty:(NSString *)property valueAtPath:(NSIndexPath *)path +{ + NSNumber *value = [self property:property valueAtPath:path]; + return [value boolValue]; +} + +- (void)setBoolProperty:(NSString *)property value:(BOOL)value atPath:(NSIndexPath *)path +{ + [self setProperty:property value:[NSNumber numberWithBool:value] atPath:path]; +} + +- (NSInteger)intProperty:(NSString *)property valueAtPath:(NSIndexPath *)path +{ + NSNumber *value = [self property:property valueAtPath:path]; + return [value intValue]; +} + +- (void)setIntProperty:(NSString *)property value:(int)value atPath:(NSIndexPath *)path +{ + [self setProperty:property value:[NSNumber numberWithInt:value] atPath:path]; +} + +- (void)refreshItemAtPath:(NSIndexPath *)path +{ + NSMutableDictionary *props = [itemData objectForKey:path]; + [props removeAllObjects]; +} + +/* NSOutlineView data source */ + +- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item +{ + return [self intProperty:CHILDREN_COUNT_PROPERTY valueAtPath:(NSIndexPath *)item]; +} + +- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item +{ + NSIndexPath *parent = item; + NSIndexPath *path = parent == nil ? [NSIndexPath indexPathWithIndex:index] : [parent indexPathByAddingIndex:index]; + return [self internalizedPath:path]; +} + +- (BOOL)outlineView:(NSOutlineView *)theOutlineView isItemExpandable:(id)item +{ + return [self outlineView:[self view] numberOfChildrenOfItem:item] > 0; +} + +- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)column item:(id)item +{ + return [[self model] canEditProperty:[column identifier] atPath:p2a((NSIndexPath *)item)]; +} + +- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)column byItem:(id)item +{ + return [self property:[column identifier] valueAtPath:(NSIndexPath *)item]; +} + +- (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)value forTableColumn:(NSTableColumn *)column byItem:(id)item +{ + [self setProperty:[column identifier] value:value atPath:(NSIndexPath *)item]; +} + +/* We need to change the model selection at both IsChanging and DidChange. We need to set the +model selection at IsChanging before of the arrow clicking. The action launched by this little arrow +is performed before DidChange. However, when using the arrow to change the selection, IsChanging is +never called +*/ +- (void)outlineViewSelectionIsChanging:(NSNotification *)notification +{ + [self setPySelection]; +} + +- (void)outlineViewSelectionDidChange:(NSNotification *)notification +{ + [self setPySelection]; +} + +/* HSOutlineView delegate */ +- (NSIndexPath *)selectedIndexPath +{ + NSArray *paths = [self selectedIndexPaths]; + if ([paths count] > 0) { + return [paths objectAtIndex:0]; + } + else { + return nil; + } +} + +- (NSString *)dataForCopyToPasteboard +{ + return nil; +} + +- (void)outlineViewDidEndEditing:(HSOutlineView *)outlineView +{ + [[self model] saveEdits]; +} + +- (void)outlineViewWasDoubleClicked:(HSOutlineView *)outlineView +{ +} + +- (void)outlineViewCancelsEdition:(HSOutlineView *)outlineView +{ + [[self model] cancelEdits]; +} +@end \ No newline at end of file diff --git a/cocoalib/controllers/HSPopUpList.h b/cocoalib/controllers/HSPopUpList.h new file mode 100644 index 0000000..88e8234 --- /dev/null +++ b/cocoalib/controllers/HSPopUpList.h @@ -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, +which should be included with this package. The terms are also available at +http://www.gnu.org/licenses/gpl-3.0.html +*/ + +#import +#import +#import "HSGUIController.h" +#import "PySelectableList.h" + +@interface HSPopUpList : HSGUIController {} +- (id)initWithPyRef:(PyObject *)aPyRef popupView:(NSPopUpButton *)aPopupView; +- (NSPopUpButton *)view; +- (void)setView:(NSPopUpButton *)aPopupView; +- (PySelectableList *)model; + +- (void)popupViewSelectionChanged; +- (void)refresh; +- (void)updateSelection; +@end \ No newline at end of file diff --git a/cocoalib/controllers/HSPopUpList.m b/cocoalib/controllers/HSPopUpList.m new file mode 100644 index 0000000..2e6fbbc --- /dev/null +++ b/cocoalib/controllers/HSPopUpList.m @@ -0,0 +1,60 @@ +/* +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 "HSPopUpList.h" +#import "Utils.h" + +@implementation HSPopUpList +- (id)initWithPyRef:(PyObject *)aPyRef popupView:(NSPopUpButton *)aPopupView +{ + self = [super initWithPyRef:aPyRef wrapperClass:[PySelectableList class] + callbackClassName:@"SelectableListView" view:aPopupView]; + return self; +} + +- (NSPopUpButton *)view +{ + return (NSPopUpButton *)view; +} + +- (void)setView:(NSPopUpButton *)aPopupView +{ + if ([self view] != nil) { + [[self view] setTarget:nil]; + } + [super setView:aPopupView]; + if (aPopupView != nil) { + [aPopupView setAction:@selector(popupViewSelectionChanged)]; + [aPopupView setTarget:self]; + [self refresh]; + } +} + +- (PySelectableList *)model +{ + return (PySelectableList *)model; +} + +- (void)popupViewSelectionChanged +{ + [[self model] selectIndex:[[self view] indexOfSelectedItem]]; +} + +/* model --> view */ +- (void)refresh +{ + [[self view] removeAllItems]; + [[self view] addItemsWithTitles:[[self model] items]]; + [self updateSelection]; +} + +- (void)updateSelection +{ + [[self view] selectItemAtIndex:[[self model] selectedIndex]]; +} +@end \ No newline at end of file diff --git a/cocoalib/controllers/HSProgressWindow.h b/cocoalib/controllers/HSProgressWindow.h new file mode 100644 index 0000000..7d9458c --- /dev/null +++ b/cocoalib/controllers/HSProgressWindow.h @@ -0,0 +1,30 @@ +/* +Copyright 2015 Hardcoded Software (http://www.hardcoded.net) + +This software is licensed under the "GPLv3" License as described in the "LICENSE" file, +which should be included with this package. The terms are also available at +http://www.gnu.org/licenses/gpl-3.0.html +*/ + +#import +#import "HSGUIController.h" +#import "HSTextField.h" +#import "Worker.h" +#import "PyProgressWindow.h" + +@interface HSProgressWindow : HSGUIController +{ + NSInteger progress; + HSTextField *jobdescTextField; + HSTextField *progressdescTextField; + NSWindow *parentWindow; +} + +- (id)initWithPyRef:(PyObject *)aPyRef view:(NSView *)aView; +- (PyProgressWindow *)model; +- (void)setParentWindow:(NSWindow *)aParentWindow; + +- (void)setProgress:(NSInteger)aProgress; +- (void)showWindow; +- (void)closeWindow; +@end \ No newline at end of file diff --git a/cocoalib/controllers/HSProgressWindow.m b/cocoalib/controllers/HSProgressWindow.m new file mode 100644 index 0000000..1ddc211 --- /dev/null +++ b/cocoalib/controllers/HSProgressWindow.m @@ -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 "HSProgressWindow.h" +#import "ProgressController.h" +#import "Utils.h" + +@implementation HSProgressWindow +- (id)initWithPyRef:(PyObject *)aPyRef view:(NSView *)aView +{ + self = [self initWithPyRef:aPyRef wrapperClass:[PyProgressWindow class] callbackClassName:@"ProgressWindowView" view:aView]; + [[ProgressController mainProgressController] setWorker:self]; + jobdescTextField = [[HSTextField alloc] initWithPyRef:[[self model] jobdescTextField] view:[[ProgressController mainProgressController] descText]]; + progressdescTextField = [[HSTextField alloc] initWithPyRef:[[self model] progressdescTextField] view:[[ProgressController mainProgressController] statusText]]; + parentWindow = nil; + return self; +} + +- (PyProgressWindow *)model +{ + return (PyProgressWindow *)model; +} + +/* Public */ +- (void)setParentWindow:(NSWindow *)aParentWindow +{ + parentWindow = aParentWindow; +} + +- (void)setProgress:(NSInteger)aProgress +{ + progress = aProgress; +} + +- (void)showWindow +{ + if (parentWindow != nil) { + [[ProgressController mainProgressController] showSheetForParent:parentWindow]; + } + else { + [[ProgressController mainProgressController] show]; + } +} + +- (void)closeWindow +{ + [[ProgressController mainProgressController] hide]; +} + +/* Worker */ + +- (NSNumber *)getJobProgress +{ + [[self model] pulse]; + return [NSNumber numberWithInt:progress]; +} + +- (NSString *)getJobDesc +{ + // Our desc label is updated independently. + return nil; +} + +- (void)cancelJob +{ + [[self model] cancel]; +} + +- (void)jobCompleted:(NSString *)jobid +{ + // With the new hscommon.gui.progress_window, this call is done from within the core. Do nothing. +} + +@end diff --git a/cocoalib/controllers/HSSelectableList.h b/cocoalib/controllers/HSSelectableList.h new file mode 100644 index 0000000..c2a6645 --- /dev/null +++ b/cocoalib/controllers/HSSelectableList.h @@ -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 +*/ + +#import +#import "HSGUIController.h" +#import "PySelectableList.h" + +@interface HSSelectableList : HSGUIController +{ + NSArray *items; +} +- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(NSTableView *)aTableView; +- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView; +- (NSTableView *)view; +- (void)setView:(NSTableView *)aTableView; +- (PySelectableList *)model; + +- (void)refresh; +@end diff --git a/cocoalib/controllers/HSSelectableList.m b/cocoalib/controllers/HSSelectableList.m new file mode 100644 index 0000000..94e47e3 --- /dev/null +++ b/cocoalib/controllers/HSSelectableList.m @@ -0,0 +1,107 @@ +/* +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 "HSSelectableList.h" +#import "Utils.h" + +@implementation HSSelectableList +- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(NSTableView *)aTableView; +{ + self = [super initWithPyRef:aPyRef wrapperClass:aWrapperClass callbackClassName:aCallbackClassName view:aTableView]; + return self; +} + +- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView +{ + self = [self initWithPyRef:aPyRef wrapperClass:[PySelectableList class] callbackClassName:@"SelectableListView" view:aTableView]; + return self; +} + +- (void)dealloc +{ + [items release]; + [super dealloc]; +} + +- (PySelectableList *)model +{ + return (PySelectableList *)model; +} + +- (NSTableView *)view +{ + return (NSTableView *)view; +} + +- (void)setView:(NSTableView *)aTableView +{ + if ([self view] != nil) { + [[self view] setDataSource:nil]; + [[self view] setDelegate:nil]; + } + [super setView:aTableView]; + if (aTableView != nil) { + [aTableView setDataSource:self]; + [aTableView setDelegate:self]; + [self refresh]; + } +} + +/* Private */ +- (void)setPySelection +{ + NSArray *selection = [Utils indexSet2Array:[[self view] selectedRowIndexes]]; + NSArray *pyselection = [[self model] selectedIndexes]; + if (![selection isEqualTo:pyselection]) { + [[self model] selectIndexes:selection]; + } +} + +- (void)setViewSelection +{ + NSIndexSet *selection = [Utils array2IndexSet:[[self model] selectedIndexes]]; + [[self view] selectRowIndexes:selection byExtendingSelection:NO]; +} + +/* Data source */ +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return [items count]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row +{ + // Cocoa's typeselect mechanism can call us with an out-of-range row + if (row >= [items count]) { + return @""; + } + return [items objectAtIndex:row]; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + [self setPySelection]; +} + +/* Public */ + +- (void)refresh +{ + // If we just deleted the last item, we want to update the selection before we reload + [items release]; + items = [[[self model] items] retain]; + [[self view] reloadData]; + [self setViewSelection]; +} + +- (void)updateSelection +{ + NSIndexSet *selection = [NSIndexSet indexSetWithIndex:[[self model] selectedIndex]]; + [[self view] selectRowIndexes:selection byExtendingSelection:NO]; +} +@end diff --git a/cocoalib/controllers/HSTable.h b/cocoalib/controllers/HSTable.h new file mode 100644 index 0000000..41a2912 --- /dev/null +++ b/cocoalib/controllers/HSTable.h @@ -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 +#import "HSGUIController.h" +#import "HSColumns.h" +#import "PyTable.h" + +@interface HSTable : HSGUIController +{ + HSColumns *columns; +} +- (id)initWithModel:(PyTable *)aModel tableView:(NSTableView *)aTableView; +- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(NSTableView *)aTableView; +- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView; + +/* Virtual */ +- (PyTable *)model; +- (NSTableView *)view; +- (void)setView:(NSTableView *)aTableView; + +/* Public */ +- (HSColumns *)columns; +- (void)refresh; +- (void)showSelectedRow; +- (void)updateSelection; +@end diff --git a/cocoalib/controllers/HSTable.m b/cocoalib/controllers/HSTable.m new file mode 100644 index 0000000..7325c81 --- /dev/null +++ b/cocoalib/controllers/HSTable.m @@ -0,0 +1,136 @@ +/* +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 "HSTable.h" +#import "Utils.h" + +@implementation HSTable +- (id)initWithModel:(PyTable *)aModel tableView:(NSTableView *)aTableView +{ + self = [super initWithModel:aModel view:aTableView]; + columns = [[HSColumns alloc] initWithPyRef:[[self model] columns] tableView:aTableView]; + return self; +} + +- (id)initWithPyRef:(PyObject *)aPyRef wrapperClass:(Class)aWrapperClass callbackClassName:(NSString *)aCallbackClassName view:(NSTableView *)aTableView +{ + self = [super initWithPyRef:aPyRef wrapperClass:aWrapperClass callbackClassName:aCallbackClassName view:aTableView]; + columns = [[HSColumns alloc] initWithPyRef:[[self model] columns] tableView:aTableView]; + return self; +} + +- (id)initWithPyRef:(PyObject *)aPyRef tableView:(NSTableView *)aTableView +{ + return [self initWithPyRef:aPyRef wrapperClass:[PyTable class] callbackClassName:@"TableView" view:aTableView]; +} + +- (void)dealloc +{ + [columns release]; + [super dealloc]; +} + +/* Private */ +- (void)setPySelection +{ + NSArray *selection = [Utils indexSet2Array:[[self view] selectedRowIndexes]]; + NSArray *pyselection = [[self model] selectedRows]; + if (![selection isEqualTo:pyselection]) + [[self model] selectRows:selection]; +} + +- (void)setViewSelection +{ + NSIndexSet *selection = [Utils array2IndexSet:[[self model] selectedRows]]; + [[self view] selectRowIndexes:selection byExtendingSelection:NO]; +} + +/* HSGUIController */ +- (PyTable *)model +{ + return (PyTable *)model; +} + +- (NSTableView *)view +{ + return (NSTableView *)view; +} + +- (void)setView:(NSTableView *)aTableView +{ + if ([self view] != nil) { + [[self view] setDataSource:nil]; + [[self view] setDelegate:nil]; + } + [super setView:aTableView]; + if (aTableView != nil) { + [aTableView setDataSource:self]; + [aTableView setDelegate:self]; + } +} + +/* Data source */ +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return [[self model] numberOfRows]; +} + +- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)column row:(NSInteger)row +{ + // Cocoa's typeselect mechanism can call us with an out-of-range row + if (row >= [[self model] numberOfRows]) { + return @""; + } + return [[self model] valueForColumn:[column identifier] row:row]; +} + +/* NSTableView Delegate */ +- (void)tableView:(NSTableView *)aTableView didClickTableColumn:(NSTableColumn *)column +{ + if ([[aTableView sortDescriptors] count] == 0) { + return; + } + NSSortDescriptor *sd = [[aTableView sortDescriptors] objectAtIndex:0]; + [[self model] sortByColumn:[sd key] desc:![sd ascending]]; +} + +// See HSOutline.outlineViewSelectionIsChanging: to know why we update selection in both notifs +- (void)tableViewSelectionIsChanging:(NSNotification *)notification +{ + [self setPySelection]; +} + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + [self setPySelection]; +} + +/* Public */ +- (HSColumns *)columns +{ + return columns; +} + +- (void)refresh +{ + // If we just deleted the last item, we want to update the selection before we reload + [self setViewSelection]; + [[self view] reloadData]; + [self setViewSelection]; +} + +- (void)showSelectedRow +{ + [[self view] scrollRowToVisible:[[self view] selectedRow]]; +} + +- (void)updateSelection +{ + [self setViewSelection]; +} +@end diff --git a/cocoalib/controllers/HSTextField.h b/cocoalib/controllers/HSTextField.h new file mode 100644 index 0000000..26a3cd2 --- /dev/null +++ b/cocoalib/controllers/HSTextField.h @@ -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 +#import +#import "HSGUIController.h" +#import "PyTextField.h" + +@interface HSTextField : HSGUIController {} +- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aView; +- (NSTextField *)view; +- (void)setView:(NSTextField *)aView; +- (PyTextField *)model; + +- (void)refresh; +@end \ No newline at end of file diff --git a/cocoalib/controllers/HSTextField.m b/cocoalib/controllers/HSTextField.m new file mode 100644 index 0000000..145f126 --- /dev/null +++ b/cocoalib/controllers/HSTextField.m @@ -0,0 +1,53 @@ +/* +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 "HSTextField.h" +#import "Utils.h" + +@implementation HSTextField +- (id)initWithPyRef:(PyObject *)aPyRef view:(NSTextField *)aView +{ + self = [super initWithPyRef:aPyRef wrapperClass:[PyTextField class] + callbackClassName:@"GUIObjectView" view:aView]; + return self; +} + +- (NSTextField *)view +{ + return (NSTextField *)view; +} + +- (void)setView:(NSTextField *)aView +{ + if ([self view] != nil) { + [[self view] setDelegate:nil]; + } + [super setView:aView]; + if (aView != nil) { + [aView setDelegate:self]; + [self refresh]; + } +} + +- (PyTextField *)model +{ + return (PyTextField *)model; +} + +/* Delegate */ +- (void)controlTextDidEndEditing:(NSNotification *)aNotification +{ + [[self model] setText:[[self view] stringValue]]; +} + +/* model --> view */ +- (void)refresh +{ + [[self view] setStringValue:[[self model] text]]; +} +@end \ No newline at end of file diff --git a/cocoalib/locale/cocoalib.pot b/cocoalib/locale/cocoalib.pot new file mode 100644 index 0000000..4589403 --- /dev/null +++ b/cocoalib/locale/cocoalib.pot @@ -0,0 +1,62 @@ +# +msgid "" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" diff --git a/cocoalib/locale/cs/LC_MESSAGES/cocoalib.po b/cocoalib/locale/cs/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..96eafa2 --- /dev/null +++ b/cocoalib/locale/cs/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: cs\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Cancel" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/de/LC_MESSAGES/cocoalib.po b/cocoalib/locale/de/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..84cb629 --- /dev/null +++ b/cocoalib/locale/de/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: de\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Abbrechen" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/el/LC_MESSAGES/cocoalib.po b/cocoalib/locale/el/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..8673966 --- /dev/null +++ b/cocoalib/locale/el/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2016-07-25 11:26+0000\n" +"Last-Translator: 1kakarot\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: el\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Ακύρωση" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "Απαλοιφή λίστας" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "Αναφορά σφάλματος" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "Όχι" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "Παρακαλώ περιμένετε..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "Κατάσταση: Εργασία..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "Εργασία σε εξέλιξη, παρακαλώ περιμένετε." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "Εργασία σε εξέλιξη..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "Ναι" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "Κλείσιμο" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "Επίσκεψη Github" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "Κάτι πήγε λάθος. Να αναφερθεί το σφάλμα;" diff --git a/cocoalib/locale/es/LC_MESSAGES/cocoalib.po b/cocoalib/locale/es/LC_MESSAGES/cocoalib.po new file mode 100755 index 0000000..c4b825c --- /dev/null +++ b/cocoalib/locale/es/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: es\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Cancelar" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "Limpiar lista" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "Informe de error" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "No" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "Aceptar" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "Por favor, espere..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "Estado: procesando..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "En proceso, por favor, espere." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "En proceso..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "Sí" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/fr/LC_MESSAGES/cocoalib.po b/cocoalib/locale/fr/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..e4b5ead --- /dev/null +++ b/cocoalib/locale/fr/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Annuler" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/hy/LC_MESSAGES/cocoalib.po b/cocoalib/locale/hy/LC_MESSAGES/cocoalib.po new file mode 100755 index 0000000..1e2246a --- /dev/null +++ b/cocoalib/locale/hy/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: hy\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Չեղարկել" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/it/LC_MESSAGES/cocoalib.po b/cocoalib/locale/it/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..f852301 --- /dev/null +++ b/cocoalib/locale/it/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: it\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Annulla" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/ko/LC_MESSAGES/cocoalib.po b/cocoalib/locale/ko/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..06823e5 --- /dev/null +++ b/cocoalib/locale/ko/LC_MESSAGES/cocoalib.po @@ -0,0 +1,70 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2015-07-20 16:42+0000\n" +"Last-Translator: Virgil Dupras \n" +"Language-Team: Korean (http://www.transifex.com/p/hscommon/language/ko/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: ko\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" diff --git a/cocoalib/locale/nl/LC_MESSAGES/cocoalib.po b/cocoalib/locale/nl/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..f550b71 --- /dev/null +++ b/cocoalib/locale/nl/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: nl\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/pl_PL/LC_MESSAGES/cocoalib.po b/cocoalib/locale/pl_PL/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..0b0d21a --- /dev/null +++ b/cocoalib/locale/pl_PL/LC_MESSAGES/cocoalib.po @@ -0,0 +1,71 @@ +# +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2015-07-20 16:53+0000\n" +"Last-Translator: Virgil Dupras \n" +"Language-Team: Polish (Poland) (http://www.transifex.com/p/hscommon/language/pl_PL/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: pl_PL\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" diff --git a/cocoalib/locale/pt_BR/LC_MESSAGES/cocoalib.po b/cocoalib/locale/pt_BR/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..75bf94e --- /dev/null +++ b/cocoalib/locale/pt_BR/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: pt_BR\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Cancelar" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "Limpar Lista" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "Não" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "Aguarde..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "Sim" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/ru/LC_MESSAGES/cocoalib.po b/cocoalib/locale/ru/LC_MESSAGES/cocoalib.po new file mode 100755 index 0000000..5dcd285 --- /dev/null +++ b/cocoalib/locale/ru/LC_MESSAGES/cocoalib.po @@ -0,0 +1,72 @@ +# +# Translators: +# Igor Fokusov , 2015 +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2015-04-14 21:20+0000\n" +"Last-Translator: Igor Fokusov \n" +"Language-Team: Russian (http://www.transifex.com/projects/p/hscommon/language/ru/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: ru\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Отменить" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "Очистить список" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "Закрыть" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "Сообщение об ошибке" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "Отчеты об ошибках нужно отправлять в Github issues проекта. Скопируйте текст ошибки выше и вставьте в созданную заметку о проблеме (перед этим желательно проверить - не создано ли уже такой проблемы до вас). Также нам очень поможет краткое описание как вы получили такую ошибку. Спасибо!\n\nВ принципе, программа может продолжать работу, но стабильная работа не гарантируется. Поэтому желательно перезапустить программу." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "Перейти на Github" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "Нет" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "ОК" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "Пожалуйста, ждите..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "Что-то пошло не так. Хотите отправить отчёт об ошибке?" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "Состояние: Работает..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "Идет работа. Пожалуйста, ждите." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "Идет работа..." + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "Да" diff --git a/cocoalib/locale/uk/LC_MESSAGES/cocoalib.po b/cocoalib/locale/uk/LC_MESSAGES/cocoalib.po new file mode 100755 index 0000000..c7cc7b2 --- /dev/null +++ b/cocoalib/locale/uk/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: uk\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "Відмінити" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/vi/LC_MESSAGES/cocoalib.po b/cocoalib/locale/vi/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..3ac0792 --- /dev/null +++ b/cocoalib/locale/vi/LC_MESSAGES/cocoalib.po @@ -0,0 +1,70 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-07-05 11:23+0000\n" +"Last-Translator: hsoft \n" +"Language-Team: Vietnamese (http://www.transifex.com/projects/p/hscommon/language/vi/)\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: vi\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/locale/zh_CN/LC_MESSAGES/cocoalib.po b/cocoalib/locale/zh_CN/LC_MESSAGES/cocoalib.po new file mode 100644 index 0000000..919ffe3 --- /dev/null +++ b/cocoalib/locale/zh_CN/LC_MESSAGES/cocoalib.po @@ -0,0 +1,69 @@ +# Translators: +msgid "" +msgstr "" +"Project-Id-Version: hscommon\n" +"PO-Revision-Date: 2013-04-28 18:26+0000\n" +"Last-Translator: hsoft \n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: utf-8\n" +"Language: zh_CN\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Cancel" +msgstr "取消" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Clear List" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Error Report" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "No" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "OK" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Please wait..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Status: Working..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress, please wait." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Work in progress..." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Yes" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Close" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "" +"Error reports should be reported as Github issues. You can copy the error traceback above and paste it in a new issue (bonus point if you run a search to make sure the issue doesn't already exist). What usually really helps is if you add a description of how you got the error. Thanks!\n" +"\n" +"Although the application should continue to run after this error, it may be in an unstable state, so it is recommended that you restart the application." +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Go to Github" +msgstr "" + +#: cocoalib/en.lproj/cocoalib.strings:0 +msgid "Something went wrong. How about reporting the error?" +msgstr "" diff --git a/cocoalib/views/HSOutlineView.h b/cocoalib/views/HSOutlineView.h new file mode 100644 index 0000000..ec52489 --- /dev/null +++ b/cocoalib/views/HSOutlineView.h @@ -0,0 +1,41 @@ +/* +Copyright 2015 Hardcoded Software (http://www.hardcoded.net) + +This software is licensed under the "GPLv3" License as described in the "LICENSE" file, +which should be included with this package. The terms are also available at +http://www.gnu.org/licenses/gpl-3.0.html +*/ + +#import +#import "NSTableViewAdditions.h" +#import "NSIndexPathAdditions.h" + +@class HSOutlineView; + +@protocol HSOutlineViewDelegate +- (NSArray *)selectedIndexPaths; /* array of NSIndexPath* */ +- (NSString *)dataForCopyToPasteboard; +- (NSIndexPath *)internalizedPath:(NSIndexPath *)path; +- (void)outlineViewDidEndEditing:(HSOutlineView *)outlineView; +- (void)outlineViewCancelsEdition:(HSOutlineView *)outlineView; +- (void)outlineViewWasDoubleClicked:(HSOutlineView *)outlineView; +@end + +@interface HSOutlineView : NSOutlineView +{ + BOOL manualEditionStop; + NSEvent *eventToIgnore; +} +- (id )delegate; +- (void)setDelegate:(id )aDelegate; +- (NSIndexPath *)selectedPath; +- (void)selectPath:(NSIndexPath *)aPath; +- (NSArray *)selectedNodePaths; +- (void)selectNodePaths:(NSArray *)nodePaths; +- (void)stopEditing; +- (void)updateSelection; +- (void)ignoreEventForEdition:(NSEvent *)aEvent; +- (void)ensureExpanded:(NSIndexPath *)aPath; + +- (IBAction)copy:(id)sender; +@end diff --git a/cocoalib/views/HSOutlineView.m b/cocoalib/views/HSOutlineView.m new file mode 100644 index 0000000..14ab995 --- /dev/null +++ b/cocoalib/views/HSOutlineView.m @@ -0,0 +1,190 @@ +/* +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 "HSOutlineView.h" + +@implementation HSOutlineView +- (id )delegate +{ + return (id )[super delegate]; +} + +- (void)setDelegate:(id )aDelegate +{ + [super setDelegate:aDelegate]; + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(outlineViewWasDoubleClicked:)]) { + [self setTarget:[self delegate]]; + [self setDoubleAction:@selector(outlineViewWasDoubleClicked:)]; + } +} +/* NSOutlineView overrides */ +- (void)keyDown:(NSEvent *)event +{ + if (![self dispatchSpecialKeys:event]) + { + [super keyDown:event]; + } +} + +- (BOOL)shouldEditTableColumn:(NSTableColumn *)column row:(NSInteger)row +{ + BOOL result = [super shouldEditTableColumn:column row:row]; + if (!result) + return result; + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(outlineView:shouldEditTableColumn:item:)]) + return [delegate outlineView:self shouldEditTableColumn:column item:[self itemAtRow:row]]; + return YES; +} + +/* Notifications & Delegate */ +- (void)textDidEndEditing:(NSNotification *)notification +{ + notification = [self processTextDidEndEditing:notification]; + NSView *nextKeyView = [self nextKeyView]; + [self setNextKeyView:nil]; + [super textDidEndEditing:notification]; + [self setNextKeyView:nextKeyView]; + + if ([self editedColumn] == -1) + { + if (!manualEditionStop) + { + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(outlineViewDidEndEditing:)]) + { + [delegate outlineViewDidEndEditing:self]; + } + } + // We may have lost the focus + [[self window] makeFirstResponder:self]; + } +} + +- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)command +{ + if (command == @selector(cancelOperation:)) + { + [self stopEditing]; // The stop editing has to happen before the cancelEdits + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(outlineViewCancelsEdition:)]) + { + [delegate outlineViewCancelsEdition:self]; + } + return YES; + } + return NO; +} + +/* Public */ +- (NSIndexPath *)selectedPath +{ + NSInteger row = [self selectedRow]; + return [self itemAtRow:row]; +} + +- (void)selectPath:(NSIndexPath *)aPath +{ + [self selectNodePaths:[NSArray arrayWithObject:aPath]]; +} + +- (NSArray *)selectedNodePaths +{ + NSMutableArray *r = [NSMutableArray array]; + NSIndexSet *indexes = [self selectedRowIndexes]; + NSInteger i = [indexes firstIndex]; + while (i != NSNotFound) { + NSIndexPath *path = [self itemAtRow:i]; + [r addObject:path]; + i = [indexes indexGreaterThanIndex:i]; + } + return r; +} + +- (void)selectNodePaths:(NSArray *)aPaths +{ + NSMutableIndexSet *toSelect = [NSMutableIndexSet indexSet]; + /* To ensure that we have correct row indexes, we must first expand all paths, and *then* select + * row indexes. + **/ + for (NSIndexPath *path in aPaths) { + [self ensureExpanded:path]; + } + for (NSIndexPath *path in aPaths) { + [toSelect addIndex:[self rowForItem:path]]; + } + [self selectRowIndexes:toSelect byExtendingSelection:NO]; + if ([toSelect count] > 0) { + [self scrollRowToVisible:[toSelect firstIndex]]; + } +} + +- (void)ensureExpanded:(NSIndexPath *)aPath +{ + /* Expands aPath and make sure that its parent items are expanded as well. + */ + id delegate = [self delegate]; + NSIndexPath *tmppath = [NSIndexPath indexPathWithIndex:[aPath indexAtPosition:0]]; + [self expandItem:[delegate internalizedPath:tmppath]]; + for (NSInteger i=1; i<[aPath length]; i++) { + tmppath = [tmppath indexPathByAddingIndex:[aPath indexAtPosition:i]]; + [self expandItem:[delegate internalizedPath:tmppath]]; + } +} + +- (void)stopEditing +{ + // If we're not editing, don't do anything because we don't want to steal focus from another view + if ([self editedColumn] >= 0) + { + manualEditionStop = YES; + [[self window] makeFirstResponder:self]; // This will abort edition + manualEditionStop = NO; + } +} + +- (void)updateSelection +{ + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(selectedIndexPaths)]) { + [self selectNodePaths:[delegate selectedIndexPaths]]; + } +} + +/* Actions */ + +- (IBAction)copy:(id)sender +{ + NSString *data = [[self delegate] dataForCopyToPasteboard]; + if (data != nil) { + NSPasteboard *p = [NSPasteboard generalPasteboard]; + [p declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil]; + [p setString:data forType:NSStringPboardType]; + } +} + +/* BIG HACK ZONE +When tracking clicks in the NSTextField, the NSTableView goes in edition mode even if we click on the +arrow or the button. The only way I found to avoid this is this scheme: let the HSOutlineView know +of the event that caused the click, and don't go in edit mode if it happens. +*/ + +- (void)ignoreEventForEdition:(NSEvent *)aEvent +{ + eventToIgnore = aEvent; +} + +- (void)editColumn:(NSInteger)columnIndex row:(NSInteger)rowIndex withEvent:(NSEvent *)theEvent select:(BOOL)flag +{ + if ((theEvent != nil) && (theEvent == eventToIgnore)) + return; + [super editColumn:columnIndex row:rowIndex withEvent:theEvent select:flag]; +} + +@end diff --git a/cocoalib/views/HSTableView.h b/cocoalib/views/HSTableView.h new file mode 100644 index 0000000..ad229dc --- /dev/null +++ b/cocoalib/views/HSTableView.h @@ -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 +#import "NSTableViewAdditions.h" + +@class HSTableView; + +@protocol HSTableViewDelegate +- (NSIndexSet *)selectedIndexes; +- (void)tableViewDidEndEditing:(HSTableView *)tableView; +- (void)tableViewCancelsEdition:(HSTableView *)tableView; +- (void)tableViewWasDoubleClicked:(HSTableView *)tableView; +@end + +@interface HSTableView : NSTableView +{ + BOOL manualEditionStop; +} +- (void)updateSelection; +- (void)stopEditing; +- (id )delegate; +- (void)setDelegate:(id )aDelegate; +- (NSScrollView *)wrapInScrollView; +@end + diff --git a/cocoalib/views/HSTableView.m b/cocoalib/views/HSTableView.m new file mode 100644 index 0000000..ea045f8 --- /dev/null +++ b/cocoalib/views/HSTableView.m @@ -0,0 +1,102 @@ +/* +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 "HSTableView.h" +#import "NSEventAdditions.h" + +@implementation HSTableView +/* NSTableView */ +- (void)keyDown:(NSEvent *)event +{ + if (![self dispatchSpecialKeys:event]) { + [super keyDown:event]; + } +} + +- (id )delegate +{ + return (id )[super delegate]; +} + +- (void)setDelegate:(id )aDelegate +{ + [super setDelegate:aDelegate]; + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(tableViewWasDoubleClicked:)]) { + [self setTarget:[self delegate]]; + [self setDoubleAction:@selector(tableViewWasDoubleClicked:)]; + } +} + +- (void)textDidEndEditing:(NSNotification *)notification +{ + notification = [self processTextDidEndEditing:notification]; + NSView *nextKeyView = [self nextKeyView]; + [self setNextKeyView:nil]; + [super textDidEndEditing:notification]; + [self setNextKeyView:nextKeyView]; + + if ([self editedColumn] == -1) { + if (!manualEditionStop) { + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(tableViewDidEndEditing:)]) { + [delegate tableViewDidEndEditing:self]; + } + } + // We may have lost the focus + [[self window] makeFirstResponder:self]; + } +} + +/* NSTextView delegate */ +- (BOOL)textView:(NSTextView *)textView doCommandBySelector:(SEL)command +{ + if (command == @selector(cancelOperation:)) { + [self stopEditing]; // The stop editing has to happen before the cancelEdits + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(tableViewCancelsEdition:)]) { + [delegate tableViewCancelsEdition:self]; + } + return YES; + } + return NO; +} + +/* Public methods */ + +- (void)updateSelection +{ + NSIndexSet *selection = [[self delegate] selectedIndexes]; + [self selectRowIndexes:selection byExtendingSelection:NO]; +} + +// Calling this does not result in a tableViewDidEndEditing: call +- (void)stopEditing +{ + // If we're not editing, don't do anything because we don't want to steal focus from another view + if ([self editedColumn] >= 0) { + manualEditionStop = YES; + [[self window] makeFirstResponder:self]; // This will abort edition + manualEditionStop = NO; + } +} + +- (NSScrollView *)wrapInScrollView; +{ + /* When programmatically creating an NSTableView, we have to wrap it in a scroll view for it to + behave properly. + */ + NSScrollView *container = [[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]; + [container setDocumentView:self]; + [container setHasVerticalScroller:YES]; + [container setHasHorizontalScroller:YES]; + [container setAutohidesScrollers:YES]; + return [container autorelease]; +} + +@end diff --git a/cocoalib/views/NSIndexPathAdditions.h b/cocoalib/views/NSIndexPathAdditions.h new file mode 100644 index 0000000..376adf2 --- /dev/null +++ b/cocoalib/views/NSIndexPathAdditions.h @@ -0,0 +1,20 @@ +/* +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 + +/* This is a hack to easily get around a cocoa limitation + +In some weird circumstances, NSOutlineView calls [item indexPath] to the item instances given to +it. I guess is expects eveyone to give it NSTreeNode instances. Anyway, because in MG, simple +NSIndexPath are used, it causes problems. Not anymore. +*/ + +@interface NSIndexPath(NSIndexPathAdditions) +- (NSIndexPath *)indexPath; +@end \ No newline at end of file diff --git a/cocoalib/views/NSIndexPathAdditions.m b/cocoalib/views/NSIndexPathAdditions.m new file mode 100644 index 0000000..b8590a1 --- /dev/null +++ b/cocoalib/views/NSIndexPathAdditions.m @@ -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 "NSIndexPathAdditions.h" + +@implementation NSIndexPath(NSIndexPathAdditions) +- (NSIndexPath *)indexPath +{ + return self; +} +@end \ No newline at end of file diff --git a/cocoalib/views/NSTableViewAdditions.h b/cocoalib/views/NSTableViewAdditions.h new file mode 100644 index 0000000..308a041 --- /dev/null +++ b/cocoalib/views/NSTableViewAdditions.h @@ -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 +*/ + +#import + + +@interface NSTableView(NSTableViewAdditions) +- (BOOL)dispatchSpecialKeys:(NSEvent *)event; +- (NSNotification *)processTextDidEndEditing:(NSNotification *)notification; +- (void)startEditing; +@end + +@interface NSObject(NSTableViewAdditionsDelegate) +- (BOOL)tableViewHadDeletePressed:(NSTableView *)tableView; +- (BOOL)tableViewHadReturnPressed:(NSTableView *)tableView; +- (BOOL)tableViewHadSpacePressed:(NSTableView *)tableView; +- (BOOL)tableView:(NSTableView *)tableView receivedKeyEvent:(NSEvent *)aEvent; +- (BOOL)shouldEditTableColumn:(NSTableColumn *)column row:(NSInteger)row; +@end \ No newline at end of file diff --git a/cocoalib/views/NSTableViewAdditions.m b/cocoalib/views/NSTableViewAdditions.m new file mode 100644 index 0000000..0306d49 --- /dev/null +++ b/cocoalib/views/NSTableViewAdditions.m @@ -0,0 +1,118 @@ +/* +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 "NSTableViewAdditions.h" +#import "NSEventAdditions.h" +#import "Utils.h" + +@implementation NSTableView(NSTableViewAdditions) + +/* Private methods */ + +// Alright, this is a hack. It has been added to put in common some table and outline code, but the +// thing is an outline view delegate doesn't use tableView:shouldEditTableColumn:row:. Anyway, for +// the outline, just using [column isEditable] works in moneyGuru for now, so we can keep it that way. +- (BOOL)shouldEditTableColumn:(NSTableColumn *)column row:(NSInteger)row +{ + if (![column isEditable]) + return NO; + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(tableView:shouldEditTableColumn:row:)]) + { + return [delegate tableView:self shouldEditTableColumn:column row:row]; + } + else + { + return YES; + } +} + +/* Public Methods */ + +// Returns whether the responder chain should be stopeed or not +- (BOOL)dispatchSpecialKeys:(NSEvent *)event +{ + id delegate = [self delegate]; + if ([delegate respondsToSelector:@selector(tableView:receivedKeyEvent:)]) { + if ([delegate tableView:self receivedKeyEvent:event]) { + return YES; + } + } + BOOL stopChain = NO; + if ([event isDeleteOrBackspace] && [delegate respondsToSelector:@selector(tableViewHadDeletePressed:)]) + { + stopChain = [delegate tableViewHadDeletePressed:self]; + } + if ([event isReturnOrEnter] && [delegate respondsToSelector:@selector(tableViewHadReturnPressed:)]) + { + stopChain = [delegate tableViewHadReturnPressed:self]; + } + if ([event isSpace] && [delegate respondsToSelector:@selector(tableViewHadSpacePressed:)]) + { + stopChain = [delegate tableViewHadSpacePressed:self]; + } + if ([event isTab]) + { + stopChain = YES; + [[self window] makeFirstResponder:[self nextValidKeyView]]; + } + if ([event isBackTab]) + { + stopChain = YES; + // Ok, this is a big hack. the normal handling of NSTableView must handle this, but we must skip over + // a NSClipView and a NSScrollView before getting to the actual previousValidKeyView. + // However, when we are not in Full Keyboard Access mode, there's no problem. Thus, we assume that + // when previousValidKeyView's class is a NSClipView, it means we must perform the hack + NSView *previous = [self previousValidKeyView]; + if ([[previous className] isEqualTo:@"NSClipView"]) // Can't use isKindOfClass, we don't want to test for a subclass + previous = [[previous previousValidKeyView] previousValidKeyView]; + [[self window] makeFirstResponder:previous]; + } + return stopChain; +} + +- (NSNotification *)processTextDidEndEditing:(NSNotification *)notification; +{ + NSDictionary *userInfo = [notification userInfo]; + int textMovement = [[userInfo valueForKey:@"NSTextMovement"] intValue]; + if (textMovement == NSReturnTextMovement) + { + // Stop editing + NSMutableDictionary *newInfo; + newInfo = [NSMutableDictionary dictionaryWithDictionary:userInfo]; + [newInfo setObject:[NSNumber numberWithInt:NSIllegalTextMovement] forKey:@"NSTextMovement"]; + notification = [NSNotification notificationWithName:[notification name] object:[notification object] userInfo:newInfo]; + } + return notification; +} + +- (void)startEditing +{ + // Make sure one row is selected + if ([self selectedRow] == -1) + { + return; + } + + // We only want to edit columns that are editable. If there aren't any, don't edit. + for (NSInteger i=0;i<[[self tableColumns] count];i++) { + NSTableColumn *col = [[self tableColumns] objectAtIndex:i]; + if ([col isHidden]) { + continue; + } + if (![self shouldEditTableColumn:col row:[self selectedRow]]) { + continue; + } + // We only want one row to be selected. + NSIndexSet *selection = [NSIndexSet indexSetWithIndex:[self selectedRow]]; + [self selectRowIndexes:selection byExtendingSelection:NO]; + [self editColumn:i row:[self selectedRow] withEvent:nil select:YES]; + break; + } +} +@end