From b235835336f3a16913cd58c82f4dbf9f5bded9d0 Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Sat, 10 Oct 2015 10:22:25 -0500 Subject: [PATCH 01/10] OS X 10.10 Yosemite got a bit too crafty and would apply it's own "disabled" styling if the NSStatusItem image is an image that has "disabled" in its name. Changed to xout to keep it looking colorful. --- AppController.m | 2 +- Flycut.xcodeproj/project.pbxproj | 16 ++++++++-------- ...png => com.generalarcade.flycut.xout.16.png} | Bin ...png => com.generalarcade.flycut.xout.32.png} | Bin 4 files changed, 9 insertions(+), 9 deletions(-) rename Resources/{com.generalarcade.flycut.disabled.16.png => com.generalarcade.flycut.xout.16.png} (100%) rename Resources/{com.generalarcade.flycut.disabled.32.png => com.generalarcade.flycut.xout.32.png} (100%) diff --git a/AppController.m b/AppController.m index a9d0481..296c880 100755 --- a/AppController.m +++ b/AppController.m @@ -247,7 +247,7 @@ statusItemText = [statusItem title]; statusItemImage = [statusItem image]; [statusItem setTitle: @""]; - [statusItem setImage: [NSImage imageNamed:@"com.generalarcade.flycut.disabled.16.png"]]; + [statusItem setImage: [NSImage imageNamed:@"com.generalarcade.flycut.xout.16.png"]]; return true; } else diff --git a/Flycut.xcodeproj/project.pbxproj b/Flycut.xcodeproj/project.pbxproj index a868214..b8f828f 100755 --- a/Flycut.xcodeproj/project.pbxproj +++ b/Flycut.xcodeproj/project.pbxproj @@ -61,8 +61,8 @@ 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; 8D2E28861B0669F500AE62C8 /* com.generalarcade.flycut.black.16.png in Resources */ = {isa = PBXBuildFile; fileRef = 8D2E28821B0669F500AE62C8 /* com.generalarcade.flycut.black.16.png */; }; 8D2E28871B0669F500AE62C8 /* com.generalarcade.flycut.black.32.png in Resources */ = {isa = PBXBuildFile; fileRef = 8D2E28831B0669F500AE62C8 /* com.generalarcade.flycut.black.32.png */; }; - 8D2E28881B0669F500AE62C8 /* com.generalarcade.flycut.disabled.16.png in Resources */ = {isa = PBXBuildFile; fileRef = 8D2E28841B0669F500AE62C8 /* com.generalarcade.flycut.disabled.16.png */; }; - 8D2E28891B0669F500AE62C8 /* com.generalarcade.flycut.disabled.32.png in Resources */ = {isa = PBXBuildFile; fileRef = 8D2E28851B0669F500AE62C8 /* com.generalarcade.flycut.disabled.32.png */; }; + 8D2E28881B0669F500AE62C8 /* com.generalarcade.flycut.xout.16.png in Resources */ = {isa = PBXBuildFile; fileRef = 8D2E28841B0669F500AE62C8 /* com.generalarcade.flycut.xout.16.png */; }; + 8D2E28891B0669F500AE62C8 /* com.generalarcade.flycut.xout.32.png in Resources */ = {isa = PBXBuildFile; fileRef = 8D2E28851B0669F500AE62C8 /* com.generalarcade.flycut.xout.32.png */; }; AABE497C09FF9CD000A6A239 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = AABE497B09FF9CD000A6A239 /* AppController.m */; }; AAFAC85F0A1BD9DD00DC6025 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AAFAC84A0A1BD9DD00DC6025 /* Carbon.framework */; }; DB46BEEB143466ED0025EA0E /* DBUserDefaults.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB46BEEA143466ED0025EA0E /* DBUserDefaults.framework */; }; @@ -169,8 +169,8 @@ 8D1107320486CEB800E47090 /* Flycut.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Flycut.app; sourceTree = BUILT_PRODUCTS_DIR; }; 8D2E28821B0669F500AE62C8 /* com.generalarcade.flycut.black.16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = com.generalarcade.flycut.black.16.png; path = Resources/com.generalarcade.flycut.black.16.png; sourceTree = ""; }; 8D2E28831B0669F500AE62C8 /* com.generalarcade.flycut.black.32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = com.generalarcade.flycut.black.32.png; path = Resources/com.generalarcade.flycut.black.32.png; sourceTree = ""; }; - 8D2E28841B0669F500AE62C8 /* com.generalarcade.flycut.disabled.16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = com.generalarcade.flycut.disabled.16.png; path = Resources/com.generalarcade.flycut.disabled.16.png; sourceTree = ""; }; - 8D2E28851B0669F500AE62C8 /* com.generalarcade.flycut.disabled.32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = com.generalarcade.flycut.disabled.32.png; path = Resources/com.generalarcade.flycut.disabled.32.png; sourceTree = ""; }; + 8D2E28841B0669F500AE62C8 /* com.generalarcade.flycut.xout.16.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = com.generalarcade.flycut.xout.16.png; path = Resources/com.generalarcade.flycut.xout.16.png; sourceTree = ""; }; + 8D2E28851B0669F500AE62C8 /* com.generalarcade.flycut.xout.32.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = com.generalarcade.flycut.xout.32.png; path = Resources/com.generalarcade.flycut.xout.32.png; sourceTree = ""; }; AABE497A09FF9CD000A6A239 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = ""; }; AABE497B09FF9CD000A6A239 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = ""; }; AAFAC84A0A1BD9DD00DC6025 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = ""; }; @@ -272,8 +272,8 @@ 7761C8CE139BE06B000FB3AB /* com.generalarcade.flycut.32.png */, 8D2E28821B0669F500AE62C8 /* com.generalarcade.flycut.black.16.png */, 8D2E28831B0669F500AE62C8 /* com.generalarcade.flycut.black.32.png */, - 8D2E28841B0669F500AE62C8 /* com.generalarcade.flycut.disabled.16.png */, - 8D2E28851B0669F500AE62C8 /* com.generalarcade.flycut.disabled.32.png */, + 8D2E28841B0669F500AE62C8 /* com.generalarcade.flycut.xout.16.png */, + 8D2E28851B0669F500AE62C8 /* com.generalarcade.flycut.xout.32.png */, 7761C8D1139BE06B000FB3AB /* jumpcut.icns */, 7761C8EB139BE09D000FB3AB /* Info.plist */, 7761C8D2139BE06B000FB3AB /* net.sf.jumpcut.ghost_scissors_small.png */, @@ -492,13 +492,13 @@ 7761C8DB139BE06B000FB3AB /* com.generalarcade.flycut.16.png in Resources */, 7761C8DC139BE06B000FB3AB /* com.generalarcade.flycut.32.png in Resources */, 7761C8DF139BE06B000FB3AB /* jumpcut.icns in Resources */, - 8D2E28891B0669F500AE62C8 /* com.generalarcade.flycut.disabled.32.png in Resources */, + 8D2E28891B0669F500AE62C8 /* com.generalarcade.flycut.xout.32.png in Resources */, 7761C8E0139BE06B000FB3AB /* net.sf.jumpcut.ghost_scissors_small.png in Resources */, 7761C8E1139BE06B000FB3AB /* net.sf.jumpcut.preferences.acknowledgements.tiff in Resources */, 8D2E28871B0669F500AE62C8 /* com.generalarcade.flycut.black.32.png in Resources */, 7761C8E2139BE06B000FB3AB /* net.sf.jumpcut.preferences.appearance.tiff in Resources */, 7761C8E3139BE06B000FB3AB /* net.sf.jumpcut.preferences.general.tiff in Resources */, - 8D2E28881B0669F500AE62C8 /* com.generalarcade.flycut.disabled.16.png in Resources */, + 8D2E28881B0669F500AE62C8 /* com.generalarcade.flycut.xout.16.png in Resources */, 7761C8E4139BE06B000FB3AB /* net.sf.jumpcut.preferences.hotkey.tiff in Resources */, 7761C8E5139BE06B000FB3AB /* net.sf.jumpcut.scissors_bw16.png in Resources */, 8D2E28861B0669F500AE62C8 /* com.generalarcade.flycut.black.16.png in Resources */, diff --git a/Resources/com.generalarcade.flycut.disabled.16.png b/Resources/com.generalarcade.flycut.xout.16.png similarity index 100% rename from Resources/com.generalarcade.flycut.disabled.16.png rename to Resources/com.generalarcade.flycut.xout.16.png diff --git a/Resources/com.generalarcade.flycut.disabled.32.png b/Resources/com.generalarcade.flycut.xout.32.png similarity index 100% rename from Resources/com.generalarcade.flycut.disabled.32.png rename to Resources/com.generalarcade.flycut.xout.32.png From a2d0e2a1a603ac0fb537d6af481faf8306f86965 Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Fri, 22 Jan 2016 23:24:44 -0600 Subject: [PATCH 02/10] Bezel animation has demonstrated problems on 10.10 Retina and any 10.11 setup. Until the problems are solved, default to not animate on installs where it would be problematic. --- AppController.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AppController.m b/AppController.m index 296c880..cef85c5 100755 --- a/AppController.m +++ b/AppController.m @@ -71,7 +71,7 @@ @"saveForgottenClippings", [NSNumber numberWithBool:YES], @"saveForgottenFavorites", - [NSNumber numberWithBool:YES], + [NSNumber numberWithBool:(floor(NSAppKitVersionNumber) <= NSAppKitVersionNumber10_9)], @"popUpAnimation", [NSNumber numberWithBool:NO], @"pasteMovesToTop", From 0b204c32c32892619d9b0fa51499b4b0f34ad80b Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Fri, 15 Jan 2016 22:59:43 -0600 Subject: [PATCH 03/10] Switch JumpcutClipping retain/release code to all use the same technique, that being to not release until after being assigned out of need. --- JumpcutEngine/JumpcutClipping.m | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/JumpcutEngine/JumpcutClipping.m b/JumpcutEngine/JumpcutClipping.m index 1f8b6c9..7f9d44e 100755 --- a/JumpcutEngine/JumpcutClipping.m +++ b/JumpcutEngine/JumpcutClipping.m @@ -93,17 +93,19 @@ -(void) setContents:(NSString *)newContents { + id old = clipContents; [newContents retain]; - [clipContents release]; clipContents = newContents; + [old release]; [self resetDisplayString]; } -(void) setType:(NSString *)newType { + id old = clipType; [newType retain]; - [clipType release]; clipType = newType; + [old release]; } -(void) setDisplayLength:(int)newDisplayLength From d1cd8508c2eec84e4212840188a63aec334feb87 Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Fri, 15 Jan 2016 23:12:56 -0600 Subject: [PATCH 04/10] Add the ability to retrieve and add entire JumpcutClipping objects from JumpcutStore rather than having to extract each member via repeated lookups. Also consolidate many repeated copies of the bezel content update code into a single method. --- AppController.m | 45 ++++++++++++++++++--------------- JumpcutEngine/JumpcutClipping.h | 1 + JumpcutEngine/JumpcutClipping.m | 5 ++++ JumpcutEngine/JumpcutStore.h | 3 +++ JumpcutEngine/JumpcutStore.m | 23 +++++++++++++---- 5 files changed, 52 insertions(+), 25 deletions(-) diff --git a/AppController.m b/AppController.m index cef85c5..1650b7a 100755 --- a/AppController.m +++ b/AppController.m @@ -481,8 +481,7 @@ [self restoreStashedStore]; } // Get text from clipping store. - [favoritesStore addClipping:[clippingStore clippingContentsAtPosition:stackPosition] - ofType:[clippingStore clippingTypeAtPosition:stackPosition] ]; + [favoritesStore addClipping:[clippingStore clippingAtPosition:stackPosition] ]; [clippingStore clearItem:stackPosition]; [self updateBezel]; [self updateMenu]; @@ -796,8 +795,7 @@ newStackPosition = pressed == 0x30 ? 9 : [[NSString stringWithCharacters:&pressed length:1] intValue] - 1; if ( [clippingStore jcListCount] >= newStackPosition ) { stackPosition = newStackPosition; - [bezel setCharString:[NSString stringWithFormat:@"%d of %d", stackPosition + 1, [clippingStore jcListCount]]]; - [bezel setText:[clippingStore clippingContentsAtPosition:stackPosition]]; + [self fillBezel]; } break; case 's': case 'S': // Save / Save-and-delete @@ -846,16 +844,14 @@ [bezel setCharString:@"Empty"]; } else { // normal - [bezel setText:[clippingStore clippingContentsAtPosition:stackPosition]]; - [bezel setCharString:[NSString stringWithFormat:@"%d of %d", stackPosition + 1, [clippingStore jcListCount]]]; + [self fillBezel]; } } - (void) showBezel { if ( [clippingStore jcListCount] > 0 && [clippingStore jcListCount] > stackPosition ) { - [bezel setCharString:[NSString stringWithFormat:@"%d of %d", stackPosition + 1, [clippingStore jcListCount]]]; - [bezel setText:[clippingStore clippingContentsAtPosition:stackPosition]]; + [self fillBezel]; } NSRect mainScreenRect = [NSScreen mainScreen].visibleFrame; [bezel setFrame:NSMakeRect(mainScreenRect.origin.x + mainScreenRect.size.width/2 - bezel.frame.size.width/2, @@ -1092,34 +1088,37 @@ { stackPosition++; if ( [clippingStore jcListCount] > stackPosition ) { - [bezel setCharString:[NSString stringWithFormat:@"%d of %d", stackPosition + 1, [clippingStore jcListCount]]]; - [bezel setText:[clippingStore clippingContentsAtPosition:stackPosition]]; + [self fillBezel]; } else { if ( [[DBUserDefaults standardUserDefaults] boolForKey:@"wraparoundBezel"] ) { stackPosition = 0; - [bezel setCharString:[NSString stringWithFormat:@"%d of %d", 1, [clippingStore jcListCount]]]; - [bezel setText:[clippingStore clippingContentsAtPosition:stackPosition]]; + [self fillBezel]; } else { stackPosition--; } } } +-(void) fillBezel +{ + JumpcutClipping* clipping = [clippingStore clippingAtPosition:stackPosition]; + [bezel setText:[clipping source]]; + [bezel setCharString:[NSString stringWithFormat:@"%d of %d", stackPosition + 1, [clippingStore jcListCount]]]; +} + -(void) stackUp { stackPosition--; if ( stackPosition < 0 ) { if ( [[DBUserDefaults standardUserDefaults] boolForKey:@"wraparoundBezel"] ) { stackPosition = [clippingStore jcListCount] - 1; - [bezel setCharString:[NSString stringWithFormat:@"%d of %d", stackPosition + 1, [clippingStore jcListCount]]]; - [bezel setText:[clippingStore clippingContentsAtPosition:stackPosition]]; + [self fillBezel]; } else { stackPosition = 0; } } if ( [clippingStore jcListCount] > stackPosition ) { - [bezel setCharString:[NSString stringWithFormat:@"%d of %d", stackPosition + 1, [clippingStore jcListCount]]]; - [bezel setText:[clippingStore clippingContentsAtPosition:stackPosition]]; + [self fillBezel]; } } @@ -1137,17 +1136,23 @@ [saveDict setObject:[NSNumber numberWithInt:[[DBUserDefaults standardUserDefaults] integerForKey:@"displayNum"]] forKey:@"displayNum"]; for (int i = 0 ; i < [clippingStore jcListCount]; i++) + { + JumpcutClipping* clipping = [clippingStore clippingAtPosition:i]; [jcListArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [clippingStore clippingContentsAtPosition:i], @"Contents", - [clippingStore clippingTypeAtPosition:i], @"Type", + [clipping contents], @"Contents", + [clipping type], @"Type", [NSNumber numberWithInt:i], @"Position",nil]]; + } [saveDict setObject:jcListArray forKey:@"jcList"]; jcListArray = [NSMutableArray array]; for (int i = 0 ; i < [favoritesStore jcListCount]; i++) + { + JumpcutClipping* clipping = [favoritesStore clippingAtPosition:i]; [jcListArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [favoritesStore clippingContentsAtPosition:i], @"Contents", - [favoritesStore clippingTypeAtPosition:i], @"Type", + [clipping contents], @"Contents", + [clipping type], @"Type", [NSNumber numberWithInt:i], @"Position",nil]]; + } [saveDict setObject:jcListArray forKey:@"favoritesList"]; [[DBUserDefaults standardUserDefaults] setObject:saveDict forKey:@"store"]; [[DBUserDefaults standardUserDefaults] synchronize]; diff --git a/JumpcutEngine/JumpcutClipping.h b/JumpcutEngine/JumpcutClipping.h index 5223094..fe2a4ef 100755 --- a/JumpcutEngine/JumpcutClipping.h +++ b/JumpcutEngine/JumpcutClipping.h @@ -53,6 +53,7 @@ -(void) setHasName:(BOOL)newHasName; // Retrieve values +-(JumpcutClipping *) clipping; -(NSString *) contents; -(int) displayLength; -(NSString *) displayString; diff --git a/JumpcutEngine/JumpcutClipping.m b/JumpcutEngine/JumpcutClipping.m index 7f9d44e..b1b928a 100755 --- a/JumpcutEngine/JumpcutClipping.m +++ b/JumpcutEngine/JumpcutClipping.m @@ -150,6 +150,11 @@ return description; } +-(JumpcutClipping *) clipping +{ + return self; +} + -(NSString *) contents { // NSString *returnClipContents; diff --git a/JumpcutEngine/JumpcutStore.h b/JumpcutEngine/JumpcutStore.h index 3c32f1f..68c4a56 100755 --- a/JumpcutEngine/JumpcutStore.h +++ b/JumpcutEngine/JumpcutStore.h @@ -34,6 +34,7 @@ // In Jumpcut 0.5, I should go through and fiddle with the nomenclature. #import +#import "JumpcutClipping.h" @interface JumpcutStore : NSObject { @@ -64,6 +65,7 @@ -(int) rememberNum; -(int) displayLen; -(int) jcListCount; +-(JumpcutClipping *) clippingAtPosition:(int)index; -(NSString *) clippingContentsAtPosition:(int)index; -(NSString *) clippingDisplayStringAtPosition:(int)index; -(NSString *) clippingTypeAtPosition:(int)index; @@ -74,6 +76,7 @@ // Add a clipping -(void) addClipping:(NSString *)clipping ofType:(NSString *)type; +-(void) addClipping:(JumpcutClipping*) clipping; // Delete a clipping -(void) clearItem:(int)index; diff --git a/JumpcutEngine/JumpcutStore.m b/JumpcutEngine/JumpcutStore.m index 57686a7..dade452 100755 --- a/JumpcutEngine/JumpcutStore.m +++ b/JumpcutEngine/JumpcutStore.m @@ -60,18 +60,22 @@ newClipping = [[JumpcutClipping alloc] initWithContents:clipping withType:type withDisplayLength:[self displayLen]]; + + [self addClipping:newClipping]; + + [newClipping release]; +} - if ([jcList containsObject:newClipping] && [[[DBUserDefaults standardUserDefaults] valueForKey:@"removeDuplicates"] boolValue]) { - [jcList removeObject:newClipping]; +-(void) addClipping:(JumpcutClipping*) clipping{ + if ([jcList containsObject:clipping] && [[[DBUserDefaults standardUserDefaults] valueForKey:@"removeDuplicates"] boolValue]) { + [jcList removeObject:clipping]; } // Push it onto our recent clippings stack - [jcList insertObject:newClipping atIndex:0]; + [jcList insertObject:clipping atIndex:0]; // Delete clippings older than jcRememberNum while ( [jcList count] > jcRememberNum ) { [jcList removeObjectAtIndex:jcRememberNum]; } - - [newClipping release]; } -(void) addClipping:(NSString *)clipping ofType:(NSString *)type withPBCount:(int *)pbCount @@ -148,6 +152,15 @@ return [jcList count]; } +-(JumpcutClipping *) clippingAtPosition:(int)index +{ + if ( index >= [jcList count] ) { + return nil; + } else { + return [[jcList objectAtIndex:index] clipping]; + } +} + -(NSString *) clippingContentsAtPosition:(int)index { if ( index >= [jcList count] ) { From f068db6b038e0f14e1949b206e7c22624b203005 Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Fri, 15 Jan 2016 23:15:23 -0600 Subject: [PATCH 05/10] Add 'source' and 'timestamp' to clipping objects so that it can be displayed in the bezel along with the clipping. --- AppController.m | 76 +++++++++++++++++++++------------ JumpcutEngine/JumpcutClipping.h | 11 ++++- JumpcutEngine/JumpcutClipping.m | 50 ++++++++++++++++++++-- JumpcutEngine/JumpcutStore.h | 2 +- JumpcutEngine/JumpcutStore.m | 11 +++-- 5 files changed, 113 insertions(+), 37 deletions(-) diff --git a/AppController.m b/AppController.m index 1650b7a..690f3fa 100755 --- a/AppController.m +++ b/AppController.m @@ -597,7 +597,7 @@ { // Check to see if they want a little help figuring out what types to enter. if ( [[DBUserDefaults standardUserDefaults] boolForKey:@"revealPasteboardTypes"] ) - [clippingStore addClipping:type ofType:type]; + [clippingStore addClipping:type ofType:type fromAppLocalizedName:@"Flycut" fromAppBundleURL:nil atTimestamp:0]; __block bool skipClipping = NO; @@ -665,11 +665,11 @@ [pbCount release]; pbCount = [[NSNumber numberWithInt:[jcPasteboard changeCount]] retain]; if ( type != nil ) { - NSString *currRunningApp = @""; + NSRunningApplication *currRunningApp = nil; for (NSRunningApplication *currApp in [[NSWorkspace sharedWorkspace] runningApplications]) if ([currApp isActive]) - currRunningApp = [currApp localizedName]; - bool largeCopyRisk = [currRunningApp rangeOfString:@"Remote Desktop Connection"].location != NSNotFound; + currRunningApp = currApp; + bool largeCopyRisk = nil != currRunningApp && [[currRunningApp localizedName] rangeOfString:@"Remote Desktop Connection"].location != NSNotFound; // Microsoft's Remote Desktop Connection has an issue with large copy actions, which appears to be in the time it takes to transer them over the network. The copy starts being registered with OS X prior to completion of the transfer, and if the active application changes during the transfer the copy will be lost. Indicate this time period by toggling the menu icon at the beginning of all RDC trasfers and back at the end. Apple's Screen Sharing does not demonstrate this problem. if (largeCopyRisk) @@ -704,7 +704,10 @@ } [clippingStore addClipping:contents - ofType:type ]; + ofType:type + fromAppLocalizedName:[currRunningApp localizedName] + fromAppBundleURL:currRunningApp.bundleURL.path + atTimestamp:[[NSDate date] timeIntervalSince1970]]; // The below tracks our position down down down... Maybe as an option? // if ( [clippingStore jcListCount] > 1 ) stackPosition++; stackPosition = 0; @@ -1063,7 +1066,10 @@ NSArray *toBeRestoredClips = [[[savedJCList subarrayWithRange:loadRange] reverseObjectEnumerator] allObjects]; for( NSDictionary *aSavedClipping in toBeRestoredClips) [clippingStore addClipping:[aSavedClipping objectForKey:@"Contents"] - ofType:[aSavedClipping objectForKey:@"Type"]]; + ofType:[aSavedClipping objectForKey:@"Type"] + fromAppLocalizedName:[aSavedClipping objectForKey:@"AppLocalizedName"] + fromAppBundleURL:[aSavedClipping objectForKey:@"AppBundleURL"] + atTimestamp:[[aSavedClipping objectForKey:@"Timestamp"] integerValue]]; // Now for the favorites, same thing. savedJCList =[loadDict objectForKey:@"favoritesList"]; @@ -1075,7 +1081,10 @@ toBeRestoredClips = [[[savedJCList subarrayWithRange:loadRange] reverseObjectEnumerator] allObjects]; for( NSDictionary *aSavedClipping in toBeRestoredClips) [favoritesStore addClipping:[aSavedClipping objectForKey:@"Contents"] - ofType:[aSavedClipping objectForKey:@"Type"]]; + ofType:[aSavedClipping objectForKey:@"Type"] + fromAppLocalizedName:[aSavedClipping objectForKey:@"AppLocalizedName"] + fromAppBundleURL:[aSavedClipping objectForKey:@"AppBundleURL"] + atTimestamp:[[aSavedClipping objectForKey:@"Timestamp"] integerValue]]; } } else NSLog(@"Not array"); [self updateMenu]; @@ -1102,7 +1111,7 @@ -(void) fillBezel { JumpcutClipping* clipping = [clippingStore clippingAtPosition:stackPosition]; - [bezel setText:[clipping source]]; + [bezel setText:[NSString stringWithFormat:@"%@ - %@\n%@" , [clipping source],[NSDate dateWithTimeIntervalSince1970: [clipping timestamp]], [clipping contents]]]; [bezel setCharString:[NSString stringWithFormat:@"%d of %d", stackPosition + 1, [clippingStore jcListCount]]]; } @@ -1122,9 +1131,35 @@ } } +- (void)saveStore:(JumpcutStore *)store toKey:(NSString *)key onDict:(NSMutableDictionary *)saveDict { + NSMutableArray *jcListArray = [NSMutableArray array]; + for ( int i = 0 ; i < [store jcListCount] ; i++ ) + { + JumpcutClipping *clipping = [store clippingAtPosition:i]; + NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [clipping contents], @"Contents", + [clipping type], @"Type", + [NSNumber numberWithInt:i], @"Position",nil]; + + NSString *val = [clipping appLocalizedName]; + if ( nil != val ) + [dict setObject:val forKey:@"AppLocalizedName"]; + + val = [clipping appBundleURL]; + if ( nil != val ) + [dict setObject:val forKey:@"AppBundleURL"]; + + int timestamp = [clipping timestamp]; + if ( timestamp > 0 ) + [dict setObject:[NSNumber numberWithInt:timestamp] forKey:@"Timestamp"]; + + [jcListArray addObject:dict]; + } + [saveDict setObject:jcListArray forKey:key]; +} + -(void) saveEngine { NSMutableDictionary *saveDict; - NSMutableArray *jcListArray = [NSMutableArray array]; saveDict = [NSMutableDictionary dictionaryWithCapacity:3]; [saveDict setObject:@"0.7" forKey:@"version"]; [saveDict setObject:[NSNumber numberWithInt:[[DBUserDefaults standardUserDefaults] integerForKey:@"rememberNum"]] @@ -1135,25 +1170,10 @@ forKey:@"displayLen"]; [saveDict setObject:[NSNumber numberWithInt:[[DBUserDefaults standardUserDefaults] integerForKey:@"displayNum"]] forKey:@"displayNum"]; - for (int i = 0 ; i < [clippingStore jcListCount]; i++) - { - JumpcutClipping* clipping = [clippingStore clippingAtPosition:i]; - [jcListArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [clipping contents], @"Contents", - [clipping type], @"Type", - [NSNumber numberWithInt:i], @"Position",nil]]; - } - [saveDict setObject:jcListArray forKey:@"jcList"]; - jcListArray = [NSMutableArray array]; - for (int i = 0 ; i < [favoritesStore jcListCount]; i++) - { - JumpcutClipping* clipping = [favoritesStore clippingAtPosition:i]; - [jcListArray addObject:[NSDictionary dictionaryWithObjectsAndKeys: - [clipping contents], @"Contents", - [clipping type], @"Type", - [NSNumber numberWithInt:i], @"Position",nil]]; - } - [saveDict setObject:jcListArray forKey:@"favoritesList"]; + + [self saveStore:clippingStore toKey:@"jcList" onDict:saveDict]; + [self saveStore:favoritesStore toKey:@"favoritesList" onDict:saveDict]; + [[DBUserDefaults standardUserDefaults] setObject:saveDict forKey:@"store"]; [[DBUserDefaults standardUserDefaults] synchronize]; } diff --git a/JumpcutEngine/JumpcutClipping.h b/JumpcutEngine/JumpcutClipping.h index fe2a4ef..9a8c258 100755 --- a/JumpcutEngine/JumpcutClipping.h +++ b/JumpcutEngine/JumpcutClipping.h @@ -38,9 +38,15 @@ NSString * clipDisplayString; // Does it have a name? BOOL clipHasName; +// The app name it came from + NSString * appLocalizedName; +// The the bunle URL of the app it came from + NSString * appBundleURL; +// The time + int clipTimestamp; } --(id) initWithContents:(NSString *)contents withType:(NSString *)type withDisplayLength:(int)displayLength; +-(id) initWithContents:(NSString *)contents withType:(NSString *)type withDisplayLength:(int)displayLength withAppLocalizedName:(NSString *)localizedName withAppBundleURL:(NSString *)bundleURL withTimestamp:(int)timestamp; /* -(id) initWithCoder:(NSCoder *)coder; -(void) decodeWithCoder:(NSCoder *)coder; */ -(NSString *) description; @@ -58,6 +64,9 @@ -(int) displayLength; -(NSString *) displayString; -(NSString *) type; +-(NSString *) appLocalizedName; +-(NSString *) appBundleURL; +-(int) timestamp; -(BOOL) hasName; // Additional functions diff --git a/JumpcutEngine/JumpcutClipping.m b/JumpcutEngine/JumpcutClipping.m index b1b928a..ee6c77a 100755 --- a/JumpcutEngine/JumpcutClipping.m +++ b/JumpcutEngine/JumpcutClipping.m @@ -31,12 +31,15 @@ -(id) init { [self initWithContents:@"" - withType:@"" - withDisplayLength:40]; + withType:@"" + withDisplayLength:40 + withAppLocalizedName:@"" + withAppBundleURL:nil + withTimestamp:0]; return self; } --(id) initWithContents:(NSString *)contents withType:(NSString *)type withDisplayLength:(int)displayLength +-(id) initWithContents:(NSString *)contents withType:(NSString *)type withDisplayLength:(int)displayLength withAppLocalizedName:(NSString *)localizedName withAppBundleURL:(NSString*)bundleURL withTimestamp:(int)timestamp { [super init]; clipContents = [[[NSString alloc] init] retain]; @@ -45,6 +48,9 @@ [self setContents:contents setDisplayLength:displayLength]; [self setType:type]; + [self setAppLocalizedName:localizedName]; + [self setAppBundleURL:bundleURL]; + [self setTimestamp:timestamp]; [self setHasName:false]; return self; @@ -116,6 +122,27 @@ } } +-(void) setAppLocalizedName:(NSString *)new +{ + id old = appLocalizedName; + [new retain]; + appLocalizedName = new; + [old release]; +} + +-(void) setAppBundleURL:(NSString *)new +{ + id old = appBundleURL; + [new retain]; + appBundleURL = new; + [old release]; +} + +-(void) setTimestamp:(NSString *)newTimestamp +{ + clipTimestamp = newTimestamp; +} + -(void) setHasName:(BOOL)newHasName { clipHasName = newHasName; @@ -163,6 +190,21 @@ return clipContents; } +-(NSString *) appLocalizedName +{ + return appLocalizedName; +} + +-(NSString *) appBundleURL +{ + return appBundleURL; +} + +-(int) timestamp +{ + return clipTimestamp; +} + -(int) displayLength { return clipDisplayLength; @@ -206,6 +248,8 @@ { [clipContents release]; [clipType release]; + [appLocalizedName release]; + [appBundleURL release]; clipDisplayLength = 0; [clipDisplayString release]; clipHasName = 0; diff --git a/JumpcutEngine/JumpcutStore.h b/JumpcutEngine/JumpcutStore.h index 68c4a56..6fd7867 100755 --- a/JumpcutEngine/JumpcutStore.h +++ b/JumpcutEngine/JumpcutStore.h @@ -75,7 +75,7 @@ -(NSArray *) previousIndexes:(int)howMany containing:(NSString*)search; // This method is in newest-first order. // Add a clipping --(void) addClipping:(NSString *)clipping ofType:(NSString *)type; +-(void) addClipping:(NSString *)clipping ofType:(NSString *)type fromAppLocalizedName:(NSString *)appLocalizedName fromAppBundleURL:(NSString *)bundleURL atTimestamp:(int) timestamp; -(void) addClipping:(JumpcutClipping*) clipping; // Delete a clipping diff --git a/JumpcutEngine/JumpcutStore.m b/JumpcutEngine/JumpcutStore.m index dade452..1678307 100755 --- a/JumpcutEngine/JumpcutStore.m +++ b/JumpcutEngine/JumpcutStore.m @@ -50,7 +50,7 @@ } // Add a clipping --(void) addClipping:(NSString *)clipping ofType:(NSString *)type{ +-(void) addClipping:(NSString *)clipping ofType:(NSString *)type fromAppLocalizedName:(NSString *)appLocalizedName fromAppBundleURL:(NSString *)bundleURL atTimestamp:(int) timestamp{ if ([clipping length] == 0) { return; } @@ -59,7 +59,10 @@ // Create clipping newClipping = [[JumpcutClipping alloc] initWithContents:clipping withType:type - withDisplayLength:[self displayLen]]; + withDisplayLength:[self displayLen] + withAppLocalizedName:appLocalizedName + withAppBundleURL:bundleURL + withTimestamp:timestamp]; [self addClipping:newClipping]; @@ -80,7 +83,7 @@ -(void) addClipping:(NSString *)clipping ofType:(NSString *)type withPBCount:(int *)pbCount { - [self addClipping:clipping ofType:type]; + [self addClipping:clipping ofType:type fromAppLocalizedName:@"PBCount" fromAppBundleURL:nil atTimestamp:0]; } // Clear remembered and listed @@ -93,7 +96,7 @@ -(void) mergeList { NSString *merge = [[[[jcList reverseObjectEnumerator] allObjects] valueForKey:@"clipContents"] componentsJoinedByString:@"\n"]; - [self addClipping:merge ofType:NSStringFromClass([merge class])]; + [self addClipping:merge ofType:NSStringFromClass([merge class]) fromAppLocalizedName:@"Merge" fromAppBundleURL:nil atTimestamp:0]; } -(void) clearItem:(int)index From 1dcb2b9f8d7bc3b519decf857e8d04d13b6f1ce3 Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Sat, 16 Jan 2016 22:15:53 -0600 Subject: [PATCH 06/10] Consolidate repeated Bezel code so that changes only need to be made in one place. --- UI/BezelWindow.m | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/UI/BezelWindow.m b/UI/BezelWindow.m index 734d856..bec3acd 100755 --- a/UI/BezelWindow.m +++ b/UI/BezelWindow.m @@ -32,8 +32,9 @@ static const float lineHeight = 16; [self setHasShadow:NO]; [self setMovableByWindowBackground:NO]; [self setColor:NO]; - [self setBackgroundColor:[self sizedBezelBackgroundWithRadius:25.0 withAlpha:[[DBUserDefaults standardUserDefaults] floatForKey:@"bezelAlpha"]]]; - NSRect textFrame = NSMakeRect(12, 36, self.frame.size.width - 24, self.frame.size.height - 50); + [self setBackgroundColor:[self backgroundColor]]; + + NSRect textFrame = [self textFrame]; textField = [[RoundRecTextField alloc] initWithFrame:textFrame]; [[self contentView] addSubview:textField]; [textField setEditable:NO]; @@ -44,7 +45,8 @@ static const float lineHeight = 16; [textField setDrawsBackground:YES]; [textField setBordered:NO]; [textField setAlignment:NSLeftTextAlignment]; - NSRect charFrame = NSMakeRect(([self frame].size.width - (3 * lineHeight)) / 2, 7, 4 * lineHeight, 1.2 * lineHeight); + + NSRect charFrame = [self charFrame]; charField = [[RoundRecTextField alloc] initWithFrame:charFrame]; [[self contentView] addSubview:charField]; [charField setEditable:NO]; @@ -62,17 +64,32 @@ static const float lineHeight = 16; - (void) update { [super update]; - [self setBackgroundColor:[self sizedBezelBackgroundWithRadius:25.0 withAlpha:[[DBUserDefaults standardUserDefaults] floatForKey:@"bezelAlpha"]]]; - NSRect textFrame = NSMakeRect(12, 36, self.frame.size.width - 24, self.frame.size.height - 50); + [self setBackgroundColor:[self backgroundColor]]; + NSRect textFrame = [self textFrame]; [textField setFrame:textFrame]; - NSRect charFrame = NSMakeRect(([self frame].size.width - (3 * lineHeight)) / 2, 7, 4 * lineHeight, 1.2 * lineHeight); + NSRect charFrame = [self charFrame]; [charField setFrame:charFrame]; } +-(NSRect) textFrame +{ + return NSMakeRect(12, 36, self.frame.size.width - 24, self.frame.size.height - 50); +} + +-(NSRect) charFrame +{ + return NSMakeRect(([self frame].size.width - (3 * lineHeight)) / 2, 7, 4 * lineHeight, 1.2 * lineHeight); +} + +-(NSColor*) backgroundColor +{ + return [self sizedBezelBackgroundWithRadius:25.0 withAlpha:[[DBUserDefaults standardUserDefaults] floatForKey:@"bezelAlpha"]]; +} + - (void) setAlpha:(float)newValue { - [self setBackgroundColor:[self sizedBezelBackgroundWithRadius:25.0 withAlpha:[[DBUserDefaults standardUserDefaults] floatForKey:@"bezelAlpha"]]]; + [self setBackgroundColor:[self backgroundColor]]; [[self contentView] setNeedsDisplay:YES]; } From 7a067ceff2685c3dae3595d8ce81a8f5ccc90df4 Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Sat, 16 Jan 2016 22:16:55 -0600 Subject: [PATCH 07/10] Display source and timestamp in bezel. --- AppController.h | 1 + AppController.m | 22 ++++++- UI/BezelWindow.h | 19 ++++++ UI/BezelWindow.m | 148 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 186 insertions(+), 4 deletions(-) diff --git a/AppController.h b/AppController.h index 291fa04..30277e0 100755 --- a/AppController.h +++ b/AppController.h @@ -30,6 +30,7 @@ int stackPosition; int favoritesStackPosition; int stashedStackPosition; + NSDateFormatter* dateFormat; // The below were pulled in from JumpcutController JumpcutStore *clippingStore; diff --git a/AppController.m b/AppController.m index 690f3fa..ed12de7 100755 --- a/AppController.m +++ b/AppController.m @@ -109,10 +109,16 @@ bezel = [[BezelWindow alloc] initWithContentRect:windowFrame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered - defer:NO]; + defer:NO + showSource:YES]; [bezel trueCenter]; [bezel setDelegate:self]; + // Set up the bezel date formatter + dateFormat = [[NSDateFormatter alloc] init]; + [dateFormat setDateFormat:@"EEEE, MMMM dd 'at' h:mm a"]; + + // Create our pasteboard interface jcPasteboard = [NSPasteboard generalPasteboard]; [jcPasteboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; @@ -1111,8 +1117,20 @@ -(void) fillBezel { JumpcutClipping* clipping = [clippingStore clippingAtPosition:stackPosition]; - [bezel setText:[NSString stringWithFormat:@"%@ - %@\n%@" , [clipping source],[NSDate dateWithTimeIntervalSince1970: [clipping timestamp]], [clipping contents]]]; + [bezel setText:[NSString stringWithFormat:@"%@", [clipping contents]]]; [bezel setCharString:[NSString stringWithFormat:@"%d of %d", stackPosition + 1, [clippingStore jcListCount]]]; + NSString *localizedName = [clipping appLocalizedName]; + if ( nil == localizedName ) + localizedName = @""; + NSString* dateString = @""; + if ( [clipping timestamp] > 0) + dateString = [dateFormat stringFromDate:[NSDate dateWithTimeIntervalSince1970: [clipping timestamp]]]; + NSImage* icon = nil; + if (nil != [clipping appBundleURL]) + icon = [[NSWorkspace sharedWorkspace] iconForFile:[clipping appBundleURL]]; + [bezel setSource:localizedName]; + [bezel setDate:dateString]; + [bezel setSourceIcon:icon]; } -(void) stackUp diff --git a/UI/BezelWindow.h b/UI/BezelWindow.h index 88b9c99..7c521b2 100755 --- a/UI/BezelWindow.h +++ b/UI/BezelWindow.h @@ -14,10 +14,20 @@ @interface BezelWindow : NSWindow { + // "n of n" text in bezel NSString *charString; // Slightly misleading, as this can be longer than one character NSString *title; + // Clipping text shown in bezel NSString *bezelText; + NSString *sourceText; + NSString *dateText; + NSImage *sourceIconImage; NSImage *icon; + Boolean showSourceField; + NSImageView *sourceIcon; + RoundRecTextField *sourceFieldBackground; + RoundRecTextField *sourceFieldApp; + RoundRecTextField *sourceFieldDate; RoundRecTextField *textField; RoundRecTextField *charField; NSImageView *iconView; @@ -25,6 +35,12 @@ Boolean color; } +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(NSUInteger)aStyle + backing:(NSBackingStoreType)bufferingType + defer:(BOOL)flag + showSource:(BOOL)showSource; + - (NSColor *)roundedBackgroundWithRect:(NSRect)bgRect withRadius:(float)radius withAlpha:(float)alpha; - (NSColor *)sizedBezelBackgroundWithRadius:(float)radius withAlpha:(float)alpha; @@ -35,6 +51,9 @@ - (void)setColor:(BOOL)value; - (void)setCharString:(NSString *)newChar; - (void)setAlpha:(float)newValue; +- (void)setSource:(NSString *)newSource; +- (void)setDate:(NSString *)newDate; +- (void)setSourceIcon:(NSImage *)newSourceIcon; - (id)delegate; - (void)setDelegate:(id)newDelegate; diff --git a/UI/BezelWindow.m b/UI/BezelWindow.m index bec3acd..40a7770 100755 --- a/UI/BezelWindow.m +++ b/UI/BezelWindow.m @@ -18,7 +18,8 @@ static const float lineHeight = 16; - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType - defer:(BOOL)flag { + defer:(BOOL)flag + showSource:(BOOL)showSource { self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask @@ -33,6 +34,55 @@ static const float lineHeight = 16; [self setMovableByWindowBackground:NO]; [self setColor:NO]; [self setBackgroundColor:[self backgroundColor]]; + showSourceField = showSource; + + if (showSourceField) + { + sourceIcon = [[NSImageView alloc] initWithFrame: [self iconFrame]]; + [[self contentView] addSubview:sourceIcon]; + [sourceIcon setEditable:NO]; + + sourceFieldBackground = [[RoundRecTextField alloc] initWithFrame:[self sourceFrame]]; + [[self contentView] addSubview:sourceFieldBackground]; + [sourceFieldBackground setEditable:NO]; + [sourceFieldBackground setTextColor:[NSColor whiteColor]]; + [sourceFieldBackground setBackgroundColor:[NSColor colorWithCalibratedWhite:0.1 alpha:.45]]; + [sourceFieldBackground setDrawsBackground:YES]; + [sourceFieldBackground setBordered:NO]; + + sourceFieldApp = [[RoundRecTextField alloc] initWithFrame:[self sourceFrameLeft]]; + [[self contentView] addSubview:sourceFieldApp]; + [sourceFieldApp setEditable:NO]; + [sourceFieldApp setTextColor:[NSColor whiteColor]]; + [sourceFieldApp setBackgroundColor:[NSColor colorWithCalibratedWhite:0.1 alpha:0]]; + [sourceFieldApp setDrawsBackground:YES]; + [sourceFieldApp setBordered:NO]; + [sourceFieldApp setAlignment:NSLeftTextAlignment]; + + NSMutableParagraphStyle *textParagraph = [[NSMutableParagraphStyle alloc] init]; + [textParagraph setLineSpacing:100.0]; + + NSDictionary *attrDic = [NSDictionary dictionaryWithObjectsAndKeys:textParagraph, NSParagraphStyleAttributeName, nil]; + NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:@"foo" attributes:attrDic]; + [sourceFieldApp setAllowsEditingTextAttributes:YES]; + [sourceFieldApp setAttributedStringValue:attrString]; + + NSFont *font = [sourceFieldApp font]; + NSFont *newFont = [NSFont fontWithName:[font fontName] size:font.pointSize*3/2]; + [sourceFieldApp setFont:newFont]; + + sourceFieldDate = [[RoundRecTextField alloc] initWithFrame:[self sourceFrameRight]]; + [[self contentView] addSubview:sourceFieldDate]; + [sourceFieldDate setEditable:NO]; + [sourceFieldDate setTextColor:[NSColor whiteColor]]; + [sourceFieldDate setBackgroundColor:[NSColor colorWithCalibratedWhite:0.1 alpha:0]]; + [sourceFieldDate setDrawsBackground:YES]; + [sourceFieldDate setBordered:NO]; + [sourceFieldDate setAlignment:NSRightTextAlignment]; + font = [sourceFieldDate font]; + newFont = [NSFont fontWithName:[font fontName] size:font.pointSize*5/4]; + [sourceFieldDate setFont:newFont]; + } NSRect textFrame = [self textFrame]; textField = [[RoundRecTextField alloc] initWithFrame:textFrame]; @@ -55,6 +105,7 @@ static const float lineHeight = 16; [charField setDrawsBackground:YES]; [charField setBordered:NO]; [charField setAlignment:NSCenterTextAlignment]; + [self setInitialFirstResponder:textField]; return self; } @@ -65,16 +116,65 @@ static const float lineHeight = 16; - (void) update { [super update]; [self setBackgroundColor:[self backgroundColor]]; + + Boolean savedShowSourceField = showSourceField; + if (nil == sourceText || 0 == sourceText.length) + showSourceField = false; + NSRect textFrame = [self textFrame]; [textField setFrame:textFrame]; NSRect charFrame = [self charFrame]; [charField setFrame:charFrame]; + if (showSourceField) + [sourceFieldBackground setBackgroundColor:[NSColor colorWithCalibratedWhite:0.1 alpha:.45]]; + else if ( nil != sourceFieldApp ) + [sourceFieldBackground setBackgroundColor:[NSColor colorWithCalibratedWhite:0.1 alpha:0]]; + showSourceField = savedShowSourceField; + +} + +-(NSRect) iconFrame +{ + NSRect frame = [self textFrame]; + frame.origin.y += frame.size.height + 5; + frame.size.height = 1.8 * lineHeight; + frame.size.width = frame.size.height; + return frame; +} + +-(NSRect) sourceFrame +{ + NSRect frame = [self textFrame]; + frame.origin.y += frame.size.height + 5; + frame.size.height = 1.8 * lineHeight; + frame.origin.x += frame.size.height + 5; + frame.size.width -= frame.size.height + 5; + return frame; +} + +-(NSRect) sourceFrameLeft +{ + NSRect frame = [self sourceFrame]; + frame.size.width = frame.size.width * 1 / 3 - 5; + frame.origin.x += 5; + return frame; +} + +-(NSRect) sourceFrameRight +{ + NSRect frame = [self sourceFrame]; + frame.size.height -= 0.3 * lineHeight; + frame.origin.x += frame.size.width * 1 / 3 + 10; + frame.size.width = frame.size.width * 2 / 3 - 10; + return frame; } -(NSRect) textFrame { - return NSMakeRect(12, 36, self.frame.size.width - 24, self.frame.size.height - 50); + int adjustHeight = 0; + if (showSourceField) adjustHeight = 1.8 * lineHeight; + return NSMakeRect(12, 36, self.frame.size.width - 24, self.frame.size.height - 50 - adjustHeight); } -(NSRect) charFrame @@ -130,6 +230,50 @@ static const float lineHeight = 16; [textField setStringValue:bezelText]; } +- (void)setSourceIcon:(NSImage *)newSourceIcon +{ + if (!showSourceField) + return; + [newSourceIcon retain]; + [sourceIconImage release]; + sourceIconImage = newSourceIcon; + [sourceIcon setImage:sourceIconImage]; +} + +- (void)setSource:(NSString *)newSource +{ + if (!showSourceField) + return; + + // Ensure that the source will fit in the screen space available, and truncate nicely if need be. + NSDictionary *attributes = @{NSFontAttributeName: sourceFieldApp.font}; + CGSize size = [newSource sizeWithAttributes:attributes]; // How big is this string when drawn in this font? + if (size.width >= sourceFieldApp.frame.size.width - 5) + { + newSource = [NSString stringWithFormat:@"%@...", newSource]; + do + { + newSource = [NSString stringWithFormat:@"%@...", [newSource substringToIndex:[newSource length] - 4]]; + size = [newSource sizeWithAttributes:attributes]; + } while (size.width >= sourceFieldApp.frame.size.width - 5); + } + + [newSource retain]; + [sourceText release]; + sourceText = newSource; + [sourceFieldApp setStringValue:sourceText]; +} + +- (void)setDate:(NSString *)newDate +{ + if (!showSourceField) + return; + [newDate retain]; + [dateText release]; + dateText = newDate; + [sourceFieldDate setStringValue:dateText]; +} + - (void)setColor:(BOOL)value { color=value; From c6ea7b49e3a16f75cf7a00a3f65ae4eab84506fa Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Fri, 22 Jan 2016 23:22:47 -0600 Subject: [PATCH 08/10] Convert Preferences panel for Appearance to programmatic creation for better team development. --- AppController.h | 1 + AppController.m | 174 +++++++++++++++++- English.lproj/MainMenu.nib/designable.nib | 188 +------------------- English.lproj/MainMenu.nib/keyedobjects.nib | Bin 42404 -> 36200 bytes 4 files changed, 180 insertions(+), 183 deletions(-) diff --git a/AppController.h b/AppController.h index 30277e0..5fc9b08 100755 --- a/AppController.h +++ b/AppController.h @@ -22,6 +22,7 @@ SGHotKey *mainHotKey; IBOutlet SRRecorderControl *mainRecorder; IBOutlet NSPanel *prefsPanel; + IBOutlet NSBox *appearancePanel; int mainHotkeyModifiers; SRKeyCodeTransformer *srTransformer; BOOL isBezelDisplayed; diff --git a/AppController.m b/AppController.m index ed12de7..73715ef 100755 --- a/AppController.m +++ b/AppController.m @@ -81,6 +81,7 @@ - (void)awakeFromNib { + [self buildAppearancesPreferencePanel]; // We no longer get autosave from ShortcutRecorder, so let's set the recorder by hand if ( [[DBUserDefaults standardUserDefaults] dictionaryForKey:@"ShortcutRecorder mainHotkey"] ) { @@ -98,10 +99,6 @@ stashedStore = NULL; [bezel setColor:NO]; - NSRect screenFrame = [[NSScreen mainScreen] frame]; - widthSlider.maxValue = screenFrame.size.width; - heightSlider.maxValue = screenFrame.size.height; - // Set up the bezel window NSRect windowFrame = NSMakeRect(0, 0, [[DBUserDefaults standardUserDefaults] floatForKey:@"bezelWidth"], @@ -379,6 +376,175 @@ [self updateMenu]; } +-(NSTextField*) preferencePanelSliderLabelForText:(NSString*)text aligned:(NSTextAlignment)alignment andFrame:(NSRect)frame +{ + NSTextField *newLabel = [[NSTextField alloc] initWithFrame:frame]; + newLabel.editable = NO; + [newLabel setAlignment:alignment]; + [newLabel setBordered:NO]; + [newLabel setDrawsBackground:NO]; + [newLabel setFont:[NSFont labelFontOfSize:10]]; + [newLabel setStringValue:text]; + return newLabel; +} + +-(NSBox*) preferencePanelSliderRowForText:(NSString*)title withTicks:(int)ticks minText:(NSString*)minText maxText:(NSString*)maxText minValue:(double)min maxValue:(double)max frameMaxY:(int)frameMaxY binding:(NSString*)keyPath action:(SEL)action +{ + NSRect panelFrame = [appearancePanel frame]; + + if ( frameMaxY < 0 ) + frameMaxY = panelFrame.size.height-8; + + int height = 63; + + NSBox *newRow = [[NSBox alloc] initWithFrame:NSMakeRect(0, frameMaxY-height, panelFrame.size.width-10, height)]; + [newRow setTitlePosition:NSNoTitle]; + [newRow setBorderType:NSNoBorder]; + + [newRow addSubview:[self preferencePanelSliderLabelForText:title aligned:NSNaturalTextAlignment andFrame:NSMakeRect(8, 25, 100, 25)]]; + + [newRow addSubview:[self preferencePanelSliderLabelForText:minText aligned:NSLeftTextAlignment andFrame:NSMakeRect(113, 0, 151, 25)]]; + [newRow addSubview:[self preferencePanelSliderLabelForText:maxText aligned:NSRightTextAlignment andFrame:NSMakeRect(109+310-151-4, 0, 151, 25)]]; + + NSSlider *newControl = [[NSSlider alloc] initWithFrame:NSMakeRect(109, 29, 310, 25)]; + + newControl.numberOfTickMarks=ticks; + [newControl setMinValue:min]; + [newControl setMaxValue:max]; + + [self setBinding:@"value" forKey:keyPath andOrAction:action on:newControl]; + + [newRow addSubview:newControl]; + + return newRow; +} + +-(NSBox*) preferencePanelPopUpRowForText:(NSString*)title items:(NSArray*)items frameMaxY:(int)frameMaxY binding:(NSString*)keyPath action:(SEL)action +{ + NSRect panelFrame = [appearancePanel frame]; + + if ( frameMaxY < 0 ) + frameMaxY = panelFrame.size.height-8; + + int height = 40; + + NSBox *newRow = [[NSBox alloc] initWithFrame:NSMakeRect(0, frameMaxY-height+5, panelFrame.size.width-10, height)]; + [newRow setTitlePosition:NSNoTitle]; + [newRow setBorderType:NSNoBorder]; + + [newRow addSubview:[self preferencePanelSliderLabelForText:title aligned:NSNaturalTextAlignment andFrame:NSMakeRect(8, -2, 100, 25)]]; + + NSPopUpButton *newControl = [[NSPopUpButton alloc] initWithFrame:NSMakeRect(109, 4, 150, 25) pullsDown:NO]; + + [newControl addItemsWithTitles:items]; + + [self setBinding:@"selectedIndex" forKey:keyPath andOrAction:action on:newControl]; + + [newRow addSubview:newControl]; + + return newRow; +} + +-(NSBox*) preferencePanelCheckboxRowForText:(NSString*)title frameMaxY:(int)frameMaxY binding:(NSString*)keyPath action:(SEL)action +{ + NSRect panelFrame = [appearancePanel frame]; + + if ( frameMaxY < 0 ) + frameMaxY = panelFrame.size.height-8; + + int height = 40; + + NSBox *newRow = [[NSBox alloc] initWithFrame:NSMakeRect(0, frameMaxY-height+5, panelFrame.size.width-10, height)]; + [newRow setTitlePosition:NSNoTitle]; + [newRow setBorderType:NSNoBorder]; + + NSButton *newControl = [[NSButton alloc] initWithFrame:NSMakeRect(8, 4, panelFrame.size.width-20, 25)]; + + [newControl setButtonType:NSSwitchButton]; + [newControl setTitle:title]; + + [self setBinding:@"value" forKey:keyPath andOrAction:action on:newControl]; + + [newRow addSubview:newControl]; + + return newRow; +} + +-(void)setBinding:(NSString*)binding forKey:(NSString*)keyPath andOrAction:(SEL)action on:(NSControl*)newControl +{ + [newControl bind:binding + toObject:[DBUserDefaults standardUserDefaults] + withKeyPath:keyPath + options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] + forKey:@"NSContinuouslyUpdatesValue"]]; + if ( nil != action ) + { + [newControl setTarget:self]; + [newControl setAction:action]; + } +} + +-(void) buildAppearancesPreferencePanel +{ + NSRect screenFrame = [[NSScreen mainScreen] frame]; + + int nextYMax = -1; + NSView *row = [self preferencePanelSliderRowForText:@"Bezel transparency" + withTicks:16 + minText:@"Lighter" + maxText:@"Darker" + minValue:0.1 + maxValue:0.9 + frameMaxY:nextYMax + binding:@"bezelAlpha" + action:@selector(setBezelAlpha:)]; + [appearancePanel addSubview:row]; + nextYMax = row.frame.origin.y; + + row = [self preferencePanelSliderRowForText:@"Bezel width" + withTicks:50 + minText:@"Smaller" + maxText:@"Bigger" + minValue:200 + maxValue:screenFrame.size.width + frameMaxY:nextYMax + binding:@"bezelWidth" + action:@selector(setBezelWidth:)]; + [appearancePanel addSubview:row]; + nextYMax = row.frame.origin.y; + + row = [self preferencePanelSliderRowForText:@"Bezel height" + withTicks:50 + minText:@"Smaller" + maxText:@"Bigger" + minValue:200 + maxValue:screenFrame.size.height + frameMaxY:nextYMax + binding:@"bezelHeight" + action:@selector(setBezelHeight:)]; + [appearancePanel addSubview:row]; + nextYMax = row.frame.origin.y; + + row = [self preferencePanelPopUpRowForText:@"Menu item icon" + items:[NSArray arrayWithObjects: + @"Flycut icon", + @"Black Flycut icon", + @"White scissors", + @"Black scissors",nil] + frameMaxY:nextYMax + binding:@"menuIcon" + action:@selector(switchMenuIcon:)]; + [appearancePanel addSubview:row]; + nextYMax = row.frame.origin.y; + + row = [self preferencePanelCheckboxRowForText:@"Animate bezel appearance" + frameMaxY:nextYMax + binding:@"popUpAnimation" + action:nil]; + [appearancePanel addSubview:row]; + nextYMax = row.frame.origin.y; +} + -(IBAction) showPreferencePanel:(id)sender { [currentRunningApplication release]; diff --git a/English.lproj/MainMenu.nib/designable.nib b/English.lproj/MainMenu.nib/designable.nib index b202bbf..83266a2 100644 --- a/English.lproj/MainMenu.nib/designable.nib +++ b/English.lproj/MainMenu.nib/designable.nib @@ -1,5 +1,5 @@ - + @@ -93,13 +93,12 @@ + - - @@ -472,7 +471,7 @@ - + @@ -517,186 +516,17 @@ - - + + - - + + - + - - - - - - - - - - - - - - - - - - Lighter - - - - - - - - - - - - Darker - - - - - - - - - - - - - - - - - - - - - - Smaller - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Menu item icon - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Bezel transparency - - - - - - - @@ -707,7 +537,7 @@ - + diff --git a/English.lproj/MainMenu.nib/keyedobjects.nib b/English.lproj/MainMenu.nib/keyedobjects.nib index 35a1f9e83f1a4f83966944d07a006963cba6fa41..729202c5ddf0cbfbec1a40e35bcfbb1d8e103c3c 100644 GIT binary patch literal 36200 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 literal 42404 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 From d51dadeb2620c7cc2c820865e6630082a22cef9a Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Fri, 22 Jan 2016 23:47:15 -0600 Subject: [PATCH 09/10] Add Preferences option to disable / enable bezel display of clipping source and timestamp. --- AppController.m | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/AppController.m b/AppController.m index 73715ef..9321d91 100755 --- a/AppController.m +++ b/AppController.m @@ -75,6 +75,8 @@ @"popUpAnimation", [NSNumber numberWithBool:NO], @"pasteMovesToTop", + [NSNumber numberWithBool:YES], + @"displayClippingSource", nil]]; return [super init]; } @@ -100,16 +102,7 @@ [bezel setColor:NO]; // Set up the bezel window - NSRect windowFrame = NSMakeRect(0, 0, - [[DBUserDefaults standardUserDefaults] floatForKey:@"bezelWidth"], - [[DBUserDefaults standardUserDefaults] floatForKey:@"bezelHeight"]); - bezel = [[BezelWindow alloc] initWithContentRect:windowFrame - styleMask:NSBorderlessWindowMask - backing:NSBackingStoreBuffered - defer:NO - showSource:YES]; - [bezel trueCenter]; - [bezel setDelegate:self]; + [self setupBezel:nil]; // Set up the bezel date formatter dateFormat = [[NSDateFormatter alloc] init]; @@ -309,6 +302,20 @@ [bezel trueCenter]; } +-(IBAction) setupBezel:(id)sender +{ + NSRect windowFrame = NSMakeRect(0, 0, + [[DBUserDefaults standardUserDefaults] floatForKey:@"bezelWidth"], + [[DBUserDefaults standardUserDefaults] floatForKey:@"bezelHeight"]); + bezel = [[BezelWindow alloc] initWithContentRect:windowFrame + styleMask:NSBorderlessWindowMask + backing:NSBackingStoreBuffered + defer:NO + showSource:[[DBUserDefaults standardUserDefaults] boolForKey:@"displayClippingSource"]]; + + [bezel trueCenter]; + [bezel setDelegate:self]; +} -(IBAction) switchMenuIcon:(id)sender { @@ -543,6 +550,13 @@ action:nil]; [appearancePanel addSubview:row]; nextYMax = row.frame.origin.y; + + row = [self preferencePanelCheckboxRowForText:@"Show clipping source app and time" + frameMaxY:nextYMax + binding:@"displayClippingSource" + action:@selector(setupBezel:)]; + [appearancePanel addSubview:row]; + nextYMax = row.frame.origin.y; } -(IBAction) showPreferencePanel:(id)sender From b16d788b88b188912687f31a5ead8a6982601765 Mon Sep 17 00:00:00 2001 From: Mark Jerde Date: Sun, 24 Jan 2016 13:36:53 -0600 Subject: [PATCH 10/10] Bump version number to 1.7 and add automatic bundle versioning to provide better support through being able to identify builds. --- Flycut.xcodeproj/project.pbxproj | 17 +++++++++++++++++ Info.plist | 14 ++------------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Flycut.xcodeproj/project.pbxproj b/Flycut.xcodeproj/project.pbxproj index b8f828f..2822b4a 100755 --- a/Flycut.xcodeproj/project.pbxproj +++ b/Flycut.xcodeproj/project.pbxproj @@ -429,6 +429,7 @@ isa = PBXNativeTarget; buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Flycut" */; buildPhases = ( + 8D60A1BA1BC8399900A26CBF /* ShellScript */, 8D1107290486CEB800E47090 /* Resources */, 8D11072C0486CEB800E47090 /* Sources */, AAECDE440A03E75C007D377A /* CopyFiles */, @@ -511,6 +512,22 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + 8D60A1BA1BC8399900A26CBF /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 12; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Sets the build number to identify, to some extent, the git version of the code.\n# Combines the hash of the origin commit, number of commits of divergence, local HEAD commit, local uncommitted lines of change, and year/month of build.\n# Eliminates all except for the origin commit if there are no local changes, since that can be easily tracked.\n# e.g. 1.b690b47.1.1.cb73ce6.35.1510 from origin b690b47, one and one different, locally at commit cb73ce6, with a further 35 lines of change, built in 2015 in the tenth month. Day numbers are excluded for brevity, as there is already much uncertainty in the local changes and this field is only to indicate a rough level of freshness. Build numer starts with \"1.\" to avoid strange compile errors.\ncd \"${SOURCE_ROOT}\"\nORIGIN=$(git rev-parse --short remotes/origin/master)\nOFFSET=\".$(git status|sed -n '1,/^# *$/ p'|grep \"[0-9]\"|head -1|sed 's/^[^0-9]*//;s/[^0-9]*$//'|sed 's/[^0-9][^0-9]*/./')\"\nLOCAL=\".$(git rev-parse --short HEAD)\"\nUNCOMMITTED=\".$(git diff -b -w HEAD|grep \"^[+\\-]\"|grep -v \"^[+\\-][+\\-][+\\-] [ab]\"|wc -l|sed 's/[^0-9]//g')\"\nBUILD_YEAR_MONTH=\".$(date +\"%y%m\")\"\nif [ \".$ORIGIN\" == \"$LOCAL\" ]\nthen\n OFFSET=\"\"\n LOCAL=\"\"\n if [ \".0\" == \"$UNCOMMITTED\" ]\n then\n UNCOMMITTED=\"\"\n BUILD_YEAR_MONTH=\"\"\n fi\nfi\nNEWVERSIONSTRING=\"1.$ORIGIN$OFFSET$LOCAL$UNCOMMITTED$BUILD_YEAR_MONTH\"\n/usr/libexec/PlistBuddy -c \"Set :CFBundleVersion $NEWVERSIONSTRING\" \"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ 8D11072C0486CEB800E47090 /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Info.plist b/Info.plist index 7f50024..023a95a 100755 --- a/Info.plist +++ b/Info.plist @@ -4,8 +4,6 @@ CFBundleDevelopmentRegion English - CFBundleDocumentTypes - CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile @@ -19,13 +17,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 1.7 CFBundleSignature ???? - CFBundleURLTypes - CFBundleVersion - 1.0 + 1.3456789 LSApplicationCategoryType public.app-category.developer-tools LSUIElement @@ -36,12 +32,6 @@ MainMenu NSPrincipalClass NSApplication - NSServices - - UTExportedTypeDeclarations - - UTImportedTypeDeclarations - NSSupportsAutomaticGraphicsSwitching