Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bae6bfa00 | |||
| 3a6af2920d | |||
| cbb87fa0e0 | |||
| 52162699c3 | |||
| 746e1b02a5 | |||
| 5b548b9507 | |||
| a3fd1e3023 | |||
| 3368d64376 | |||
| 1948bff0f5 | |||
| e30572a7f1 | |||
| 6ce1490fa9 | |||
| 4aadd80fe8 | |||
| aec4a42363 | |||
| a68912d9b2 | |||
| 2aa38d60cc | |||
| e23797f70a | |||
| 7a9daedc0e |
BIN
Binary file not shown.
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:@{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -29,7 +29,7 @@ void backupCursorForIdentifier(NSString *ident) {
|
||||
return;
|
||||
|
||||
NSDictionary *cape = capeWithIdentifier(ident);
|
||||
(void)applyCapeForIdentifier(cape, backupIdent);
|
||||
(void)applyCapeForIdentifier(cape, backupIdent, YES);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"];
|
||||
}
|
||||
|
||||
@@ -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"];
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ⌘N (Command-N) and editing it with ⌘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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user