17 Commits

Author SHA1 Message Date
Alex Zielenski 2bae6bfa00 Merge pull request #37 from alexzielenski/dev
0.0.5
2014-07-26 02:27:25 -04:00
Alex Zielenski 3a6af2920d fix window not showing applied cape 2014-07-26 02:20:02 -04:00
Alex Zielenski cbb87fa0e0 add option for left-handed users to flip pointers 2014-07-26 02:19:50 -04:00
Alex Zielenski 52162699c3 update current version in read me 2014-07-22 14:33:47 -04:00
Alex Zielenski 746e1b02a5 sort cursors by name in main window to fix #34 2014-07-15 01:50:09 -04:00
Alex Zielenski 5b548b9507 change the way saving works to fix #35 2014-07-15 01:37:36 -04:00
Alex Zielenski a3fd1e3023 use constant 2014-07-15 01:18:53 -04:00
Alex Zielenski 3368d64376 make sure libraryWindowController is loaded before anything to fix #28 2014-07-15 01:18:45 -04:00
Alex Zielenski 1948bff0f5 use promises for dragging with alt key 2014-07-14 23:49:41 -04:00
Alex Zielenski e30572a7f1 allow dragging in multiple images to animate cursors to fix #36 2014-07-14 23:25:41 -04:00
Alex Zielenski 6ce1490fa9 have the first rep set determine image size to fix #31 2014-07-14 18:35:31 -04:00
Alex Zielenski 4aadd80fe8 make hotspot position more accurate to fix #33 2014-07-14 18:13:04 -04:00
Alex Zielenski aec4a42363 better identifier checking 2014-07-14 17:49:57 -04:00
Alex Zielenski a68912d9b2 remove logging 2014-07-14 17:43:21 -04:00
Alex Zielenski 2aa38d60cc add cursor dumping interface action to fix #32 2014-07-14 17:42:17 -04:00
Alex Zielenski e23797f70a allow dragging images out holding alt to fix #29 2014-07-14 16:40:11 -04:00
Alex Zielenski 7a9daedc0e Information about how to animate your cursors 2014-07-02 16:42:13 -04:00
35 changed files with 531 additions and 154 deletions
+21 -9
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6154.21" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6185.7" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6154.21"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6185.7"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MCEditWindowController">
@@ -146,10 +146,10 @@
</connections>
</button>
<button translatesAutoresizingMaskIntoConstraints="NO" id="w4h-lB-jLi">
<rect key="frame" x="22" y="0.0" width="22" height="22"/>
<rect key="frame" x="22" y="0.0" width="22" height="20"/>
<constraints>
<constraint firstAttribute="width" constant="22" id="L21-Ci-myo"/>
<constraint firstAttribute="height" constant="22" id="M80-94-gpJ"/>
<constraint firstAttribute="height" constant="20" id="M80-94-gpJ"/>
</constraints>
<buttonCell key="cell" type="smallSquare" bezelStyle="smallSquare" image="NSRemoveTemplate" imagePosition="only" alignment="center" imageScaling="proportionallyDown" inset="2" id="HjH-hq-TxT">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
@@ -181,7 +181,7 @@
<constraint firstItem="w4h-lB-jLi" firstAttribute="leading" secondItem="0ps-WH-ASN" secondAttribute="trailing" id="oqH-vB-hO8"/>
<constraint firstItem="0ps-WH-ASN" firstAttribute="bottom" secondItem="w4h-lB-jLi" secondAttribute="bottom" id="sWe-gf-GX0"/>
<constraint firstItem="2UQ-R4-qJn" firstAttribute="leading" secondItem="IHY-vi-Wpr" secondAttribute="leading" id="thB-tX-fKx"/>
<constraint firstItem="0ps-WH-ASN" firstAttribute="height" secondItem="w4h-lB-jLi" secondAttribute="height" id="uJj-nv-0fc"/>
<constraint firstItem="0ps-WH-ASN" firstAttribute="height" secondItem="w4h-lB-jLi" secondAttribute="height" constant="2" id="uJj-nv-0fc"/>
<constraint firstAttribute="bottom" secondItem="2UQ-R4-qJn" secondAttribute="bottom" constant="22" id="vLX-hs-yVP"/>
</constraints>
</customView>
@@ -210,7 +210,7 @@
<binding destination="oUs-Nv-PHw" name="title" keyPath="cursorLibrary.name" id="RKE-nQ-CzI"/>
<outlet property="delegate" destination="-2" id="4"/>
</connections>
<point key="canvasLocation" x="399.5" y="275.5"/>
<point key="canvasLocation" x="139.5" y="555.5"/>
</window>
<viewController title="List" id="oUs-Nv-PHw" customClass="MCEditListController">
<connections>
@@ -452,7 +452,11 @@
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="tMj-cQ-hK0" name="value" keyPath="cursor.frameCount" id="E0q-ga-ftR"/>
<binding destination="tMj-cQ-hK0" name="value" keyPath="cursor.frameCount" id="05V-RH-y2o">
<dictionary key="options">
<bool key="NSContinuouslyUpdatesValue" value="YES"/>
</dictionary>
</binding>
<outlet property="nextKeyView" destination="VbK-WJ-M0L" id="y5I-kM-hNq"/>
</connections>
</textField>
@@ -467,7 +471,11 @@
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="tMj-cQ-hK0" name="value" keyPath="cursor.frameDuration" id="I6z-BZ-xhp"/>
<binding destination="tMj-cQ-hK0" name="value" keyPath="cursor.frameDuration" id="pBC-y0-ehq">
<dictionary key="options">
<bool key="NSContinuouslyUpdatesValue" value="YES"/>
</dictionary>
</binding>
<outlet property="nextKeyView" destination="Qcr-83-Tz0" id="gYj-Hj-Txi"/>
</connections>
</textField>
@@ -480,7 +488,11 @@
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="tMj-cQ-hK0" name="value" keyPath="cursor.hotSpot" id="aFb-fI-aHw"/>
<binding destination="tMj-cQ-hK0" name="value" keyPath="cursor.hotSpot" id="7E7-XS-E4D">
<dictionary key="options">
<bool key="NSContinuouslyUpdatesValue" value="YES"/>
</dictionary>
</binding>
<outlet property="nextKeyView" destination="wdB-yr-r6Y" id="Smn-hK-iP2"/>
</connections>
</textField>
+36 -3
View File
@@ -1,18 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6154.21" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6185.11" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6154.21"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6185.11"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MCLibraryWindowController">
<connections>
<outlet property="appliedAccessory" destination="3Ui-b0-Yod" id="2Ik-Bo-djE"/>
<outlet property="libraryViewController" destination="zL4-Ay-t4Q" id="DE1-rb-MWQ"/>
<outlet property="progressBar" destination="F5l-qD-fvC" id="VNK-If-Tfi"/>
<outlet property="progressField" destination="Ui8-vk-7P8" id="Esy-lZ-9RN"/>
<outlet property="window" destination="1" id="3"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Mousecape" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" frameAutosaveName="LibraryWindow" animationBehavior="default" id="1">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<rect key="contentRect" x="196" y="240" width="711" height="311"/>
@@ -247,6 +249,37 @@ CA
<rect key="frame" x="0.0" y="0.0" width="100" height="100"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="default" id="DeD-dq-yBF" customClass="NSPanel">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" utility="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="272" y="172" width="452" height="58"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1418"/>
<view key="contentView" id="me1-hA-pTT">
<rect key="frame" x="0.0" y="0.0" width="452" height="58"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<progressIndicator maxValue="100" indeterminate="YES" style="bar" translatesAutoresizingMaskIntoConstraints="NO" id="F5l-qD-fvC">
<rect key="frame" x="15" y="19" width="343" height="20"/>
</progressIndicator>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ui8-vk-7P8">
<rect key="frame" x="364" y="21" width="75" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="000 of 129" id="sq0-HM-mp6">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="F5l-qD-fvC" firstAttribute="leading" secondItem="me1-hA-pTT" secondAttribute="leading" constant="15" id="1rr-uN-NwK"/>
<constraint firstItem="F5l-qD-fvC" firstAttribute="centerY" secondItem="Ui8-vk-7P8" secondAttribute="centerY" constant="0.5" id="FgD-90-fQk"/>
<constraint firstAttribute="trailing" secondItem="Ui8-vk-7P8" secondAttribute="trailing" constant="15" id="OgP-qZ-5ss"/>
<constraint firstItem="Ui8-vk-7P8" firstAttribute="leading" secondItem="F5l-qD-fvC" secondAttribute="trailing" constant="8" id="Q6O-6O-84O"/>
<constraint firstAttribute="centerY" secondItem="F5l-qD-fvC" secondAttribute="centerY" id="yZk-CB-OJj"/>
</constraints>
</view>
<point key="canvasLocation" x="499" y="-470"/>
</window>
</objects>
<resources>
<image name="HDTemplate" width="30" height="18"/>
+9 -3
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6154.21" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6185.11" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6154.21"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6185.11"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@@ -10,7 +10,7 @@
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<menu title="AMainMenu" systemMenu="main" id="29">
<items>
<menuItem title="Mousecape" id="56">
@@ -370,6 +370,12 @@ CA
<action selector="restoreCape:" target="494" id="2ve-Ez-ktd"/>
</connections>
</menuItem>
<menuItem title="Dump Cursors…" id="CYY-iE-0bP">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="dumpCapeAction:" target="-1" id="B89-Hc-T1C"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
+5 -1
View File
@@ -24,9 +24,13 @@
@implementation MCAppDelegate
@dynamic preferencesWindowController;
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
self.libraryWindowController = [[MCLibraryWindowController alloc] initWithWindowNibName:@"Library"];
[self.libraryWindowController loadWindow];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self configureHelperToolMenuItem];
self.libraryWindowController = [[MCLibraryWindowController alloc] initWithWindowNibName:@"Library"];
[self.libraryWindowController showWindow:self];
}
+2 -2
View File
@@ -36,11 +36,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.0.3</string>
<string>0.0.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1329</string>
<string>1551</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
@@ -64,8 +64,14 @@
}
- (void)imageView:(MMAnimatingImageView *)imageView didAcceptDroppedImages:(NSArray *)images {
CGFloat scale = imageView.scale;
[self.cursor setRepresentation:images.lastObject forScale:cursorScaleForScale(scale)];
MCCursorScale scale = cursorScaleForScale(imageView.scale);
if (NSEvent.modifierFlags == NSAlternateKeyMask) {
[self.cursor addFrame:[MCCursor composeRepresentationWithFrames:images] forScale:scale];
} else {
[self.cursor setRepresentation:[MCCursor composeRepresentationWithFrames:images] forScale:scale];
self.cursor.frameCount = images.count;
}
}
- (void)imageView:(MMAnimatingImageView *)imageView didDragOutImage:(NSImage *)image {
@@ -78,10 +78,10 @@ const char MCCursorNameContext;
NSUInteger index = [self.cursors indexForInsertingObject:lib sortedUsingComparator:self.class.sortComparator];
NSIndexSet *indices = [NSIndexSet indexSetWithIndex:index];
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"capes"];
[self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"cursors"];
[self.cursors insertObject:lib atIndex:index];
[self startObservingCursor:lib];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"capes"];
[self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indices forKey:@"cursors"];
[self.tableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:index + 1] withAnimation:NSTableViewAnimationSlideUp];
}
} else if (kind == NSKeyValueChangeRemoval) {
@@ -90,10 +90,10 @@ const char MCCursorNameContext;
if (index != NSNotFound) {
NSIndexSet *indices = [NSIndexSet indexSetWithIndex:index];
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indices forKey:@"capes"];
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indices forKey:@"cursors"];
[self stopObservingCursor:lib];
[self.cursors removeObjectAtIndex:index];
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indices forKey:@"capes"];
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indices forKey:@"cursors"];
[self.tableView removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:index + 1] withAnimation:NSTableViewAnimationSlideUp | NSTableViewAnimationEffectFade];
}
}
@@ -28,6 +28,7 @@
- (NSURL *)URLForCape:(MCCursorLibrary *)cape;
- (NSSet *)capesWithIdentifier:(NSString *)identifier;
- (BOOL)dumpCursorsWithProgressBlock:(BOOL (^)(NSUInteger current, NSUInteger total))block;
@end
@@ -10,8 +10,7 @@
#import "NSOrderedSet+AZSortedInsert.h"
#import "apply.h"
#import "restore.h"
const char MCLibraryIdentifierContext;
#import "create.h"
@interface MCLibraryController ()
@property (nonatomic, readwrite, strong) NSUndoManager *undoManager;
@@ -19,18 +18,21 @@ const char MCLibraryIdentifierContext;
@property (readwrite, copy) NSURL *libraryURL;
@property (readwrite, weak) MCCursorLibrary *appliedCape;
- (void)loadLibrary;
- (void)willSaveNotification:(NSNotification *)note;
@end
@implementation MCLibraryController
- (NSURL *)URLForCape:(MCCursorLibrary *)cape {
return [NSURL fileURLWithPathComponents:@[ self.libraryURL.path, [cape.identifier stringByAppendingPathExtension:@"cape"] ]];
return [NSURL fileURLWithPathComponents:@[ self.libraryURL.path, [cape.identifier stringByAppendingPathExtension:@"cape"] ]];;
}
- (instancetype)initWithURL:(NSURL *)url {
if ((self = [self init])) {
self.libraryURL = url;
self.undoManager = [[NSUndoManager alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(willSaveNotification:) name:MCLibraryWillSaveNotificationName object:nil];
[self loadLibrary];
}
@@ -64,17 +66,14 @@ const char MCLibraryIdentifierContext;
}
- (void)importCapeAtURL:(NSURL *)url {
MCCursorLibrary *lib = [MCCursorLibrary cursorLibraryWithContentsOfURL:url];
NSURL *destinationURL = [self URLForCape:lib];
NSError *error = nil;
[[NSFileManager defaultManager] copyItemAtURL:lib.fileURL toURL:destinationURL error:&error];
if (!error) {
lib.fileURL = destinationURL;
[self addCape:lib];
}
[self importCape:[MCCursorLibrary cursorLibraryWithContentsOfURL:url]];
}
- (void)importCape:(MCCursorLibrary *)lib {
if ([[self.capes valueForKeyPath:@"identifier"] containsObject:lib.identifier]) {
lib.identifier = [lib.identifier stringByAppendingFormat:@".%@", UUID()];
}
lib.fileURL = [self URLForCape:lib];
[lib writeToFile:lib.fileURL.path atomically:NO];
@@ -83,7 +82,7 @@ const char MCLibraryIdentifierContext;
- (void)addCape:(MCCursorLibrary *)cape {
if ([self.capes containsObject:cape]) {
if ([self.capes containsObject:cape] || [[self.capes valueForKeyPath:@"identifier"] containsObject:cape.identifier]) {
NSLog(@"Not adding %@ to the library because an object with that identifier already exists", cape.identifier);
return;
}
@@ -95,18 +94,18 @@ const char MCLibraryIdentifierContext;
NSSet *change = [NSSet setWithObject:cape];
[self willChangeValueForKey:@"capes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:change];
[cape addObserver:self forKeyPath:@"identifier" options:NSKeyValueObservingOptionOld context:(void *)&MCLibraryIdentifierContext];
cape.library = self;
[self.capes addObject:cape];
[[self.undoManager prepareWithInvocationTarget:self] removeCape:cape];
if (!self.undoManager.isUndoing) {
[self.undoManager setActionName:[@"Add " stringByAppendingString:cape.name]];
}
[self didChangeValueForKey:@"capes" withSetMutation:NSKeyValueUnionSetMutation usingObjects:change];
[cape.undoManager removeAllActions];
}
@@ -116,9 +115,7 @@ const char MCLibraryIdentifierContext;
[self willChangeValueForKey:@"capes" withSetMutation:NSKeyValueMinusSetMutation usingObjects:change];
if (cape == self.appliedCape)
[self restoreCape];
[cape removeObserver:self forKeyPath:@"identifier" context:(void *)&MCLibraryIdentifierContext];
if (cape.library == self)
cape.library = nil;
@@ -155,21 +152,30 @@ const char MCLibraryIdentifierContext;
return [self.capes filteredSetUsingPredicate:pred];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == &MCLibraryIdentifierContext) {
// change the file url to reflect the new identifier
MCCursorLibrary *cape = object;
NSURL *oldURL = cape.fileURL;
[cape setFileURL:[self URLForCape:cape]];
NSError *error = nil;
[[NSFileManager defaultManager] moveItemAtURL:oldURL toURL:cape.fileURL error:&error];
if (error) {
NSLog(@"Failed to rename the identifier of the cape %@. Reverting to %@...", cape.identifier, change[NSKeyValueChangeOldKey]);
cape.identifier = change[NSKeyValueChangeOldKey];
cape.fileURL = [self URLForCape:cape];
}
- (void)willSaveNotification:(NSNotification *)note {
MCCursorLibrary *cape = note.object;
NSURL *oldURL = cape.fileURL;
[cape setFileURL:[self URLForCape:cape]];
NSError *error = nil;
[[NSFileManager defaultManager] removeItemAtURL:oldURL error:&error];
if (error) {
NSLog(@"error removing cape after rename: %@", error);
}
}
- (BOOL)dumpCursorsWithProgressBlock:(BOOL (^)(NSUInteger current, NSUInteger total))block {
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat: @"Mousecape Dump (%f).cape", NSDate.date.timeIntervalSince1970]];
if (dumpCursorsToFile(path, block)) {
__weak MCLibraryController *weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf importCapeAtURL:[NSURL fileURLWithPath:path]];
});
return YES;
}
return NO;
}
@end
@@ -122,6 +122,11 @@ const char MCLibraryNameContext;
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indices forKey:@"capes"];
[self.tableView removeRowsAtIndexes:indices withAnimation:NSTableViewAnimationSlideUp | NSTableViewAnimationEffectFade];
if (self.editWindowController.cursorLibrary == lib) {
self.editWindowController.cursorLibrary = nil;
[self.editWindowController close];
}
}
}
@@ -10,8 +10,10 @@
#import "MCLibraryViewController.h"
@interface MCLibraryWindowController : NSWindowController <NSWindowDelegate>
@property (assign) IBOutlet MCLibraryViewController *libraryViewController;
@property (assign) IBOutlet NSView *appliedAccessory;
@property (weak) IBOutlet MCLibraryViewController *libraryViewController;
@property (weak) IBOutlet NSView *appliedAccessory;
@property (weak) IBOutlet NSProgressIndicator *progressBar;
@property (weak) IBOutlet NSTextField *progressField;
@end
@interface MCAppliedCapeValueTransformer : NSValueTransformer
@@ -14,6 +14,10 @@
@implementation MCLibraryWindowController
- (void)awakeFromNib {
[self composeAccessory];
}
- (id)initWithWindow:(NSWindow *)window {
if ((self = [super initWithWindow:window])) {
@@ -22,9 +26,9 @@
}
- (void)windowDidLoad {
NSLog(@"window load");
[super windowDidLoad];
[self composeAccessory];
}
- (NSString *)windowNibName {
@@ -124,6 +128,30 @@
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[ cape.fileURL ]];
}
- (IBAction)dumpCapeAction:(NSMenuItem *)sender {
[self.window beginSheet:self.progressBar.window completionHandler:nil];
__weak MCLibraryWindowController *weakSelf = self;
self.progressBar.doubleValue = 0.0;
[self.progressBar setIndeterminate:NO];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[weakSelf.libraryViewController.libraryController dumpCursorsWithProgressBlock:^BOOL (NSUInteger current, NSUInteger total) {
dispatch_sync(dispatch_get_main_queue(), ^{
weakSelf.progressField.stringValue = [NSString stringWithFormat:@"%lu of %lu", (unsigned long)current, (unsigned long)total];
weakSelf.progressBar.minValue = 0;
weakSelf.progressBar.maxValue = total;
weakSelf.progressBar.doubleValue = current;
});
return YES;
}];
dispatch_sync(dispatch_get_main_queue(), ^{
[weakSelf.window endSheet:self.progressBar.window];
[[NSCursor arrowCursor] set];
});
});
}
@end
@implementation MCAppliedCapeValueTransformer
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6154.17" systemVersion="14A261i" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6185.11" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment version="1070" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6154.17"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6185.11"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="MCGeneralPreferencesController">
@@ -11,20 +11,48 @@
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="1">
<rect key="frame" x="0.0" y="0.0" width="480" height="118"/>
<rect key="frame" x="0.0" y="0.0" width="480" height="143"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Ndv-TL-Pur">
<rect key="frame" x="18" y="106" width="206" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="I am... handed." id="a3X-1e-eOG">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="8pe-9P-fa1">
<rect key="frame" x="68" y="101" width="94" height="25"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedSquare" trackingMode="selectOne" id="Hdu-Yg-F0d">
<font key="font" metaFont="system"/>
<segments>
<segment label="right" selected="YES"/>
<segment label="left" tag="1"/>
</segments>
</segmentedCell>
<connections>
<binding destination="Dw0-Gt-5ak" name="selectedIndex" keyPath="values.MCHandedness" id="ZqA-zg-dh2">
<dictionary key="options">
<integer key="NSMultipleValuesPlaceholder" value="0"/>
<integer key="NSNoSelectionPlaceholder" value="0"/>
<integer key="NSNotApplicablePlaceholder" value="0"/>
<integer key="NSNullPlaceholder" value="0"/>
</dictionary>
</binding>
</connections>
</segmentedControl>
<slider verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="JRx-YT-ae7">
<rect key="frame" x="18" y="19" width="392" height="27"/>
<rect key="frame" x="18" y="15" width="392" height="27"/>
<sliderCell key="cell" continuous="YES" state="on" alignment="left" minValue="1" maxValue="16" doubleValue="1" tickMarkPosition="below" numberOfTickMarks="16" sliderType="linear" id="osm-63-Q66"/>
<connections>
<binding destination="-2" name="value" keyPath="cursorScale" id="PlF-RW-V9z"/>
</connections>
</slider>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="hg4-8F-fVE">
<rect key="frame" x="20" y="52" width="82" height="17"/>
<rect key="frame" x="20" y="48" width="82" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Cursor Scale" id="gxo-nh-UCg">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -32,7 +60,7 @@
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="yjb-oM-v3t">
<rect key="frame" x="18" y="81" width="265" height="17"/>
<rect key="frame" x="18" y="77" width="265" height="17"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Double Clicks capes" id="mFs-6l-Guo">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -40,7 +68,7 @@
</textFieldCell>
</textField>
<segmentedControl verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZYi-xB-wBd">
<rect key="frame" x="119" y="75" width="111" height="25"/>
<rect key="frame" x="119" y="71" width="111" height="25"/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedSquare" trackingMode="selectOne" id="oXj-Hj-PSJ">
<font key="font" metaFont="system"/>
<segments>
@@ -60,7 +88,7 @@
</connections>
</segmentedControl>
<textField verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="QBy-dj-wZ4">
<rect key="frame" x="416" y="25" width="44" height="19"/>
<rect key="frame" x="416" y="21" width="44" height="19"/>
<textFieldCell key="cell" controlSize="small" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="nlB-rR-gpk">
<numberFormatter key="formatter" formatterBehavior="custom10_4" positiveFormat="#0.00" negativeFormat="#0.00" usesGroupingSeparator="NO" paddingCharacter="*" groupingSize="0" minimumIntegerDigits="1" maximumIntegerDigits="2" minimumFractionDigits="2" maximumFractionDigits="2" decimalSeparator="." groupingSeparator="," currencyDecimalSeparator="." plusSign="+" minusSign="-" notANumberSymbol="NaN" perMillSymbol="‰" percentSymbol="%" exponentSymbol="E" positivePrefix="" positiveSuffix="" negativePrefix="-" negativeSuffix="" id="ZjS-1g-txf">
<real key="minimum" value="0.5"/>
@@ -75,6 +103,7 @@
</connections>
</textField>
</subviews>
<point key="canvasLocation" x="311" y="408.5"/>
</customView>
<userDefaultsController representsSharedInstance="YES" id="Dw0-Gt-5ak"/>
</objects>
@@ -33,11 +33,13 @@ extern MCCursorScale cursorScaleForScale(CGFloat scale);
- (void)setRepresentation:(NSImageRep *)imageRep forScale:(MCCursorScale)scale;
- (void)removeRepresentationForScale:(MCCursorScale)scale;
- (void)addFrame:(NSImageRep *)frame forScale:(MCCursorScale)scale;
- (NSImageRep *)representationForScale:(MCCursorScale)scale;
- (NSImageRep *)representationWithScale:(CGFloat)scale;
- (NSDictionary *)dictionaryRepresentation;
+ (NSImageRep *)composeRepresentationWithFrames:(NSArray *)frames;
// Derived Properties
- (NSImage *)imageWithAllReps;
+73
View File
@@ -17,6 +17,7 @@ MCCursorScale cursorScaleForScale(CGFloat scale) {
@interface MCCursor ()
@property (readwrite, strong) NSMutableDictionary *representations;
- (NSInteger)framesForScale:(MCCursorScale)scale;
- (BOOL)_readFromDictionary:(NSDictionary *)dictionary ofVersion:(CGFloat)version;
@end
@@ -185,10 +186,82 @@ MCCursorScale cursorScaleForScale(CGFloat scale) {
[self.representations setObject:imageRep forKey:@(scale)];
else
[self.representations removeObjectForKey:@(scale)];
if (self.representations.count == 1) {
// This is the first object, set the image size to this
NSSize size = NSMakeSize((double)imageRep.pixelsWide / (scale / 100.0), (double)imageRep.pixelsHigh / self.frameCount / (scale / 100.0));
if (!NSEqualSizes(size, NSZeroSize)) {
self.size = size;
}
}
[self didChangeValueForKey:key];
[self didChangeValueForKey:@"representations"];
}
- (void)addFrame:(NSImageRep *)frame forScale:(MCCursorScale)scale {
NSImageRep *rep = [self representationForScale:scale];
NSImageRep *newRep = [self.class composeRepresentationWithFrames:@[ rep, frame ]];
NSInteger frames = newRep.pixelsHigh / self.size.height;
if (self.frameCount < frames) {
self.frameCount = frames;
}
[self setRepresentation:newRep forScale:scale];
}
+ (NSImageRep *)composeRepresentationWithFrames:(NSArray *)frames {
if (frames.count == 0)
return nil;
if (frames.count == 1)
return frames.firstObject;
NSUInteger height = [[frames valueForKeyPath:@"@sum.pixelsHigh"] unsignedIntegerValue];
NSUInteger width = [(NSImageRep *)frames[0] pixelsWide];
NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:width
pixelsHigh:height
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:4 * width
bitsPerPixel:32];
NSGraphicsContext *ctx = [NSGraphicsContext graphicsContextWithBitmapImageRep:newRep];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:ctx];
NSUInteger currentY = 0;
for (NSInteger idx = frames.count - 1; idx >= 0; idx--) {
NSImageRep *rep = frames[idx];
if (rep.pixelsWide != width) {
NSLog(@"Can't create representation from images of different widths");
return nil;
}
[rep drawInRect:NSMakeRect(0, currentY, rep.pixelsWide, rep.pixelsHigh)
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0
respectFlipped:YES
hints:nil];
currentY += rep.pixelsHigh;
}
[NSGraphicsContext restoreGraphicsState];
return newRep;
}
- (NSInteger)framesForScale:(MCCursorScale)scale {
return [self representationForScale:scale].pixelsHigh / self.size.height;
}
- (void)removeRepresentationForScale:(MCCursorScale)scale {
[self setRepresentation:nil forScale:scale];
}
@@ -9,6 +9,9 @@
#import <Foundation/Foundation.h>
#import "MCCursor.h"
extern NSString *const MCLibraryWillSaveNotificationName;
extern NSString *const MCLibraryDidSaveNotificationName;
@class MCLibraryController;
@interface MCCursorLibrary : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@@ -8,12 +8,16 @@
#import "MCCursorLibrary.h"
NSString *const MCLibraryWillSaveNotificationName = @"MCLibraryWillSave";
NSString *const MCLibraryDidSaveNotificationName = @"MCLibraryDidSave";
@interface MCCursorLibrary ()
@property (nonatomic, strong) NSUndoManager *undoManager;
@property (nonatomic, readwrite, strong) NSMutableSet *cursors;
@property (nonatomic, assign) NSUInteger changeCount;
@property (nonatomic, assign) NSUInteger lastChangeCount;
@property (nonatomic, strong) NSArray *observers;
@property (nonatomic, copy) NSString *oldIdentifier;
- (BOOL)_readFromDictionary:(NSDictionary *)dictionary;
- (void)addCursorsFromDictionary:(NSDictionary *)cursorDicts ofVersion:(CGFloat)doubleVersion;
@@ -244,6 +248,10 @@ const char MCCursorPropertiesContext;
if (!self.undoManager.isUndoing) {
[self.undoManager setActionName:[[@"Change " stringByAppendingString:decamelized] capitalizedString]];
}
if ([keyPath isEqualToString:@"identifier"]) {
self.oldIdentifier = oldValue;
}
}
}
@@ -348,11 +356,13 @@ const char MCCursorPropertiesContext;
NSLocalizedDescriptionKey: NSLocalizedString(@"Save failed", nil),
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedString(@"Multiple cursors with the name(s): %@ exist.", nil), duplicates] }];
}
// [self.undoManager removeAllActions];
[[NSNotificationCenter defaultCenter] postNotificationName:MCLibraryWillSaveNotificationName object:self];
BOOL success = [self writeToFile:self.fileURL.path atomically:NO];
if (success) {
[self updateChangeCount:NSChangeCleared];
[[NSNotificationCenter defaultCenter] postNotificationName:MCLibraryDidSaveNotificationName object:self];
return nil;
}
return [NSError errorWithDomain:MCErrorDomain code:MCErrorWriteFailCode userInfo:@{
+20 -2
View File
@@ -9,12 +9,18 @@
#import "MCCapeCellView.h"
#import "MCCapePreviewItem.h"
@interface MCCapeCellView ()
@end
@interface MCSortValueTransformer : NSValueTransformer
@end
@implementation MCCapeCellView
- (void)viewDidMoveToWindow {
self.collectionView.itemPrototype = [MCCapePreviewItem new];
[self.collectionView bind:NSContentBinding toObject:self withKeyPath:@"objectValue.cursors" options:nil];
[self.collectionView bind:NSContentBinding toObject:self withKeyPath:@"objectValue.cursors" options:@{ NSValueTransformerBindingOption: [MCSortValueTransformer new] }];
self.collectionView.minItemSize = self.collectionView.itemPrototype.view.frame.size;
self.collectionView.maxItemSize = self.collectionView.minItemSize;
}
@@ -25,6 +31,18 @@
@end
@implementation MCSortValueTransformer
+ (Class)transformedValueClass {
return [NSSet class];
}
- (NSArray *)transformedValue:(NSSet *)value {
return [value sortedArrayUsingDescriptors: @[ [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)] ]];
}
@end
@implementation MCHDValueTransformer
+ (Class)transformedValueClass {
@@ -26,13 +26,17 @@
[self.animatingImageView bind:@"image" toObject:self withKeyPath:@"representedObject.imageWithAllReps" options:nil];
[self.animatingImageView bind:@"frameCount" toObject:self withKeyPath:@"representedObject.frameCount" options:nil];
[self.animatingImageView bind:@"frameDuration" toObject:self withKeyPath:@"representedObject.frameDuration" options:nil];
[self.animatingImageView bind:@"shouldFlipHorizontally"
toObject:[NSUserDefaults standardUserDefaults]
withKeyPath:MCPreferencesHandednessKey
options:nil];
}
return self;
}
- (void)dealloc {
[self.animatingImageView unbind:@"shouldFlipHorizontally"];
[self.animatingImageView unbind:@"image"];
[self.animatingImageView unbind:@"frameCount"];
[self.animatingImageView unbind:@"frameDuration"];
@@ -33,6 +33,7 @@
@property (assign) NSInteger frameCount;
@property (assign) CGFloat scale; // set to 0.0 if you want to inherit window scale
@property (assign) NSPoint hotSpot;
@property (assign) BOOL shouldFlipHorizontally;
@property (weak) IBOutlet id <MMAnimatingImageViewDelegate> delegate;
@property (assign) BOOL shouldAnimate;
@property (assign) BOOL shouldShowHotSpot;
@@ -9,6 +9,8 @@
#import "MMAnimatingImageView.h"
#import "MCSpriteLayer.h"
#define SHOULDCOPY NSEvent.modifierFlags & NSAlternateKeyMask
const char MCInvalidateContext;
@interface MMAnimatingImageView ()
@@ -17,6 +19,7 @@ const char MCInvalidateContext;
- (void)_initialize;
- (void)_invalidateFrame;
- (void)_invalidateAnimation;
- (void)_resetTransform;
- (void)registerTypes;
- (void)_dragAnimationEnded:(id)sender;
@end
@@ -59,10 +62,10 @@ const char MCInvalidateContext;
self.layer.delegate = self;
CALayer *hotSpotLayer = [CALayer layer];
hotSpotLayer.bounds = CGRectMake(0, 0, 4, 4);
hotSpotLayer.bounds = CGRectMake(0, 0, 3, 3);
hotSpotLayer.backgroundColor = [[NSColor redColor] CGColor];
hotSpotLayer.autoresizingMask = kCALayerNotSizable;
hotSpotLayer.anchorPoint = CGPointMake(0, 0);
hotSpotLayer.anchorPoint = CGPointMake(0.5, 0.5);
hotSpotLayer.borderColor = [[NSColor blackColor] CGColor];
hotSpotLayer.borderWidth = 0.5;
[self.layer addSublayer:hotSpotLayer];
@@ -82,6 +85,7 @@ const char MCInvalidateContext;
[self addObserver:self forKeyPath:@"frameCount" options:0 context:(void *)&MCInvalidateContext];
[self addObserver:self forKeyPath:@"frameDuration" options:0 context:(void *)&MCInvalidateContext];
[self addObserver:self forKeyPath:@"shouldAnimate" options:0 context:NULL];
[self addObserver:self forKeyPath:@"shouldFlipHorizontally" options:0 context:NULL];
}
- (void)dealloc {
@@ -102,6 +106,8 @@ const char MCInvalidateContext;
[self _invalidateAnimation];
} else if ([keyPath isEqualToString:@"shouldAnimate"]) {
[self _invalidateAnimation];
} else if ([keyPath isEqualToString:@"shouldFlipHorizontally"]) {
[self _resetTransform];
}
}
@@ -132,6 +138,14 @@ const char MCInvalidateContext;
#pragma mark - Invalidators
- (void)_resetTransform {
if (self.shouldFlipHorizontally) {
self.layer.transform = CATransform3DMakeAffineTransform(CGAffineTransformMake(-1, 0, 0, 1, self.layer.bounds.size.width, 0));
} else {
self.layer.transform = CATransform3DIdentity;
}
}
- (void)_invalidateFrame {
CGFloat scale = self.scale;
if (!self.scale || !self.image) {
@@ -155,11 +169,13 @@ const char MCInvalidateContext;
CGSize effectiveSize = CGSizeMake(self.image.size.width, self.image.size.height / self.frameCount);
CGRect effectiveRect = CGRectIntegral(CGRectMake(self.layer.frame.size.width / 2.0 - effectiveSize.width / 2.0, self.layer.frame.size.height / 2.0 + effectiveSize.height / 2.0, effectiveSize.width, effectiveSize.height));
self.hotSpotLayer.position = CGPointMake(ceil(CGRectGetMinX(effectiveRect) + self.hotSpot.x - self.hotSpotLayer.frame.size.width / 2), ceil(CGRectGetMinY(effectiveRect) - self.hotSpot.y - self.hotSpotLayer.frame.size.height / 2));
self.hotSpotLayer.position = CGPointMake(CGRectGetMinX(effectiveRect) + self.hotSpot.x, CGRectGetMinY(effectiveRect) - self.hotSpot.y);
self.hotSpotLayer.opacity = 1.0;
} else {
self.hotSpotLayer.opacity = 0.0;
}
[self _resetTransform];
}
- (void)_invalidateAnimation {
@@ -187,34 +203,45 @@ const char MCInvalidateContext;
#pragma mark - NSDraggingSource
- (void)draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint {
}
- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context {
if (context == NSDraggingContextWithinApplication && self.shouldAllowDragging)
return NSDragOperationCopy;
if (self.shouldAllowDragging)
return NSDragOperationEvery;
return NSDragOperationNone;
}
- (void)draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation {
if (self.delegate && [self.delegate respondsToSelector:@selector(imageView:didDragOutImage:)] && operation == NSDragOperationNone && !NSPointInRect(screenPoint, self.window.frame)) {
[[NSCursor currentCursor] pop];
NSShowAnimationEffect(NSAnimationEffectPoof, screenPoint, NSZeroSize, self, @selector(_dragAnimationEnded:), nil);
[self.delegate imageView:self didDragOutImage:self.image];
if (!NSPointInRect(screenPoint, self.window.frame)) {
if (SHOULDCOPY) {
[self _dragAnimationEnded:self];
} else if (self.delegate && [self.delegate respondsToSelector:@selector(imageView:didDragOutImage:)]) {
NSShowAnimationEffect(NSAnimationEffectPoof, screenPoint, NSZeroSize, self, @selector(_dragAnimationEnded:), nil);
[self.delegate imageView:self didDragOutImage:self.image];
}
}
}
- (void)_dragAnimationEnded:(id)sender {
[[NSCursor arrowCursor] push];
[[NSCursor arrowCursor] set];
}
- (void)draggingSession:(NSDraggingSession *)session movedToPoint:(NSPoint)screenPoint {
if (!NSPointInRect(screenPoint, self.window.frame)) {
[[NSCursor disappearingItemCursor] push];
if (SHOULDCOPY)
[[NSCursor dragCopyCursor] set];
else
[[NSCursor disappearingItemCursor] set];
} else if ([NSCursor currentCursor] == [NSCursor disappearingItemCursor]) {
[[NSCursor currentCursor] pop];
[self _dragAnimationEnded:self];
}
}
- (BOOL)ignoreModifierKeysForDraggingSession:(NSDraggingSession *)session {
return YES;
return NO;
}
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
@@ -226,7 +253,7 @@ const char MCInvalidateContext;
return;
NSPasteboardItem *pbItem = [NSPasteboardItem new];
[pbItem setDataProvider:self forTypes:@[ NSPasteboardTypePNG, NSPasteboardTypeTIFF, @"public.image" ]];
[pbItem setDataProvider:self forTypes:@[ NSPasteboardTypePNG, NSPasteboardTypeTIFF, @"public.image", (__bridge NSString *)kPasteboardTypeFileURLPromise ]];
NSDraggingItem *dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter:pbItem];
@@ -249,12 +276,15 @@ const char MCInvalidateContext;
- (void)pasteboard:(NSPasteboard *)sender item:(NSPasteboardItem *)item provideDataForType:(NSString *)type {
if ([type compare: NSPasteboardTypeTIFF] == NSOrderedSame) {
[sender setData:[self.image TIFFRepresentation] forType:NSPasteboardTypeTIFF];
} else if ([type compare: NSPasteboardTypePNG] == NSOrderedSame) {
[sender setData:[self.image.representations.lastObject representationUsingType:NSPNGFileType properties:nil] forType:NSPasteboardTypePNG];
} else if ([type compare:@"public.image"] == NSOrderedSame) {
[sender writeObjects:@[ self.image ]];
} else if ([type compare:(__bridge NSString *)kPasteboardTypeFileURLPromise] == NSOrderedSame && SHOULDCOPY) {
NSURL *url = [[NSURL URLWithString:[item stringForType:@"com.apple.pastelocation"]] URLByAppendingPathComponent:[NSString stringWithFormat:@"Mousecape Image (%f).png", NSDate.date.timeIntervalSince1970]];
[[self.image.representations.firstObject representationUsingType:NSPNGFileType properties:nil] writeToFile:url.path atomically:NO];
}
}
#pragma mark - NSDragDestination
@@ -266,10 +296,10 @@ const char MCInvalidateContext;
// Only thing we have to do here is confirm that the dragged file is an image. We use NSImage's +canInitWithPasteboard: and we also check to see there is only one item being dragged
if ([self.delegate conformsToProtocol:@protocol(MMAnimatingImageViewDelegate)] && // No point in accepting the drop if the delegate doesn't support it/exist
[NSImage canInitWithPasteboard:sender.draggingPasteboard] && // Only Accept Images
sender.draggingPasteboard.pasteboardItems.count == 1 &&
self.shouldAllowDragging) { // Only accept one item
return [self.delegate imageView:self draggingEntered:sender];
self.shouldAllowDragging) {
return [self.delegate imageView:self draggingEntered:sender];
}
return NSDragOperationNone;
}
@@ -284,27 +314,28 @@ const char MCInvalidateContext;
- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender {
if ([self.delegate conformsToProtocol:@protocol(MMAnimatingImageViewDelegate)] && // Only do the operation if a delegate exists to actually set the image.
[self.delegate imageView:self shouldPerformDragOperation:sender]) { // Only do the operation if a delegate wants us to do the operation.
// Get the image from the pasteboard
NSImage *im = [[NSImage alloc] initWithPasteboard:sender.draggingPasteboard];
// Make an array of the valid drops (NSBitmapImageRep)
NSMutableArray *acceptedDrops = [NSMutableArray arrayWithCapacity:im.representations.count];
for (NSImageRep *rep in im.representations) {
if (![rep isKindOfClass:[NSBitmapImageRep class]]) // We don't want PDFs
continue;
[acceptedDrops addObject:rep];
}
NSArray *imageArray = [sender.draggingPasteboard readObjectsForClasses:@[[NSImage class], [NSURL class]] options:nil];
NSMutableArray *acceptedDrops = [NSMutableArray arrayWithCapacity:imageArray.count];
for (NSInteger idx = 0; idx < imageArray.count; idx++) {
id obj = imageArray[idx];
if ([obj isKindOfClass:[NSImage class]]) {
[acceptedDrops addObject:[[obj representations] firstObject]];
} else {
// NSURL
[acceptedDrops addObject:[NSImageRep imageRepWithContentsOfURL:obj]];
}
}
if (acceptedDrops.count > 0) {
// We already confirmed that the delegate conforms to the protocol above. Now we can let the delegate
// decide what to do with the dropped images.
[self.delegate imageView:self didAcceptDroppedImages:acceptedDrops];
return YES;
}
return YES;
}
return NO;
+2 -2
View File
@@ -71,5 +71,5 @@ extern NSData *pngDataForImage(id image);
extern NSString *MMGet();
extern CGError MCIsCursorRegistered(CGSConnectionID cid, char *cursorName, bool *registered);
#endif
extern BOOL MCCursorIsPointer(NSString *identifier);
#endif
+13 -2
View File
@@ -130,7 +130,7 @@ NSDictionary *capeWithIdentifier(NSString *identifier) {
return dict;
}
NSDictionary *cursorMap() {
extern NSDictionary *cursorMap() {
static NSDictionary *cursorNameMap = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
@@ -202,7 +202,7 @@ NSString *cursorIdentifierForName(NSString *name) {
return UUID();
}
extern CGError MCIsCursorRegistered(CGSConnectionID cid, char *cursorName, bool *registered) {
CGError MCIsCursorRegistered(CGSConnectionID cid, char *cursorName, bool *registered) {
if (CGSIsCursorRegistered != NULL) {
return CGSIsCursorRegistered(cid, cursorName, registered);
}
@@ -215,3 +215,14 @@ extern CGError MCIsCursorRegistered(CGSConnectionID cid, char *cursorName, bool
return err;
}
BOOL MCCursorIsPointer(NSString *identifier) {
static NSArray *pointers = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary *c = cursorMap();
pointers = [@[ [c allKeysForObject:@"Alias"][0], [c allKeysForObject:@"Arrow"][0], [c allKeysForObject:@"Busy"][0], [c allKeysForObject:@"Closed"][0], [c allKeysForObject:@"Copy Drag"][0], [c allKeysForObject:@"Counting Down"][0], [c allKeysForObject:@"Counting Up"][0], [c allKeysForObject:@"Counting Up/Down"][0], [c allKeysForObject:@"Ctx Menu"][0], [c allKeysForObject:@"Forbidden"][0], [c allKeysForObject:@"Link"][0], [c allKeysForObject:@"Move"][0], [c allKeysForObject:@"Open"][0], [c allKeysForObject:@"Pointing"][0], [c allKeysForObject:@"Poof"][0], [c allKeysForObject:@"Wait"][0], [c allKeysForObject:@"Zoom In"][0], [c allKeysForObject:@"Zoom Out"] ] retain];
});
return [pointers containsObject:identifier];
}
+5 -4
View File
@@ -15,13 +15,14 @@ extern NSString *MCPreferencesAppliedCursorKey;
extern NSString *MCPreferencesAppliedClickActionKey;
extern NSString *MCPreferencesCursorScaleKey;
extern NSString *MCPreferencesDoubleActionKey;
extern NSString *MCPreferencesHandednessKey;
extern NSString *MCSuppressDeleteLibraryConfirmationKey;
extern NSString *MCSuppressDeleteCursorConfirmationKey;
extern id MCDefaultFor(NSString *key, NSString *user, NSString *host);
#define MCDefault(key) MCDefaultFor(key, (__bridge NSString *)kCFPreferencesCurrentUser, (__bridge NSString *)kCFPreferencesCurrentHost)
extern id MCDefaultFor(NSString *key);
#define MCDefault(key) MCDefaultFor(key)
#define MCFlag(key) [MCDefault(key) boolValue]
extern void MCSetDefaultFor(id value, NSString *key, NSString *user, NSString *host);
#define MCSetDefault(value, key) MCSetDefaultFor(value, key, (__bridge NSString *)kCFPreferencesCurrentUser, (__bridge NSString *)kCFPreferencesCurrentHost)
extern void MCSetDefaultFor(id value, NSString *key);
#define MCSetDefault(value, key) MCSetDefaultFor(value, key)
#define MCSetFlag(value, key) MCSetDefault(@(value), key)
#endif
+5 -4
View File
@@ -12,15 +12,16 @@ NSString *MCPreferencesAppliedCursorKey = @"MCAppliedCursor";
NSString *MCPreferencesAppliedClickActionKey = @"MCLibraryClickAction";
NSString *MCPreferencesCursorScaleKey = @"MCCursorScale";
NSString *MCPreferencesDoubleActionKey = @"MCDoubleAction";
NSString *MCPreferencesHandednessKey = @"MCHandedness";
NSString *MCSuppressDeleteLibraryConfirmationKey = @"MCSuppressDeleteLibraryConfirmationKey";
NSString *MCSuppressDeleteCursorConfirmationKey = @"MCSuppressDeleteCursorConfirmationKey";
id MCDefaultFor(NSString *key, NSString *user, NSString *host) {
NSString *value = (NSString *)CFPreferencesCopyValue((CFStringRef)key, (CFStringRef)kMCDomain, (CFStringRef)user, (CFStringRef)host);
id MCDefaultFor(NSString *key) {
NSString *value = (NSString *)CFPreferencesCopyAppValue((CFStringRef)key, (CFStringRef)kMCDomain);
return [value autorelease];
}
void MCSetDefaultFor(id value, NSString *key, NSString *user, NSString *host) {
CFPreferencesSetValue((CFStringRef)key, (CFPropertyListRef)value, (CFStringRef)kMCDomain, (CFStringRef)user, (CFStringRef)host);
void MCSetDefaultFor(id value, NSString *key) {
CFPreferencesSetAppValue((CFStringRef)key, (CFPropertyListRef)value, (CFStringRef)kMCDomain);
// CFPreferencesSynchronize((CFStringRef)kMCDomain, (CFStringRef)user, (CFStringRef)host);
}
+1 -1
View File
@@ -10,7 +10,7 @@
#define Mousecape_apply_h
extern BOOL applyCursorForIdentifier(NSUInteger frameCount, CGFloat frameDuration, CGPoint hotSpot, CGSize size, NSArray *images, NSString *ident, NSUInteger repeatCount);
extern BOOL applyCapeForIdentifier(NSDictionary *cursor, NSString *identifier);
extern BOOL applyCapeForIdentifier(NSDictionary *cursor, NSString *identifier, BOOL restore);
extern BOOL applyCape(NSDictionary *dictionary);
extern BOOL applyCapeAtPath(NSString *path);
+64 -21
View File
@@ -35,10 +35,14 @@ BOOL applyCursorForIdentifier(NSUInteger frameCount, CGFloat frameDuration, CGPo
return (err == kCGErrorSuccess);
}
BOOL applyCapeForIdentifier(NSDictionary *cursor, NSString *identifier) {
if (!cursor)
BOOL applyCapeForIdentifier(NSDictionary *cursor, NSString *identifier, BOOL restore) {
if (!cursor || !identifier) {
NSLog(@"bad seed");
return NO;
}
BOOL lefty = MCFlag(MCPreferencesHandednessKey);
BOOL pointer = MCCursorIsPointer(identifier);
NSNumber *frameCount = cursor[MCCursorDictionaryFrameCountKey];
NSNumber *frameDuration = cursor[MCCursorDictionaryFrameDuratiomKey];
// NSNumber *repeatCount = cursor[MCCursorDictionaryRepeatCountKey];
@@ -48,27 +52,66 @@ BOOL applyCapeForIdentifier(NSDictionary *cursor, NSString *identifier) {
CGSize size = CGSizeMake([cursor[MCCursorDictionaryPointsWideKey] doubleValue],
[cursor[MCCursorDictionaryPointsHighKey] doubleValue]);
NSArray *reps = cursor[MCCursorDictionaryRepresentationsKey];
NSMutableArray *images = [NSMutableArray array];
if (lefty && !restore && pointer) {
MMLog("Lefty mode for %s", identifier.UTF8String);
hotSpot.x = size.width - hotSpot.x - 1;
}
for (id object in reps) {
CFTypeID type = CFGetTypeID((__bridge CFTypeRef)object);
// special case if array has a type of CGImage already there is no need to convert it
if (type == CGImageGetTypeID()) {
images[images.count] = object;
continue;
if (!lefty || restore || !pointer) {
// special case if array has a type of CGImage already there is no need to convert it
if (type == CGImageGetTypeID()) {
images[images.count] = object;
continue;
}
CFDataRef pngData = (__bridge CFDataRef)object;
CGDataProviderRef pngProvider = CGDataProviderCreateWithCFData(pngData);
CGImageRef rep = CGImageCreateWithPNGDataProvider(pngProvider, NULL, false, kCGRenderingIntentDefault);
CGDataProviderRelease(pngProvider);
images[images.count] = (__bridge id)rep;
CGImageRelease(rep);
} else {
NSBitmapImageRep *rep;
if (type == CGImageGetTypeID()) {
rep = [[NSBitmapImageRep alloc] initWithCGImage:(__bridge CGImageRef)object];
} else {
rep = [[NSBitmapImageRep alloc] initWithData:object];
}
NSBitmapImageRep *newRep = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL
pixelsWide:rep.pixelsWide
pixelsHigh:rep.pixelsHigh
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSDeviceRGBColorSpace
bytesPerRow:4 * rep.pixelsWide
bitsPerPixel:32];
NSGraphicsContext *ctx = [NSGraphicsContext graphicsContextWithBitmapImageRep:newRep];
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:ctx];
NSAffineTransform *transform = [NSAffineTransform transform];
[transform translateXBy:rep.pixelsWide yBy:0];
[transform scaleXBy:-1 yBy:1];
[transform concat];
[rep drawInRect:NSMakeRect(0, 0, rep.pixelsWide, rep.pixelsHigh)
fromRect:NSZeroRect
operation:NSCompositeSourceOver
fraction:1.0
respectFlipped:NO
hints:nil];
[NSGraphicsContext restoreGraphicsState];
images[images.count] = (__bridge id)[newRep CGImage];
}
CFDataRef pngData = (__bridge CFDataRef)object;
CGDataProviderRef pngProvider = CGDataProviderCreateWithCFData(pngData);
CGImageRef rep = CGImageCreateWithPNGDataProvider(pngProvider, NULL, false, kCGRenderingIntentDefault);
CGDataProviderRelease(pngProvider);
images[images.count] = (__bridge id)rep;
CGImageRelease(rep);
}
return applyCursorForIdentifier(frameCount.unsignedIntegerValue, frameDuration.doubleValue, hotSpot, size, images, identifier, 0);
@@ -89,7 +132,7 @@ BOOL applyCape(NSDictionary *dictionary) {
NSDictionary *cape = cursors[key];
MMLog("Hooking for %s", key.UTF8String);
BOOL success = applyCapeForIdentifier(cape, key);
BOOL success = applyCapeForIdentifier(cape, key, NO);
if (!success) {
MMLog(BOLD RED "Failed to hook identifier %s for some unknown reason. Bailing out..." RESET, key.UTF8String);
return NO;
+1 -1
View File
@@ -29,7 +29,7 @@ void backupCursorForIdentifier(NSString *ident) {
return;
NSDictionary *cape = capeWithIdentifier(ident);
(void)applyCapeForIdentifier(cape, backupIdent);
(void)applyCapeForIdentifier(cape, backupIdent, YES);
}
+2 -2
View File
@@ -11,8 +11,8 @@
extern NSError *createCape(NSString *input, NSString *output, BOOL convert);
extern NSDictionary *processedCapeWithIdentifier(NSString *identifier);
extern void dumpCursorsToFile(NSString *path);
extern void dumpCursorsToFolder(NSString *path);
extern BOOL dumpCursorsToFile(NSString *path, BOOL (^progress)(NSUInteger current, NSUInteger total));
extern BOOL dumpCursorsToFolder(NSString *path, BOOL (^progress)(NSUInteger current, NSUInteger total));
extern NSDictionary *createCapeFromDirectory(NSString *path);
extern NSDictionary *createCapeFromMightyMouse(NSDictionary *mightyMouse, NSDictionary *metadata);
+49 -10
View File
@@ -253,7 +253,7 @@ NSDictionary *processedCapeWithIdentifier(NSString *identifier) {
return dict;
}
void dumpCursorsToFile(NSString *path) {
BOOL dumpCursorsToFile(NSString *path, BOOL (^progress)(NSUInteger current, NSUInteger total)) {
MMLog("Dumping cursors...");
float originalScale;
@@ -261,17 +261,34 @@ void dumpCursorsToFile(NSString *path) {
CGSSetCursorScale(CGSMainConnectionID(), 16.0);
CGSHideCursor(CGSMainConnectionID());
NSInteger total = 9 + 45;
NSInteger current = 0;
NSMutableDictionary *cursors = [NSMutableDictionary dictionary];
NSUInteger i = 0;
NSString *key = nil;
while ((key = defaultCursors[i]) != nil) {
if (progress) {
current = i;
if (!progress(current, total)) {
return NO;
}
}
MMLog("Gathering data for %s", key.UTF8String);
cursors[key] = processedCapeWithIdentifier(key);
i++;
}
for (int x = 0x0; x < 0x100; x++) {
for (int x = 0; x < 45; x++) {
if (progress) {
current = i + x;
if (!progress(current, total)) {
return NO;
}
}
NSString *key = [@"com.apple.cursor." stringByAppendingFormat:@"%d", x];
CoreCursorSet(CGSMainConnectionID(), x);
@@ -283,7 +300,11 @@ void dumpCursorsToFile(NSString *path) {
cursors[key] = cape;
}
if (progress) {
progress(total, total);
}
NSMutableDictionary *cape = [NSMutableDictionary dictionary];
cape[MCCursorDictionaryAuthorKey] = @"Apple, Inc.";
cape[MCCursorDictionaryCapeNameKey] = @"Cursor Dump";
@@ -298,10 +319,10 @@ void dumpCursorsToFile(NSString *path) {
CGSSetCursorScale(CGSMainConnectionID(), originalScale);
CGSShowCursor(CGSMainConnectionID());
[cape writeToFile:path atomically:NO];
return [cape writeToFile:path atomically:NO];
}
extern void dumpCursorsToFolder(NSString *path) {
BOOL dumpCursorsToFolder(NSString *path, BOOL (^progress)(NSUInteger current, NSUInteger total)) {
[[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
MMLog("Dumping cursors...");
@@ -311,11 +332,19 @@ extern void dumpCursorsToFolder(NSString *path) {
CGSSetCursorScale(CGSMainConnectionID(), 16.0);
CGSHideCursor(CGSMainConnectionID());
NSInteger total = 9 + 45;
NSInteger current = 0;
NSUInteger i = 0;
NSString *key = nil;
while ((key = defaultCursors[i]) != nil) {
current = i;
if (progress) {
if (!progress(current, total)) {
return NO;
}
}
MMLog("Gathering data for %s", key.UTF8String);
NSDictionary *cape = processedCapeWithIdentifier(key);
@@ -323,7 +352,13 @@ extern void dumpCursorsToFolder(NSString *path) {
i++;
}
for (int x = 0x0; x < 0x100; x++) {
for (int x = 0; x < 45; x++) {
current = i + x;
if (progress) {
if (!progress(current, total)) {
return NO;
}
}
NSString *key = [@"com.apple.cursor." stringByAppendingFormat:@"%d", x];
CoreCursorSet(CGSMainConnectionID(), x);
@@ -336,10 +371,14 @@ extern void dumpCursorsToFolder(NSString *path) {
[[cape[MCCursorDictionaryRepresentationsKey] lastObject] writeToFile:[[path stringByAppendingPathComponent:key] stringByAppendingPathExtension:@"png"] atomically:NO];
}
if (progress) {
progress(total, total);
}
CGSSetCursorScale(CGSMainConnectionID(), originalScale);
CGSShowCursor(CGSMainConnectionID());
return YES;
}
extern void exportCape(NSDictionary *cape, NSString *destination) {
+2 -1
View File
@@ -16,7 +16,8 @@
NSString *appliedCapePathForUser(NSString *user) {
NSString *home = NSHomeDirectoryForUser(user);
NSString *ident = MCDefaultFor(@"MCAppliedCursor", user, (NSString *)kCFPreferencesCurrentHost);
#warning help
NSString *ident = MCDefaultFor(@"MCAppliedCursor");
NSString *appSupport = [home stringByAppendingPathComponent:@"Library/Application Support"];
return [[[appSupport stringByAppendingPathComponent:@"Mousecape/capes"] stringByAppendingPathComponent:ident] stringByAppendingPathExtension:@"cape"];
}
+4 -1
View File
@@ -161,7 +161,10 @@ int main(int argc, char * argv[]) {
goto fin;
} else if (dump) {
dumpCursorsToFile([settings objectForKey:@"dump"]);
dumpCursorsToFile([settings objectForKey:@"dump"], ^BOOL (NSUInteger progress, NSUInteger total) {
MMLog("Dumped %lu of %lu", (unsigned long)progress, (unsigned long)total);
return YES;
});
} else if (scale) {
NSNumber *number = [settings objectForKey:@"scale"];
+1 -1
View File
@@ -23,7 +23,7 @@ void restoreCursorForIdentifier(NSString *ident) {
MMLog("Restoring cursor %s from %s", restoreIdent.UTF8String, ident.UTF8String);
if (cape && registered) {
applyCapeForIdentifier(cape, restoreIdent);
applyCapeForIdentifier(cape, restoreIdent, YES);
}
CGSRemoveRegisteredCursor(CGSMainConnectionID(), (char *)ident.UTF8String, false);
+5 -1
View File
@@ -19,13 +19,17 @@ There is an example cape file included in this Git Repo located [here for downlo
You can create a new cape document in the Mousecape app by hitting &#8984;N (Command-N) and editing it with &#8984;E. Click the "+" button to add cursors to customize and symply drag your images into the fields provided.
## How do animated cursors work?
When you want to animate a cursor, change the value in the frames field in the edit window and make sure frame duration is how you want it. Next, create an image that has all of your cursor frames stacked on top of each other vertically. Mousecape will traverse down the image for each frame, using a box the same size as whatever you put in the size field.
## How can I say thanks?
Tell your friends.
## Where can I get a copy of this sweet tool?
In the [releases section](https://github.com/alexzielenski/Mousecape/releases) of this GitHub page. There are stable reases there. **The current version is 0.0.3**.
In the [releases section](https://github.com/alexzielenski/Mousecape/releases) of this GitHub page. There are stable reases there. **The current version is 0.0.4**.
## LICENSE