mirror of
https://github.com/TermiT/Flycut.git
synced 2025-11-08 07:30:48 +08:00
Add support for iCloud Sync.
This commit is contained in:
parent
7958af9110
commit
13ceb8d7d0
14 changed files with 854 additions and 187 deletions
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
@class SGHotKey;
|
||||
|
||||
@interface AppController : NSObject <NSMenuDelegate> {
|
||||
@interface AppController : NSObject <NSMenuDelegate, NSApplicationDelegate> {
|
||||
BezelWindow *bezel;
|
||||
SGHotKey *mainHotKey;
|
||||
IBOutlet SRRecorderControl *mainRecorder;
|
||||
|
|
@ -35,6 +35,8 @@
|
|||
NSString *currentKeycodeCharacter;
|
||||
NSDateFormatter* dateFormat;
|
||||
|
||||
NSArray *settingsSyncList;
|
||||
|
||||
FlycutOperator *flycutOperator;
|
||||
|
||||
// Status item -- the little icon in the menu bar
|
||||
|
|
@ -105,6 +107,9 @@
|
|||
-(IBAction) switchMenuIcon:(id)sender;
|
||||
-(IBAction) toggleLoadOnStartup:(id)sender;
|
||||
-(IBAction) toggleMainHotKey:(id)sender;
|
||||
-(IBAction) toggleICloudSyncSettings:(id)sender;
|
||||
-(IBAction) toggleICloudSyncClippings:(id)sender;
|
||||
-(IBAction) setSavePreference:(id)sender;
|
||||
-(void) setHotKeyPreferenceForRecorder:(SRRecorderControl *)aRecorder;
|
||||
|
||||
@end
|
||||
|
|
|
|||
218
AppController.m
218
AppController.m
|
|
@ -19,6 +19,7 @@
|
|||
#import "UKLoginItemRegistry.h"
|
||||
#import "NSWindow+TrueCenter.h"
|
||||
#import "NSWindow+ULIZoomEffect.h"
|
||||
#import "MJCloudKitUserDefaultsSync.h"
|
||||
|
||||
@implementation AppController
|
||||
|
||||
|
|
@ -53,9 +54,49 @@
|
|||
[NSNumber numberWithBool:YES],
|
||||
@"displayClippingSource",
|
||||
nil]];
|
||||
|
||||
/* For testing, the ability to force initial values of the sync settings:
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO]
|
||||
forKey:@"syncSettingsViaICloud"];
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO]
|
||||
forKey:@"syncClippingsViaICloud"];*/
|
||||
|
||||
settingsSyncList = @[@"displayNum",
|
||||
@"displayLen",
|
||||
@"menuIcon",
|
||||
@"bezelAlpha",
|
||||
@"stickyBezel",
|
||||
@"wraparoundBezel",
|
||||
@"loadOnStartup",
|
||||
@"menuSelectionPastes",
|
||||
@"bezelWidth",
|
||||
@"bezelHeight",
|
||||
@"popUpAnimation",
|
||||
@"displayClippingSource"];
|
||||
[settingsSyncList retain];
|
||||
|
||||
return [super init];
|
||||
}
|
||||
|
||||
- (void)registerOrDeregisterICloudSync
|
||||
{
|
||||
if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"syncSettingsViaICloud"] ) {
|
||||
[MJCloudKitUserDefaultsSync removeNotificationsFor:MJSyncNotificationChanges forTarget:self];
|
||||
[MJCloudKitUserDefaultsSync addNotificationFor:MJSyncNotificationChanges withSelector:@selector(checkPreferencesChanges:) withTarget: self];
|
||||
// Not registering for conflict notifications, since we just sync settings, and if the settings are conflictingly adjusted simultaneously on two systems there is nothing to say which setting is better.
|
||||
|
||||
[MJCloudKitUserDefaultsSync startWithKeyMatchList:settingsSyncList
|
||||
withContainerIdentifier:@"iCloud.com.mark-a-jerde.Flycut"];
|
||||
}
|
||||
else {
|
||||
[MJCloudKitUserDefaultsSync stopForKeyMatchList:settingsSyncList];
|
||||
|
||||
[MJCloudKitUserDefaultsSync removeNotificationsFor:MJSyncNotificationChanges forTarget:self];
|
||||
}
|
||||
|
||||
[flycutOperator registerOrDeregisterICloudSync];
|
||||
}
|
||||
|
||||
- (void)awakeFromNib
|
||||
{
|
||||
[self buildAppearancesPreferencePanel];
|
||||
|
|
@ -130,6 +171,8 @@
|
|||
}
|
||||
});
|
||||
|
||||
[self registerOrDeregisterICloudSync];
|
||||
|
||||
[NSApp activateIgnoringOtherApps: YES];
|
||||
}
|
||||
|
||||
|
|
@ -312,52 +355,79 @@
|
|||
}
|
||||
}
|
||||
|
||||
-(NSDictionary*) checkPreferencesChanges:(NSDictionary*)changes
|
||||
{
|
||||
if ( [changes valueForKey:@"rememberNum"] )
|
||||
[self checkRememberNumPref:[[NSUserDefaults standardUserDefaults] integerForKey:@"rememberNum"]
|
||||
forPrimaryStore:YES];
|
||||
if ( [changes valueForKey:@"favoritesRememberNum"] )
|
||||
[self checkFavoritesRememberNumPref:[[NSUserDefaults standardUserDefaults] integerForKey:@"favoritesRememberNum"]];
|
||||
return nil;
|
||||
}
|
||||
|
||||
-(IBAction) setRememberNumPref:(id)sender
|
||||
{
|
||||
int choice;
|
||||
int newRemember = [sender intValue];
|
||||
[self checkRememberNumPref:[sender intValue] forPrimaryStore:YES];
|
||||
}
|
||||
|
||||
-(void) checkRememberNumPref:(int)newRemember forPrimaryStore:(BOOL) isPrimaryStore
|
||||
{
|
||||
int oldRemeber = [flycutOperator rememberNum];
|
||||
if ( newRemember < [flycutOperator jcListCount] &&
|
||||
! issuedRememberResizeWarning &&
|
||||
! [[NSUserDefaults standardUserDefaults] boolForKey:@"stifleRememberResizeWarning"]
|
||||
) {
|
||||
choice = NSRunAlertPanel(@"Resize Stack",
|
||||
int choice = NSRunAlertPanel(@"Resize Stack",
|
||||
@"Resizing the stack to a value below its present size will cause clippings to be lost.",
|
||||
@"Resize", @"Cancel", @"Don't Warn Me Again");
|
||||
if ( choice == NSAlertAlternateReturn ) {
|
||||
// User selected Cancel. This appears to set the user default to
|
||||
// the current clipping count while not updating the clippingStore,
|
||||
// resulting in truncation in the future. This condition dates back
|
||||
// to snarkout's original creation of setRememberNumPref on May 17,
|
||||
// 2006.
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:[flycutOperator jcListCount]]
|
||||
forKey:@"rememberNum"];
|
||||
[self updateMenu];
|
||||
return;
|
||||
// Cancel - Change to prior setting.
|
||||
newRemember = oldRemeber;
|
||||
if ( isPrimaryStore ) {
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:newRemember]
|
||||
forKey:@"rememberNum"];
|
||||
[self updateMenu];
|
||||
} else {
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:newRemember]
|
||||
forKey:@"favoritesRememberNum"];
|
||||
}
|
||||
} else if ( choice == NSAlertOtherReturn ) {
|
||||
// Don't Warn Me Again
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:YES]
|
||||
forKey:@"stifleRememberResizeWarning"];
|
||||
} else {
|
||||
// Resize
|
||||
issuedRememberResizeWarning = YES;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim down the number displayed in the menu if it is greater than the new
|
||||
// number to remember.
|
||||
if ( newRemember < [[NSUserDefaults standardUserDefaults] integerForKey:@"displayNum"] ) {
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:newRemember]
|
||||
forKey:@"displayNum"];
|
||||
if ( newRemember < oldRemeber )
|
||||
{
|
||||
// Trim down the number displayed in the menu if it is greater than the new
|
||||
// number to remember.
|
||||
if ( isPrimaryStore ) {
|
||||
if ( newRemember < [[NSUserDefaults standardUserDefaults] integerForKey:@"displayNum"] ) {
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:newRemember]
|
||||
forKey:@"displayNum"];
|
||||
[self updateMenu];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the value.
|
||||
[flycutOperator setRememberNum: newRemember];
|
||||
[self updateMenu];
|
||||
}
|
||||
|
||||
-(IBAction) setFavoritesRememberNumPref:(id)sender
|
||||
{
|
||||
[flycutOperator switchToFavoritesStore];
|
||||
[self setRememberNumPref: sender];
|
||||
[flycutOperator restoreStashedStore];
|
||||
[self checkFavoritesRememberNumPref:[sender intValue]];
|
||||
}
|
||||
|
||||
-(void) checkFavoritesRememberNumPref:(int)newRemember
|
||||
{
|
||||
[flycutOperator switchToFavoritesStore];
|
||||
[self checkRememberNumPref:newRemember forPrimaryStore:NO];
|
||||
[flycutOperator restoreStashedStore];
|
||||
}
|
||||
|
||||
-(IBAction) setDisplayNumPref:(id)sender
|
||||
|
|
@ -847,10 +917,33 @@
|
|||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)notification
|
||||
{
|
||||
// Enable notification from CloudKit
|
||||
[NSApp registerForRemoteNotificationTypes:NSRemoteNotificationTypeNone];// silent push notification!
|
||||
|
||||
//Create our hot key
|
||||
[self toggleMainHotKey:[NSNull null]];
|
||||
}
|
||||
|
||||
// Remote Notifications (APN, aka Push Notifications) are only available on apps distributed via the App Store.
|
||||
// To support building for both distribution channels, include the following two methods to detect if Remote Notifications are available and inform MJCloudKitUserDefaultsSync.
|
||||
- (void)application:(NSApplication *)application
|
||||
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
|
||||
// Forward the token to your provider, using a custom method.
|
||||
NSLog(@"Registered for remote notifications.");
|
||||
[MJCloudKitUserDefaultsSync setRemoteNotificationsEnabled:YES];
|
||||
}
|
||||
|
||||
- (void)application:(NSApplication *)application
|
||||
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
|
||||
NSLog(@"Remote notification support is unavailable due to error: %@", error);
|
||||
[MJCloudKitUserDefaultsSync setRemoteNotificationsEnabled:NO];
|
||||
}
|
||||
|
||||
- (void)application:(NSApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
|
||||
{
|
||||
[flycutOperator checkCloudKitUpdates];
|
||||
}
|
||||
|
||||
- (void) updateBezel
|
||||
{
|
||||
[flycutOperator adjustStackPositionIfOutOfBounds];
|
||||
|
|
@ -937,6 +1030,89 @@
|
|||
[[SGHotKeyCenter sharedCenter] registerHotKey:mainHotKey];
|
||||
}
|
||||
|
||||
- (IBAction)toggleICloudSyncSettings:(id)sender
|
||||
{
|
||||
if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"syncSettingsViaICloud"] ) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:@"Warning"];
|
||||
[alert addButtonWithTitle:@"Ok"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setInformativeText:@"Enabling iCloud Settings Sync will overwrite local settings if your iCloud account already has Flycut settings. If you have never enabled this in Flycut on any computer, your current settings will be retained and loaded into iCloud."];
|
||||
if ( [alert runModal] != NSAlertFirstButtonReturn )
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO]
|
||||
forKey:@"syncSettingsViaICloud"];
|
||||
}
|
||||
[alert release];
|
||||
// Add option to overwrite iCloud.
|
||||
}
|
||||
|
||||
[self registerOrDeregisterICloudSync];
|
||||
}
|
||||
|
||||
- (IBAction)toggleICloudSyncClippings:(id)sender
|
||||
{
|
||||
if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"syncClippingsViaICloud"] ) {
|
||||
if ( [[NSUserDefaults standardUserDefaults] integerForKey:@"savePreference"] < 2 ) {
|
||||
// Must set syncClippingsViaICloud = 2
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:@"Settings Change"];
|
||||
[alert addButtonWithTitle:@"Ok"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setInformativeText:@"iCloud Clippings Sync will set 'Save: After each clip'."];
|
||||
if ( [alert runModal] == NSAlertFirstButtonReturn )
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:2]
|
||||
forKey:@"savePreference"];
|
||||
} else {
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO]
|
||||
forKey:@"syncClippingsViaICloud"];
|
||||
}
|
||||
[alert release];
|
||||
}
|
||||
}
|
||||
|
||||
if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"syncClippingsViaICloud"] ) {
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:@"Warning"];
|
||||
[alert addButtonWithTitle:@"Ok"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setInformativeText:@"Enabling iCloud Clippings Sync will overwrite local clippings if your iCloud account already has Flycut clippings. If you have never enabled this in Flycut on any computer, your current clippings will be retained and loaded into iCloud."];
|
||||
if ( [alert runModal] != NSAlertFirstButtonReturn )
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO]
|
||||
forKey:@"syncClippingsViaICloud"];
|
||||
}
|
||||
// Add option to overwrite iCloud.
|
||||
}
|
||||
|
||||
[self registerOrDeregisterICloudSync];
|
||||
}
|
||||
|
||||
- (IBAction)setSavePreference:(id)sender
|
||||
{
|
||||
if ( [[NSUserDefaults standardUserDefaults] integerForKey:@"savePreference"] < 2 ) {
|
||||
if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"syncClippingsViaICloud"] ) {
|
||||
// Must disable syncClippingsViaICloud
|
||||
NSAlert *alert = [[NSAlert alloc] init];
|
||||
[alert setMessageText:@"Settings Change"];
|
||||
[alert addButtonWithTitle:@"Ok"];
|
||||
[alert addButtonWithTitle:@"Cancel"];
|
||||
[alert setInformativeText:@"Disabling 'Save: After each clip' will disable iCloud Clippings Sync."];
|
||||
|
||||
if ( [alert runModal] == NSAlertFirstButtonReturn )
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:NO]];
|
||||
}
|
||||
else
|
||||
{
|
||||
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithInt:2]];
|
||||
}
|
||||
[alert release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-(IBAction)clearClippingList:(id)sender {
|
||||
int choice;
|
||||
|
||||
|
|
|
|||
137
English.lproj/MainMenu.nib/designable.nib
generated
137
English.lproj/MainMenu.nib/designable.nib
generated
|
|
@ -1,5 +1,5 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="16B2555" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" systemVersion="15G1510" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
|
||||
<dependencies>
|
||||
<deployment version="1050" identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
|
||||
|
|
@ -142,8 +142,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.stickyBezel" id="811"/>
|
||||
</connections>
|
||||
</button>
|
||||
<popUpButton verticalHuggingPriority="750" id="392">
|
||||
<rect key="frame" x="79" y="199" width="189" height="26"/>
|
||||
<popUpButton verticalHuggingPriority="750" misplaced="YES" id="392">
|
||||
<rect key="frame" x="79" y="179" width="189" height="26"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<popUpButtonCell key="cell" type="push" title="On exit" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" inset="2" arrowPosition="arrowAtCenter" preferredEdge="maxY" selectedItem="394" id="751">
|
||||
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
|
||||
|
|
@ -157,11 +157,12 @@
|
|||
</menu>
|
||||
</popUpButtonCell>
|
||||
<connections>
|
||||
<action selector="setSavePreference:" target="213" id="Tad-Qs-Oft"/>
|
||||
<binding destination="808" name="selectedIndex" keyPath="values.savePreference" id="823"/>
|
||||
</connections>
|
||||
</popUpButton>
|
||||
<button id="398">
|
||||
<rect key="frame" x="14" y="258" width="204" height="18"/>
|
||||
<button misplaced="YES" id="398">
|
||||
<rect key="frame" x="14" y="259" width="162" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<buttonCell key="cell" type="check" title="Launch Flycut on login" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="753">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
|
@ -172,8 +173,20 @@
|
|||
<binding destination="808" name="value" keyPath="values.loadOnStartup" id="820"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button id="897">
|
||||
<rect key="frame" x="14" y="143" width="247" height="18"/>
|
||||
<button misplaced="YES" id="IXN-q7-y5J">
|
||||
<rect key="frame" x="193" y="239" width="79" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<buttonCell key="cell" type="check" title="Clippings" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="oLL-dD-GRW">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="toggleICloudSyncClippings:" target="213" id="hdD-v4-8dV"/>
|
||||
<binding destination="808" name="value" keyPath="values.syncClippingsViaICloud" id="gbJ-fW-npv"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button misplaced="YES" id="897">
|
||||
<rect key="frame" x="14" y="123" width="247" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<buttonCell key="cell" type="check" title="Don't copy from password fields" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="898">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
|
@ -183,8 +196,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.skipPasswordFields" id="902"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button id="903">
|
||||
<rect key="frame" x="14" y="61" width="247" height="18"/>
|
||||
<button misplaced="YES" id="903">
|
||||
<rect key="frame" x="14" y="41" width="247" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<buttonCell key="cell" type="check" title="Remove duplicates" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="904">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
|
@ -194,8 +207,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.removeDuplicates" id="906"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" id="416">
|
||||
<rect key="frame" x="13" y="227" width="76" height="23"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="416">
|
||||
<rect key="frame" x="13" y="207" width="76" height="23"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="754">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
|
@ -205,8 +218,17 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" id="418">
|
||||
<rect key="frame" x="13" y="205" width="33" height="14"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="oXn-B4-1Ni">
|
||||
<rect key="frame" x="32" y="223" width="116" height="34"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="iCloud Sync:" id="17g-S0-Mqp">
|
||||
<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>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="418">
|
||||
<rect key="frame" x="13" y="185" width="33" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="755">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
|
@ -216,8 +238,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" id="419">
|
||||
<rect key="frame" x="13" y="179" width="62" height="14"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="419">
|
||||
<rect key="frame" x="13" y="159" width="62" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="756">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
|
@ -227,8 +249,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" id="420">
|
||||
<rect key="frame" x="134" y="179" width="95" height="14"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="420">
|
||||
<rect key="frame" x="134" y="159" width="95" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Display in menu" id="757">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
|
@ -236,8 +258,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" id="470">
|
||||
<rect key="frame" x="79" y="174" width="25" height="22"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="470">
|
||||
<rect key="frame" x="79" y="154" width="25" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" state="on" id="758">
|
||||
<numberFormatter key="formatter" formatterBehavior="10_0" positiveFormat="#" negativeFormat="-#" hasThousandSeparators="NO" thousandSeparator=" " id="483">
|
||||
|
|
@ -255,8 +277,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.rememberNum" id="826"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" id="471">
|
||||
<rect key="frame" x="224" y="174" width="24" height="22"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="471">
|
||||
<rect key="frame" x="224" y="154" width="24" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" state="on" id="759">
|
||||
<numberFormatter key="formatter" formatterBehavior="10_0" positiveFormat="#" negativeFormat="-#" hasThousandSeparators="NO" thousandSeparator=" " id="484">
|
||||
|
|
@ -274,8 +296,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.displayNum" id="832"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<stepper toolTip="Controls how many clippings Flycut stores in its stack" horizontalHuggingPriority="750" verticalHuggingPriority="750" id="472">
|
||||
<rect key="frame" x="104" y="172" width="19" height="27"/>
|
||||
<stepper toolTip="Controls how many clippings Flycut stores in its stack" horizontalHuggingPriority="750" verticalHuggingPriority="750" misplaced="YES" id="472">
|
||||
<rect key="frame" x="104" y="152" width="19" height="27"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<stepperCell key="cell" continuous="YES" alignment="left" minValue="10" maxValue="99" doubleValue="10" valueWraps="YES" id="760"/>
|
||||
<connections>
|
||||
|
|
@ -283,8 +305,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.rememberNum" id="829"/>
|
||||
</connections>
|
||||
</stepper>
|
||||
<stepper toolTip="Controls how many clippings Flycut shows in the menu" horizontalHuggingPriority="750" verticalHuggingPriority="750" id="473">
|
||||
<rect key="frame" x="249" y="172" width="19" height="27"/>
|
||||
<stepper toolTip="Controls how many clippings Flycut shows in the menu" horizontalHuggingPriority="750" verticalHuggingPriority="750" misplaced="YES" id="473">
|
||||
<rect key="frame" x="249" y="152" width="19" height="27"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<stepperCell key="cell" continuous="YES" alignment="left" minValue="5" maxValue="99" doubleValue="5" valueWraps="YES" id="761"/>
|
||||
<connections>
|
||||
|
|
@ -315,8 +337,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.menuSelectionPastes" id="817"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button id="914">
|
||||
<rect key="frame" x="14" y="41" width="230" height="18"/>
|
||||
<button misplaced="YES" id="914">
|
||||
<rect key="frame" x="14" y="21" width="230" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Move pasted item to top of stack" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="915">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
|
@ -326,8 +348,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.pasteMovesToTop" id="922"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button id="LLh-vf-2Q3">
|
||||
<rect key="frame" x="246" y="21" width="87" height="18"/>
|
||||
<button misplaced="YES" id="LLh-vf-2Q3">
|
||||
<rect key="frame" x="246" y="1" width="87" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Clippings" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="oLu-XL-Wuo">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
|
@ -337,8 +359,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.saveForgottenClippings" id="9eO-xd-ge5"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" id="Ubt-DS-zHf">
|
||||
<rect key="frame" x="280" y="179" width="62" height="14"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="Ubt-DS-zHf">
|
||||
<rect key="frame" x="280" y="159" width="62" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Favorites" id="2L6-OM-5rx">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
|
|
@ -346,8 +368,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" id="dM5-eN-Zvt">
|
||||
<rect key="frame" x="346" y="174" width="25" height="22"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="dM5-eN-Zvt">
|
||||
<rect key="frame" x="346" y="154" width="25" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" state="on" id="nes-6O-e5R">
|
||||
<numberFormatter key="formatter" formatterBehavior="10_0" positiveFormat="#" negativeFormat="-#" hasThousandSeparators="NO" thousandSeparator="," id="aqt-p2-eya">
|
||||
|
|
@ -365,8 +387,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.rememberNum" id="887-jS-Kpn"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<stepper toolTip="Controls how many clippings Flycut stores in its stack" horizontalHuggingPriority="750" verticalHuggingPriority="750" id="wZv-HJ-VdW">
|
||||
<rect key="frame" x="371" y="172" width="19" height="27"/>
|
||||
<stepper toolTip="Controls how many clippings Flycut stores in its stack" horizontalHuggingPriority="750" verticalHuggingPriority="750" misplaced="YES" id="wZv-HJ-VdW">
|
||||
<rect key="frame" x="371" y="152" width="19" height="27"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<stepperCell key="cell" continuous="YES" alignment="left" minValue="10" maxValue="99" doubleValue="10" valueWraps="YES" id="gUi-gl-RH6"/>
|
||||
<connections>
|
||||
|
|
@ -374,8 +396,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.rememberNum" id="nUm-81-DgP"/>
|
||||
</connections>
|
||||
</stepper>
|
||||
<button id="pDS-jE-9jb">
|
||||
<rect key="frame" x="32" y="123" width="133" height="18"/>
|
||||
<button misplaced="YES" id="pDS-jE-9jb">
|
||||
<rect key="frame" x="32" y="103" width="133" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Pasteboard types:" bezelStyle="regularSquare" imagePosition="left" inset="2" id="tzh-NM-RSb">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
|
@ -385,8 +407,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.skipPboardTypes" id="kfD-fG-t1V"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button id="Bxp-pI-XMk">
|
||||
<rect key="frame" x="32" y="102" width="356" height="18"/>
|
||||
<button misplaced="YES" id="Bxp-pI-XMk">
|
||||
<rect key="frame" x="32" y="82" width="356" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Detect with Upper, Lower, Digit, and Symbol lengths:" bezelStyle="regularSquare" imagePosition="left" inset="2" id="aAK-L0-EHa">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
|
@ -396,8 +418,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.skipPasswordLengths" id="PyJ-gO-ieM"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button toolTip="For identifying types to include in Pasteboard Types above." id="mka-NZ-Esg">
|
||||
<rect key="frame" x="32" y="82" width="279" height="18"/>
|
||||
<button toolTip="For identifying types to include in Pasteboard Types above." misplaced="YES" id="mka-NZ-Esg">
|
||||
<rect key="frame" x="32" y="62" width="279" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Include pasteboard types in clippings list" bezelStyle="regularSquare" imagePosition="left" inset="2" id="9Vi-RY-G0b">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
|
@ -407,8 +429,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.revealPasteboardTypes" id="Nn3-7D-D7e"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField verticalHuggingPriority="750" id="RYj-wx-tTx">
|
||||
<rect key="frame" x="394" y="100" width="108" height="22"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="RYj-wx-tTx">
|
||||
<rect key="frame" x="394" y="80" width="108" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" title="12, 20, 32" drawsBackground="YES" id="aqQ-SR-BmT">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
|
@ -419,8 +441,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.skipPasswordLengthsList" id="NJg-wN-gsn"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" id="x34-dK-wxl">
|
||||
<rect key="frame" x="171" y="121" width="331" height="22"/>
|
||||
<textField verticalHuggingPriority="750" misplaced="YES" id="x34-dK-wxl">
|
||||
<rect key="frame" x="171" y="101" width="331" height="22"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" title="PasswordPboardType" drawsBackground="YES" id="kTZ-dC-s1q">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
|
@ -431,8 +453,8 @@
|
|||
<binding destination="808" name="value" keyPath="values.skipPboardTypesList" id="Wbn-u9-3vY"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" id="FNU-zj-OIm">
|
||||
<rect key="frame" x="32" y="22" width="211" height="17"/>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" misplaced="YES" id="FNU-zj-OIm">
|
||||
<rect key="frame" x="32" y="2" width="211" height="17"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Save forgotten items to Desktop:" id="ksy-al-FbE">
|
||||
<font key="font" metaFont="system"/>
|
||||
|
|
@ -440,8 +462,8 @@
|
|||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<button id="cGj-g9-2Dm">
|
||||
<rect key="frame" x="337" y="21" width="157" height="18"/>
|
||||
<button misplaced="YES" id="cGj-g9-2Dm">
|
||||
<rect key="frame" x="337" y="1" width="157" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<buttonCell key="cell" type="check" title="Favorites" bezelStyle="regularSquare" imagePosition="left" alignment="left" state="on" inset="2" id="vC2-Ch-UPQ">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
|
|
@ -451,6 +473,18 @@
|
|||
<binding destination="808" name="value" keyPath="values.saveForgottenFavorites" id="WTf-Ip-R9L"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button misplaced="YES" id="bqG-v4-vqw">
|
||||
<rect key="frame" x="116" y="239" width="73" height="18"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<buttonCell key="cell" type="check" title="Settings" bezelStyle="regularSquare" imagePosition="left" alignment="left" inset="2" id="d4T-QV-zqa">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="toggleICloudSyncSettings:" target="213" id="lWN-Hi-1aI"/>
|
||||
<binding destination="808" name="value" keyPath="values.syncSettingsViaICloud" id="q7Y-Id-atI"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
</view>
|
||||
</box>
|
||||
|
|
@ -536,7 +570,7 @@
|
|||
<autoresizingMask key="autoresizingMask"/>
|
||||
<clipView key="contentView" id="Yuw-af-7PF">
|
||||
<rect key="frame" x="1" y="1" width="474" height="344"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView editable="NO" importsGraphics="NO" richText="NO" usesFontPanel="YES" usesRuler="YES" spellingCorrection="YES" smartInsertDelete="YES" id="357">
|
||||
<rect key="frame" x="0.0" y="0.0" width="474" height="344"/>
|
||||
|
|
@ -613,6 +647,7 @@ Thanks to Clare Bates Congdon, Joshua Davis, Brad Graham, David Jacobs, John Ken
|
|||
</tabView>
|
||||
</subviews>
|
||||
</view>
|
||||
<point key="canvasLocation" x="138.5" y="551.5"/>
|
||||
</window>
|
||||
<userDefaultsController representsSharedInstance="YES" id="264" userLabel="Shared Defaults"/>
|
||||
<customObject id="503" userLabel="UKPrefsPanel" customClass="UKPrefsPanel">
|
||||
|
|
|
|||
BIN
English.lproj/MainMenu.nib/keyedobjects.nib
generated
BIN
English.lproj/MainMenu.nib/keyedobjects.nib
generated
Binary file not shown.
|
|
@ -7,9 +7,10 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import UserNotifications
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
|
@ -22,9 +23,39 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
appDelegate.myViewController = viewController
|
||||
|
||||
let center = UNUserNotificationCenter.current()
|
||||
center.requestAuthorization(options:[]) { (granted, error) in
|
||||
// Enable or disable features based on authorization.
|
||||
}
|
||||
application.registerForRemoteNotifications()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Called by push notifications when a CloudKit change occurs.
|
||||
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||
// Notification was generated by CloudKit changes, so check for updates.
|
||||
MJCloudKitUserDefaultsSync.checkCloudKitUpdates()
|
||||
// Signal that we have completed our response to notification, with
|
||||
// parameter assuming that we received new data.
|
||||
completionHandler(.newData)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||
|
||||
let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)})
|
||||
print(deviceTokenString)
|
||||
|
||||
MJCloudKitUserDefaultsSync.setRemoteNotificationsEnabled(true)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
||||
|
||||
print("i am not available in simulator \(error)")
|
||||
MJCloudKitUserDefaultsSync.setRemoteNotificationsEnabled(false)
|
||||
|
||||
}
|
||||
|
||||
func applicationWillResignActive(_ application: UIApplication) {
|
||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||
|
|
|
|||
|
|
@ -22,6 +22,10 @@
|
|||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>remote-notification</string>
|
||||
</array>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,15 @@
|
|||
|
||||
import UIKit
|
||||
|
||||
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
|
||||
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, FlycutStoreDelegate {
|
||||
|
||||
let flycut:FlycutOperator = FlycutOperator()
|
||||
var adjustQuantity:Int = 0
|
||||
var activeUpdates:Int = 0
|
||||
var tableView:UITableView!
|
||||
var currentAnimation = UITableViewRowAnimation.none
|
||||
var pbCount:Int = -1
|
||||
|
||||
let pasteboardInteractionQueue = DispatchQueue(label: "com.Flycut.pasteboardInteractionQueue")
|
||||
|
||||
// Some buttons we will reuse.
|
||||
var deleteButton:MGSwipeButton? = nil
|
||||
|
|
@ -31,11 +35,11 @@ class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSour
|
|||
deleteButton = MGSwipeButton(title: "Delete", backgroundColor: .red, callback: { (cell) -> Bool in
|
||||
let indexPath = self.tableView.indexPath(for: cell)
|
||||
if ( nil != indexPath ) {
|
||||
let previousAnimation = self.currentAnimation
|
||||
self.currentAnimation = UITableViewRowAnimation.left // Use .left to look better with swiping left to delete.
|
||||
self.flycut.setStackPositionTo( Int32((indexPath?.row)! ))
|
||||
self.flycut.clearItemAtStackPosition()
|
||||
self.tableView.beginUpdates()
|
||||
self.tableView.deleteRows(at: [indexPath!], with: .left) // Use .left to look better with swiping left to delete.
|
||||
self.tableView.endUpdates()
|
||||
self.currentAnimation = previousAnimation
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -52,6 +56,12 @@ class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSour
|
|||
return true;
|
||||
})
|
||||
|
||||
// Enable sync by default on iOS until we have a mechanism to adjust preferences on-device.
|
||||
UserDefaults.standard.set(NSNumber(value: true), forKey: "syncSettingsViaICloud")
|
||||
UserDefaults.standard.set(NSNumber(value: true), forKey: "syncClippingsViaICloud")
|
||||
|
||||
flycut.setClippingsStoreDelegate(self);
|
||||
|
||||
flycut.awake(fromNibDisplaying: 10, withDisplayLength: 140, withSave: #selector(savePreferences(toDict:)), forTarget: self) // The 10 isn't used in iOS right now and 140 characters seems to be enough to cover the width of the largest screen.
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.checkForClippingAddedToClipboard), name: .UIPasteboardChanged, object: nil)
|
||||
|
|
@ -63,39 +73,91 @@ class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSour
|
|||
{
|
||||
}
|
||||
|
||||
func beginUpdates()
|
||||
{
|
||||
if ( !Thread.isMainThread )
|
||||
{
|
||||
DispatchQueue.main.sync { beginUpdates() }
|
||||
return
|
||||
}
|
||||
|
||||
print("Begin updates")
|
||||
print("Num rows: \(tableView.dataSource?.tableView(tableView, numberOfRowsInSection: 0))")
|
||||
if ( 0 == activeUpdates )
|
||||
{
|
||||
tableView.beginUpdates()
|
||||
}
|
||||
activeUpdates += 1
|
||||
}
|
||||
|
||||
func endUpdates()
|
||||
{
|
||||
if ( !Thread.isMainThread )
|
||||
{
|
||||
DispatchQueue.main.sync { endUpdates() }
|
||||
return
|
||||
}
|
||||
|
||||
print("End updates");
|
||||
activeUpdates -= 1;
|
||||
if ( 0 == activeUpdates )
|
||||
{
|
||||
tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
func insertClipping(at index: Int32) {
|
||||
if ( !Thread.isMainThread )
|
||||
{
|
||||
DispatchQueue.main.sync { insertClipping(at: index) }
|
||||
return
|
||||
}
|
||||
print("Insert row \(index)")
|
||||
tableView.insertRows(at: [IndexPath(row: Int(index), section: 0)], with: currentAnimation) // We will override the animation for now, because we are the ViewController and should guide the UX.
|
||||
}
|
||||
|
||||
func deleteClipping(at index: Int32) {
|
||||
if ( !Thread.isMainThread )
|
||||
{
|
||||
DispatchQueue.main.sync { deleteClipping(at: index) }
|
||||
return
|
||||
}
|
||||
print("Delete row \(index)")
|
||||
tableView.deleteRows(at: [IndexPath(row: Int(index), section: 0)], with: currentAnimation) // We will override the animation for now, because we are the ViewController and should guide the UX.
|
||||
}
|
||||
|
||||
func reloadClipping(at index: Int32) {
|
||||
if ( !Thread.isMainThread )
|
||||
{
|
||||
DispatchQueue.main.sync { reloadClipping(at: index) }
|
||||
return
|
||||
}
|
||||
print("Reloading row \(index)")
|
||||
tableView.reloadRows(at: [IndexPath(row: Int(index), section: 0)], with: currentAnimation) // We will override the animation for now, because we are the ViewController and should guide the UX.
|
||||
}
|
||||
|
||||
func moveClipping(at index: Int32, to newIndex: Int32) {
|
||||
if ( !Thread.isMainThread )
|
||||
{
|
||||
DispatchQueue.main.sync { moveClipping(at: index, to: newIndex) }
|
||||
return
|
||||
}
|
||||
print("Moving row \(index) to \(newIndex)")
|
||||
tableView.moveRow(at: IndexPath(row: Int(index), section: 0), to: IndexPath(row: Int(newIndex), section: 0))
|
||||
}
|
||||
|
||||
func checkForClippingAddedToClipboard()
|
||||
{
|
||||
let pasteboard = UIPasteboard.general.string
|
||||
if ( nil != pasteboard )
|
||||
{
|
||||
|
||||
let startCount = Int(flycut.jcListCount())
|
||||
let previousIndex = flycut.index(ofClipping: pasteboard, ofType: "public.utf8-plain-text", fromApp: "iOS", withAppBundleURL: "iOS")
|
||||
let added = flycut.addClipping(pasteboard, ofType: "public.utf8-plain-text", fromApp: "iOS", withAppBundleURL: "iOS", target: nil, clippingAddedSelector: nil)
|
||||
|
||||
if ( added )
|
||||
pasteboardInteractionQueue.async {
|
||||
if ( UIPasteboard.general.changeCount != self.pbCount )
|
||||
{
|
||||
var reAdjustQuantity = 0
|
||||
var deleteIndex = -1
|
||||
if( -1 < previousIndex )
|
||||
{
|
||||
deleteIndex = Int(previousIndex)
|
||||
}
|
||||
else if(startCount == Int(flycut.jcListCount()))
|
||||
{
|
||||
deleteIndex = startCount - 1
|
||||
}
|
||||
self.pbCount = UIPasteboard.general.changeCount;
|
||||
|
||||
tableView.beginUpdates()
|
||||
if ( deleteIndex >= 0 )
|
||||
if ( UIPasteboard.general.types.contains("public.utf8-plain-text") )
|
||||
{
|
||||
adjustQuantity -= 1
|
||||
reAdjustQuantity = 1
|
||||
tableView.deleteRows(at: [IndexPath(row: deleteIndex, section: 0)], with: .none)
|
||||
let pasteboard = UIPasteboard.general.value(forPasteboardType: "public.utf8-plain-text")
|
||||
self.flycut.addClipping(pasteboard as! String!, ofType: "public.utf8-plain-text", fromApp: "iOS", withAppBundleURL: "iOS", target: nil, clippingAddedSelector: nil)
|
||||
}
|
||||
adjustQuantity += reAdjustQuantity
|
||||
tableView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .none)
|
||||
tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +183,7 @@ class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSour
|
|||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return Int(flycut.jcListCount()) + adjustQuantity
|
||||
return Int(flycut.jcListCount())
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
|
@ -162,10 +224,27 @@ class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSour
|
|||
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if ( MGSwipeState.none == (tableView.cellForRow(at: indexPath) as! MGSwipeTableCell).swipeState ) {
|
||||
let content = flycut.clippingString(withCount: Int32(indexPath.row) )
|
||||
tableView.deselectRow(at: indexPath, animated: true) // deselect before getPaste since getPaste may reorder the list
|
||||
let content = flycut.getPasteFrom(Int32(indexPath.row))
|
||||
print("Select: \(indexPath.row) \(content) OK")
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
UIPasteboard.general.string = content
|
||||
|
||||
pasteboardInteractionQueue.async {
|
||||
// Capture value before setting the pastboard for reasons noted below.
|
||||
self.pbCount = UIPasteboard.general.changeCount
|
||||
|
||||
// This call will clear all other content types and appears to immediately increment the changeCount.
|
||||
UIPasteboard.general.setValue(content as Any, forPasteboardType: "public.utf8-plain-text")
|
||||
|
||||
// Apple documents that "UIPasteboard waits until the end of the current event loop before incrementing the change count", but this doesn't seem to be the case for the above call. Handle both scenarios by doing a simple increment if unchanged and an update-to-match if changed.
|
||||
if ( UIPasteboard.general.changeCount == self.pbCount )
|
||||
{
|
||||
self.pbCount += 1
|
||||
}
|
||||
else
|
||||
{
|
||||
self.pbCount = UIPasteboard.general.changeCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.developer.aps-environment</key>
|
||||
<string>development</string>
|
||||
<key>com.apple.developer.icloud-container-identifiers</key>
|
||||
<array>
|
||||
<string>iCloud.com.mark-a-jerde.Flycut</string>
|
||||
|
|
|
|||
|
|
@ -224,6 +224,7 @@
|
|||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
8D61FD581F7961D200BD4B3D /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/UserNotifications.framework; sourceTree = DEVELOPER_DIR; };
|
||||
8D84B6361F72FBA900DB58F9 /* Flycut.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Flycut.entitlements; sourceTree = "<group>"; };
|
||||
8D84B63A1F7412E600DB58F9 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS10.2.sdk/System/Library/Frameworks/CloudKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
8DC9C99B1F742115003BE8B5 /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; };
|
||||
|
|
@ -378,6 +379,7 @@
|
|||
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
8D61FD581F7961D200BD4B3D /* UserNotifications.framework */,
|
||||
8DC9C99B1F742115003BE8B5 /* CloudKit.framework */,
|
||||
8D84B63A1F7412E600DB58F9 /* CloudKit.framework */,
|
||||
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
|
||||
|
|
@ -652,6 +654,9 @@
|
|||
DevelopmentTeam = 66X95R6W2D;
|
||||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.BackgroundModes = {
|
||||
enabled = 1;
|
||||
};
|
||||
com.apple.Push = {
|
||||
enabled = 1;
|
||||
};
|
||||
|
|
@ -675,7 +680,7 @@
|
|||
ProvisioningStyle = Automatic;
|
||||
SystemCapabilities = {
|
||||
com.apple.Push = {
|
||||
enabled = 1;
|
||||
enabled = 0;
|
||||
};
|
||||
com.apple.Sandbox = {
|
||||
enabled = 0;
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@
|
|||
|
||||
-(void) setDisplayLength:(int)newDisplayLength
|
||||
{
|
||||
if ( newDisplayLength > 0 ) {
|
||||
if ( newDisplayLength > 0 && clipDisplayLength != newDisplayLength ) {
|
||||
clipDisplayLength = newDisplayLength;
|
||||
[self resetDisplayString];
|
||||
}
|
||||
|
|
@ -222,9 +222,7 @@
|
|||
if (!other || ![other isKindOfClass:[self class]])
|
||||
return NO;
|
||||
FlycutClipping * otherClip = (FlycutClipping *)other;
|
||||
return ([self.type isEqualToString:otherClip.type] &&
|
||||
[self.displayString isEqualToString:otherClip.displayString] &&
|
||||
(self.displayLength == otherClip.displayLength) &&
|
||||
return (/*[self.type isEqualToString:otherClip.type] &&*/ // Type is under-utilized a this time and will mismatch on cross-device (macOS <-> iOS) usage. This should be revisited once we have support for more than just raw text clippings.
|
||||
[self.contents isEqualToString:otherClip.contents]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,18 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
#import "FlycutClipping.h"
|
||||
|
||||
@protocol FlycutStoreDelegate <NSObject>
|
||||
@optional
|
||||
- (void)beginUpdates; // allow multiple insert/delete of rows and sections to be animated simultaneously. Nestable
|
||||
|
||||
- (void)endUpdates; // only call insert/delete/reload calls or change the editing state inside an update block. otherwise things like row count, etc. may be invalid.
|
||||
|
||||
- (void)insertClippingAtIndex:(int)index;
|
||||
- (void)deleteClippingAtIndex:(int)index;
|
||||
- (void)reloadClippingAtIndex:(int)index;
|
||||
- (void)moveClippingAtIndex:(int)index toIndex:(int)newIndex;
|
||||
@end
|
||||
|
||||
@interface FlycutStore : NSObject {
|
||||
|
||||
// Our various listener-related preferences
|
||||
|
|
@ -50,11 +62,14 @@
|
|||
-(NSArray *) previousDisplayStrings:(int)howMany;
|
||||
-(NSArray *) previousDisplayStrings:(int)howMany containing:(NSString*)search;
|
||||
-(NSArray *) previousIndexes:(int)howMany containing:(NSString*)search; // This method is in newest-first order.
|
||||
-(int) indexOfClipping:(FlycutClipping*) clipping;
|
||||
-(int) indexOfClipping:(NSString *)clipping ofType:(NSString *)type fromAppLocalizedName:(NSString *)appLocalizedName fromAppBundleURL:(NSString *)bundleURL atTimestamp:(int) timestamp;
|
||||
-(bool) removeDuplicates;
|
||||
|
||||
// Add a clipping
|
||||
-(bool) addClipping:(NSString *)clipping ofType:(NSString *)type fromAppLocalizedName:(NSString *)appLocalizedName fromAppBundleURL:(NSString *)bundleURL atTimestamp:(NSInteger) timestamp;
|
||||
-(void) addClipping:(FlycutClipping*) clipping;
|
||||
-(void) insertClipping:(FlycutClipping*) clipping atIndex:(int) index;
|
||||
|
||||
// Delete a clipping
|
||||
-(void) clearItem:(int)index;
|
||||
|
|
@ -67,6 +82,10 @@
|
|||
|
||||
// Move the clipping at index to the top
|
||||
-(void) clippingMoveToTop:(int)index;
|
||||
-(void) clippingMoveFrom:(int)index To:(int)toIndex;
|
||||
|
||||
/** optional delegate (not retained) */
|
||||
@property (nonatomic, nullable, assign) id<FlycutStoreDelegate> delegate;
|
||||
|
||||
// Delete all named clippings
|
||||
@end
|
||||
|
|
|
|||
|
|
@ -56,10 +56,16 @@
|
|||
}
|
||||
|
||||
-(int) indexOfClipping:(FlycutClipping*) clipping{
|
||||
if (![jcList containsObject:clipping]) {
|
||||
return [self indexOfClipping:clipping afterIndex:-1];
|
||||
}
|
||||
|
||||
-(int) indexOfClipping:(FlycutClipping*) clipping afterIndex:(int) after{
|
||||
NSUInteger index = [jcList indexOfObject:clipping
|
||||
inRange:NSMakeRange(after + 1, [jcList count] - (after + 1) )];
|
||||
if ( NSNotFound == index ) {
|
||||
return -1;
|
||||
}
|
||||
return (int)[jcList indexOfObject:clipping];
|
||||
return (int)index;
|
||||
}
|
||||
|
||||
// Add a clipping
|
||||
|
|
@ -83,16 +89,45 @@
|
|||
return YES;
|
||||
}
|
||||
|
||||
-(bool) removeDuplicates{
|
||||
return [[[NSUserDefaults standardUserDefaults] valueForKey:@"removeDuplicates"] boolValue];
|
||||
}
|
||||
|
||||
-(void) addClipping:(FlycutClipping*) clipping{
|
||||
if ([jcList containsObject:clipping] && [[[NSUserDefaults standardUserDefaults] valueForKey:@"removeDuplicates"] boolValue]) {
|
||||
[self insertClipping:clipping atIndex:0];
|
||||
}
|
||||
|
||||
-(void) insertClipping:(FlycutClipping*) clipping atIndex:(int) index{
|
||||
[self delegateBeginUpdates];
|
||||
|
||||
int moveFromIndex = -1;
|
||||
if ([jcList containsObject:clipping] && [self removeDuplicates]) {
|
||||
moveFromIndex = (int)[jcList indexOfObject:clipping];
|
||||
[jcList removeObject:clipping];
|
||||
}
|
||||
|
||||
// Push it onto our recent clippings stack
|
||||
[jcList insertObject:clipping atIndex:0];
|
||||
if ( index < [jcList count] ) {
|
||||
[jcList insertObject:clipping atIndex:index];
|
||||
}
|
||||
else {
|
||||
// If the index is beyond the current count then just append it and disregard requested index.
|
||||
// This doesn't alter the remember number and the jcList is self-growing so it is fine to append.
|
||||
index = [jcList count];
|
||||
[jcList addObject:clipping];
|
||||
}
|
||||
if ( moveFromIndex >= 0 )
|
||||
[self delegateMoveClippingAtIndex:moveFromIndex toIndex:index];
|
||||
else
|
||||
[self delegateInsertClippingAtIndex:index];
|
||||
|
||||
// Delete clippings older than jcRememberNum
|
||||
while ( [jcList count] > jcRememberNum ) {
|
||||
[jcList removeObjectAtIndex:jcRememberNum];
|
||||
[self delegateDeleteClippingAtIndex:(jcRememberNum-1)]; // -1 for before-add indexing
|
||||
}
|
||||
|
||||
[self delegateEndUpdates];
|
||||
}
|
||||
|
||||
-(void) addClipping:(NSString *)clipping ofType:(NSString *)type withPBCount:(int *)pbCount
|
||||
|
|
@ -102,10 +137,17 @@
|
|||
|
||||
// Clear remembered and listed
|
||||
-(void) clearList {
|
||||
[self delegateBeginUpdates];
|
||||
|
||||
for ( int i = (int)[jcList count] ; i > 0 ; i-- )
|
||||
[self delegateDeleteClippingAtIndex:(i-1)];
|
||||
|
||||
NSMutableArray *emptyJCList;
|
||||
emptyJCList = [[NSMutableArray alloc] init];
|
||||
[jcList release];
|
||||
jcList = emptyJCList;
|
||||
|
||||
[self delegateEndUpdates];
|
||||
}
|
||||
|
||||
-(void) mergeList {
|
||||
|
|
@ -115,14 +157,29 @@
|
|||
|
||||
-(void) clearItem:(int)index
|
||||
{
|
||||
[self delegateBeginUpdates];
|
||||
|
||||
[jcList removeObjectAtIndex:index];
|
||||
[self delegateDeleteClippingAtIndex:index];
|
||||
|
||||
[self delegateEndUpdates];
|
||||
}
|
||||
|
||||
-(void) clippingMoveToTop:(int)index
|
||||
{
|
||||
[self clippingMoveFrom:index To:0];
|
||||
}
|
||||
|
||||
-(void) clippingMoveFrom:(int)index To:(int)toIndex
|
||||
{
|
||||
[self delegateBeginUpdates];
|
||||
|
||||
FlycutClipping *clipping = [jcList objectAtIndex:index];
|
||||
[jcList insertObject:clipping atIndex:0];
|
||||
[jcList insertObject:clipping atIndex:toIndex];
|
||||
[jcList removeObjectAtIndex:index+1];
|
||||
[self delegateMoveClippingAtIndex:index toIndex:toIndex];
|
||||
|
||||
[self delegateEndUpdates];
|
||||
}
|
||||
|
||||
// Set various values
|
||||
|
|
@ -130,8 +187,15 @@
|
|||
{
|
||||
if ( nowRemembering > 0 ) {
|
||||
jcRememberNum = nowRemembering;
|
||||
while ( [jcList count] > jcRememberNum ) {
|
||||
[jcList removeObjectAtIndex:jcRememberNum];
|
||||
|
||||
if ( [jcList count] > jcRememberNum ) {
|
||||
[self delegateBeginUpdates];
|
||||
|
||||
while ( [jcList count] > jcRememberNum ) {
|
||||
[jcList removeObjectAtIndex:jcRememberNum];
|
||||
[self delegateDeleteClippingAtIndex:jcRememberNum];
|
||||
}
|
||||
[self delegateEndUpdates];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -295,6 +359,42 @@
|
|||
return returnArray;
|
||||
}
|
||||
|
||||
-(void) delegateBeginUpdates
|
||||
{
|
||||
if ( self.delegate && [self.delegate respondsToSelector:@selector(beginUpdates)] )
|
||||
[self.delegate beginUpdates];
|
||||
}
|
||||
|
||||
-(void) delegateEndUpdates
|
||||
{
|
||||
if ( self.delegate && [self.delegate respondsToSelector:@selector(endUpdates)] )
|
||||
[self.delegate endUpdates];
|
||||
}
|
||||
|
||||
-(void) delegateInsertClippingAtIndex:(int)index
|
||||
{
|
||||
if ( self.delegate && [self.delegate respondsToSelector:@selector(insertClippingAtIndex:)] )
|
||||
[self.delegate insertClippingAtIndex:index];
|
||||
}
|
||||
|
||||
-(void) delegateDeleteClippingAtIndex:(int)index
|
||||
{
|
||||
if ( self.delegate && [self.delegate respondsToSelector:@selector(deleteClippingAtIndex:)] )
|
||||
[self.delegate deleteClippingAtIndex:index];
|
||||
}
|
||||
|
||||
-(void) delegateReloadClippingAtIndex:(int)index
|
||||
{
|
||||
if ( self.delegate && [self.delegate respondsToSelector:@selector(reloadClippingAtIndex:)] )
|
||||
[self.delegate reloadClippingAtIndex:index];
|
||||
}
|
||||
|
||||
-(void) delegateMoveClippingAtIndex:(int)index toIndex:(int)newIndex
|
||||
{
|
||||
if ( self.delegate && [self.delegate respondsToSelector:@selector(moveClippingAtIndex:toIndex:)] )
|
||||
[self.delegate moveClippingAtIndex:index toIndex:newIndex];
|
||||
}
|
||||
|
||||
-(void) dealloc
|
||||
{
|
||||
// Free preferences
|
||||
|
|
|
|||
|
|
@ -28,8 +28,13 @@
|
|||
|
||||
SEL saveSelector;
|
||||
NSObject* saveTarget;
|
||||
int displayNum;
|
||||
int displayLength;
|
||||
|
||||
NSArray *settingsSyncList;
|
||||
|
||||
BOOL disableStore;
|
||||
BOOL inhibitSaveEngineAfterListModification;
|
||||
}
|
||||
|
||||
// Basic functionality
|
||||
|
|
@ -61,6 +66,8 @@
|
|||
// Save and load
|
||||
-(void) saveEngine;
|
||||
-(bool) loadEngineFromPList;
|
||||
-(void) registerOrDeregisterICloudSync;
|
||||
-(void) checkCloudKitUpdates;
|
||||
|
||||
// Preference related
|
||||
-(void) setRememberNum:(int)newRemember;
|
||||
|
|
@ -78,11 +85,14 @@
|
|||
|
||||
// Clippings Store related
|
||||
-(int)jcListCount;
|
||||
-(int)rememberNum;
|
||||
-(FlycutClipping*)clippingAtStackPosition;
|
||||
-(NSArray *) previousDisplayStrings:(int)howMany containing:(NSString*)search;
|
||||
-(NSArray *) previousIndexes:(int)howMany containing:(NSString*)search; // This method is in newest-first order.
|
||||
-(void)setDisableStoreTo:(bool) value;
|
||||
-(bool)storeDisabled;
|
||||
-(void)setClippingsStoreDelegate:(id<FlycutStoreDelegate>) delegate;
|
||||
-(void)setFavoritesStoreDelegate:(id<FlycutStoreDelegate>) delegate;
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
|||
333
FlycutOperator.m
333
FlycutOperator.m
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FlycutOperator.h"
|
||||
#import "MJCloudKitUserDefaultsSync.h"
|
||||
|
||||
@implementation FlycutOperator
|
||||
|
||||
|
|
@ -48,31 +49,83 @@
|
|||
@"saveForgottenFavorites",
|
||||
[NSNumber numberWithBool:YES], // do not commit with YES. Use NO
|
||||
@"pasteMovesToTop",
|
||||
[NSNumber numberWithBool:NO],
|
||||
@"syncSettingsViaICloud",
|
||||
[NSNumber numberWithBool:NO],
|
||||
@"syncClippingsViaICloud",
|
||||
nil]];
|
||||
|
||||
settingsSyncList = @[@"rememberNum",
|
||||
@"favoritesRememberNum",
|
||||
@"savePreference",
|
||||
@"skipPasswordFields",
|
||||
@"skipPboardTypes",
|
||||
@"skipPboardTypesList",
|
||||
@"skipPasswordLengths",
|
||||
@"skipPasswordLengthsList",
|
||||
@"removeDuplicates",
|
||||
@"saveForgottenClippings",
|
||||
@"saveForgottenFavorites",
|
||||
@"pasteMovesToTop"];
|
||||
[settingsSyncList retain];
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)awakeFromNibDisplaying:(int) displayNum withDisplayLength:(int) displayLength withSaveSelector:(SEL) selector forTarget:(NSObject*) target
|
||||
- (void)awakeFromNibDisplaying:(int) dispNum withDisplayLength:(int) dispLength withSaveSelector:(SEL) selector forTarget:(NSObject*) target
|
||||
{
|
||||
// Initialize the FlycutStore
|
||||
clippingStore = [[FlycutStore alloc] initRemembering:[[NSUserDefaults standardUserDefaults] integerForKey:@"rememberNum"]
|
||||
displaying:displayNum
|
||||
withDisplayLength:displayLength];
|
||||
favoritesStore = [[FlycutStore alloc] initRemembering:[[NSUserDefaults standardUserDefaults] integerForKey:@"favoritesRememberNum"]
|
||||
displaying:displayNum
|
||||
withDisplayLength:displayLength];
|
||||
displayNum = dispNum;
|
||||
displayLength = dispLength;
|
||||
saveSelector = selector;
|
||||
saveTarget = target;
|
||||
stashedStore = NULL;
|
||||
|
||||
// If our preferences indicate that we are saving, load the dictionary from the saved plist
|
||||
// and use it to get everything set up.
|
||||
if ( [[NSUserDefaults standardUserDefaults] integerForKey:@"savePreference"] >= 1 ) {
|
||||
[self loadEngineFromPList];
|
||||
}
|
||||
// Initialize the FlycutStore
|
||||
[self initializeStoresAndLoadContents];
|
||||
|
||||
// Stack position starts @ 0 by default
|
||||
stackPosition = favoritesStackPosition = stashedStackPosition = 0;
|
||||
|
||||
[self registerOrDeregisterICloudSync];
|
||||
}
|
||||
|
||||
-(FlycutStore*) allocInitFlycutStoreRemembering:(int) remembering
|
||||
{
|
||||
return [[FlycutStore alloc] initRemembering:remembering
|
||||
displaying:displayNum
|
||||
withDisplayLength:displayLength];
|
||||
}
|
||||
|
||||
-(void) initializeStores
|
||||
{
|
||||
// Fixme - These stores are not released anywhere.
|
||||
if ( !clippingStore )
|
||||
clippingStore = [self allocInitFlycutStoreRemembering:[[NSUserDefaults standardUserDefaults] integerForKey:@"rememberNum"]];
|
||||
else
|
||||
{
|
||||
[clippingStore setDisplayNum:displayNum];
|
||||
[clippingStore setDisplayLen:displayLength];
|
||||
}
|
||||
|
||||
if ( ! favoritesStore )
|
||||
favoritesStore = [self allocInitFlycutStoreRemembering:[[NSUserDefaults standardUserDefaults] integerForKey:@"favoritesRememberNum"]];
|
||||
else
|
||||
{
|
||||
[favoritesStore setDisplayNum:displayNum];
|
||||
[favoritesStore setDisplayLen:displayLength];
|
||||
}
|
||||
|
||||
stashedStore = NULL;
|
||||
}
|
||||
|
||||
-(void) initializeStoresAndLoadContents
|
||||
{
|
||||
[self initializeStores];
|
||||
|
||||
// If our preferences indicate that we are saving, load the dictionary from the saved plist
|
||||
// and use it to get everything set up.
|
||||
if ( [[NSUserDefaults standardUserDefaults] integerForKey:@"savePreference"] >= 1 ) {
|
||||
[self loadEngineFromPList];
|
||||
}
|
||||
}
|
||||
|
||||
-(void) setRememberNum:(int) newRemember
|
||||
|
|
@ -178,7 +231,7 @@
|
|||
}
|
||||
// Get text from clipping store.
|
||||
[favoritesStore addClipping:[clippingStore clippingAtPosition:stackPosition] ];
|
||||
[clippingStore clearItem:stackPosition];
|
||||
[self clearItemAtStackPosition];
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
|
|
@ -190,6 +243,8 @@
|
|||
if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"pasteMovesToTop"] ) {
|
||||
[clippingStore clippingMoveToTop:position];
|
||||
stackPosition = 0;
|
||||
|
||||
[self actionAfterListModification];
|
||||
}
|
||||
return clipping;
|
||||
}
|
||||
|
|
@ -215,6 +270,7 @@
|
|||
// Check to see if they want a little help figuring out what types to enter.
|
||||
if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"revealPasteboardTypes"] )
|
||||
[clippingStore addClipping:type ofType:type fromAppLocalizedName:@"Flycut" fromAppBundleURL:nil atTimestamp:0];
|
||||
[self actionAfterListModification];
|
||||
|
||||
__block bool skipClipping = NO;
|
||||
|
||||
|
|
@ -283,6 +339,20 @@
|
|||
return disableStore;
|
||||
}
|
||||
|
||||
-(void)setClippingsStoreDelegate:(id<FlycutStoreDelegate>) delegate
|
||||
{
|
||||
if ( !clippingStore )
|
||||
[self initializeStores];
|
||||
clippingStore.delegate = delegate;
|
||||
}
|
||||
|
||||
-(void)setFavoritesStoreDelegate:(id<FlycutStoreDelegate>) delegate
|
||||
{
|
||||
if ( !favoritesStore )
|
||||
[self initializeStores];
|
||||
favoritesStore.delegate = delegate;
|
||||
}
|
||||
|
||||
-(int)indexOfClipping:(NSString*)contents ofType:(NSString*)type fromApp:(NSString *)appName withAppBundleURL:(NSString *)bundleURL
|
||||
{
|
||||
return [clippingStore indexOfClipping:contents
|
||||
|
|
@ -301,7 +371,8 @@
|
|||
// clippingStore is full, so save the last entry before it gets lost.
|
||||
// Set to last item, save, and restore position.
|
||||
int savePosition = stackPosition;
|
||||
stackPosition = [clippingStore rememberNum]-1;
|
||||
stackPosition = [clippingStore rememberNum]-1;
|
||||
|
||||
[self saveFromStackWithPrefix:@"Autosave "];
|
||||
stackPosition = savePosition;
|
||||
}
|
||||
|
|
@ -311,23 +382,35 @@
|
|||
fromAppLocalizedName:appName
|
||||
fromAppBundleURL:bundleURL
|
||||
atTimestamp:[[NSDate date] timeIntervalSince1970]];
|
||||
|
||||
// The below tracks our position down down down... Maybe as an option?
|
||||
// if ( [clippingStore jcListCount] > 1 ) stackPosition++;
|
||||
stackPosition = 0;
|
||||
[selectorTarget performSelector:clippingAddedSelector];
|
||||
if ( [[NSUserDefaults standardUserDefaults] integerForKey:@"savePreference"] >= 2 )
|
||||
[self saveEngine];
|
||||
[self actionAfterListModification];
|
||||
|
||||
return success;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(void)actionAfterListModification
|
||||
{
|
||||
if ( !inhibitSaveEngineAfterListModification
|
||||
&& [[NSUserDefaults standardUserDefaults] integerForKey:@"savePreference"] >= 2 )
|
||||
[self saveEngine];
|
||||
}
|
||||
|
||||
-(int)jcListCount
|
||||
{
|
||||
return [clippingStore jcListCount];
|
||||
}
|
||||
|
||||
-(int)rememberNum
|
||||
{
|
||||
return [clippingStore rememberNum];
|
||||
}
|
||||
|
||||
-(int)stackPosition
|
||||
{
|
||||
return stackPosition;
|
||||
|
|
@ -374,7 +457,9 @@
|
|||
if ([clippingStore jcListCount] == 0)
|
||||
return NO;
|
||||
|
||||
[clippingStore clearItem:stackPosition];
|
||||
[clippingStore clearItem:stackPosition];
|
||||
[self actionAfterListModification];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
|
|
@ -404,11 +489,12 @@
|
|||
-(void)clearList
|
||||
{
|
||||
[clippingStore clearList];
|
||||
[self actionAfterListModification];
|
||||
}
|
||||
|
||||
-(void)mergeList
|
||||
{
|
||||
[clippingStore mergeList];
|
||||
[clippingStore mergeList];
|
||||
}
|
||||
|
||||
-(BOOL) isValidClippingNumber:(NSNumber *)number {
|
||||
|
|
@ -423,52 +509,173 @@
|
|||
}
|
||||
}
|
||||
|
||||
-(void) registerOrDeregisterICloudSync
|
||||
{
|
||||
if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"syncSettingsViaICloud"] ) {
|
||||
[MJCloudKitUserDefaultsSync startWithKeyMatchList:settingsSyncList
|
||||
withContainerIdentifier:@"iCloud.com.mark-a-jerde.Flycut"];
|
||||
}
|
||||
else {
|
||||
[MJCloudKitUserDefaultsSync stopForKeyMatchList:settingsSyncList];
|
||||
}
|
||||
|
||||
if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"syncClippingsViaICloud"] ) {
|
||||
[MJCloudKitUserDefaultsSync removeNotificationsFor:MJSyncNotificationChanges forTarget:self];
|
||||
[MJCloudKitUserDefaultsSync addNotificationFor:MJSyncNotificationChanges withSelector:@selector(checkPreferencesChanges:) withTarget: self];
|
||||
|
||||
[MJCloudKitUserDefaultsSync removeNotificationsFor:MJSyncNotificationConflicts forTarget:self];
|
||||
[MJCloudKitUserDefaultsSync addNotificationFor:MJSyncNotificationConflicts withSelector:@selector(checkPreferencesConflicts:) withTarget: self];
|
||||
|
||||
[MJCloudKitUserDefaultsSync startWithKeyMatchList:@[@"store"]
|
||||
withContainerIdentifier:@"iCloud.com.mark-a-jerde.Flycut"];
|
||||
}
|
||||
else {
|
||||
[MJCloudKitUserDefaultsSync removeNotificationsFor:MJSyncNotificationChanges forTarget:self];
|
||||
|
||||
[MJCloudKitUserDefaultsSync stopForKeyMatchList:@[@"store"]];
|
||||
}
|
||||
}
|
||||
|
||||
-(NSDictionary*) checkPreferencesChanges:(NSDictionary*)changes
|
||||
{
|
||||
if ( [changes valueForKey:@"store"] )
|
||||
{
|
||||
inhibitSaveEngineAfterListModification = YES;
|
||||
|
||||
[self integrateStoreAtKey:@"jcList" into:clippingStore];
|
||||
[self integrateStoreAtKey:@"favoritesList" into:favoritesStore];
|
||||
|
||||
inhibitSaveEngineAfterListModification = NO;
|
||||
[self actionAfterListModification];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
-(void) integrateStoreAtKey:(NSString*)listKey into:(FlycutStore*)store
|
||||
{
|
||||
FlycutStore *newContent = [self allocInitFlycutStoreRemembering:[clippingStore rememberNum]];
|
||||
NSDictionary *loadDict = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"store"] copy];
|
||||
|
||||
if ( loadDict && [self loadEngineFrom:loadDict key:listKey into:newContent] )
|
||||
{
|
||||
int newCount = [newContent jcListCount];
|
||||
for ( int i = 0 ; i < newCount ; i++ )
|
||||
{
|
||||
FlycutClipping *newClipping = [newContent clippingAtPosition:i];
|
||||
if ( i >= [store jcListCount] )
|
||||
{
|
||||
// Clipping was beyond the end of the store, so just add it.
|
||||
[newClipping setDisplayLength:displayLength];
|
||||
[store insertClipping:newClipping atIndex:i];
|
||||
}
|
||||
else if ( ![newClipping isEqual:[store clippingAtPosition:i]] )
|
||||
{
|
||||
int firstIndex = [store indexOfClipping:newClipping];
|
||||
if ( firstIndex < 0 )
|
||||
{
|
||||
// Clipping wasn't previously in the store, so just add it.
|
||||
[newClipping setDisplayLength:displayLength];
|
||||
[store insertClipping:newClipping atIndex:i];
|
||||
}
|
||||
else if ( [newContent indexOfClipping:[store clippingAtPosition:i]] < 0 )
|
||||
{
|
||||
// Contents in the store at this position didn't exist in the newContent. Handle deletion.
|
||||
[store clearItem:i];
|
||||
i--;
|
||||
}
|
||||
else if ( [store removeDuplicates] )
|
||||
{
|
||||
if ( i < firstIndex )
|
||||
[store clippingMoveFrom:firstIndex To:i];
|
||||
else
|
||||
{
|
||||
// This can only happen if the remote store allowed duplicates and we do not. Just delete from the new content and move on.
|
||||
[newContent clearItem:i];
|
||||
i--;
|
||||
newCount--;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[newClipping setDisplayLength:displayLength];
|
||||
[store insertClipping:newClipping atIndex:i];
|
||||
}
|
||||
}
|
||||
}
|
||||
while ( [store jcListCount] > newCount )
|
||||
[store clearItem:newCount];
|
||||
|
||||
#ifdef DEBUG
|
||||
[newContent release];
|
||||
newContent = [self allocInitFlycutStoreRemembering:[clippingStore rememberNum]];
|
||||
[self loadEngineFrom:loadDict key:listKey into:newContent];
|
||||
newCount = [newContent jcListCount];
|
||||
if ( newCount != [store jcListCount] )
|
||||
NSLog(@"Error in integrateStoreAtKey with mismatching after counts!");
|
||||
else
|
||||
{
|
||||
for ( int i = 0 ; i < newCount ; i++ )
|
||||
{
|
||||
if ( ![[store clippingAtPosition:i] isEqual:[newContent clippingAtPosition:i]] )
|
||||
NSLog(@"Error in integrateStoreAtKey with mismatching clippings at index %i!", i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[newContent release];
|
||||
}
|
||||
|
||||
-(NSDictionary*) checkPreferencesConflicts:(NSDictionary*)changes
|
||||
{
|
||||
NSMutableDictionary *corrections = nil;
|
||||
if ( [changes valueForKey:@"store"] )
|
||||
{
|
||||
// Just clobber back, for now. They already finished their save, so they won't notice and will just think we made a calculated change.
|
||||
NSLog(@"Oh. My changes were clobbered. I will clobber back.");
|
||||
if ( !corrections )
|
||||
corrections = [[NSMutableDictionary alloc] init];
|
||||
corrections[@"store"] = [changes valueForKey:@"store"][1]; // We win.
|
||||
}
|
||||
return corrections;
|
||||
}
|
||||
|
||||
-(void) checkCloudKitUpdates
|
||||
{
|
||||
[MJCloudKitUserDefaultsSync checkCloudKitUpdates];
|
||||
}
|
||||
|
||||
-(bool) loadEngineFrom:(NSDictionary*)loadDict key:(NSString*)listKey into:(FlycutStore*)store
|
||||
{
|
||||
NSArray *savedJCList = [loadDict objectForKey:listKey];
|
||||
if ( [savedJCList isKindOfClass:[NSArray class]] ) {
|
||||
// There's probably a nicer way to prevent the range from going out of bounds, but this works.
|
||||
int rangeCap = [savedJCList count] < [store rememberNum] ? [savedJCList count] : [store rememberNum];
|
||||
NSRange loadRange = NSMakeRange(0, rangeCap);
|
||||
NSArray *toBeRestoredClips = [[[savedJCList subarrayWithRange:loadRange] reverseObjectEnumerator] allObjects];
|
||||
for( NSDictionary *aSavedClipping in toBeRestoredClips)
|
||||
[store addClipping:[aSavedClipping objectForKey:@"Contents"]
|
||||
ofType:[aSavedClipping objectForKey:@"Type"]
|
||||
fromAppLocalizedName:[aSavedClipping objectForKey:@"AppLocalizedName"]
|
||||
fromAppBundleURL:[aSavedClipping objectForKey:@"AppBundleURL"]
|
||||
atTimestamp:[[aSavedClipping objectForKey:@"Timestamp"] integerValue]];
|
||||
return YES;
|
||||
} else DLog(@"Not array");
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(bool) loadEngineFromPList
|
||||
{
|
||||
NSDictionary *loadDict = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"store"] copy];
|
||||
NSArray *savedJCList;
|
||||
NSRange loadRange;
|
||||
NSDictionary *loadDict = [[[NSUserDefaults standardUserDefaults] dictionaryForKey:@"store"] copy];
|
||||
|
||||
int rangeCap;
|
||||
|
||||
if ( loadDict != nil ) {
|
||||
|
||||
savedJCList = [loadDict objectForKey:@"jcList"];
|
||||
|
||||
if ( [savedJCList isKindOfClass:[NSArray class]] ) {
|
||||
int rememberNumPref = [[NSUserDefaults standardUserDefaults]
|
||||
integerForKey:@"rememberNum"];
|
||||
// There's probably a nicer way to prevent the range from going out of bounds, but this works.
|
||||
rangeCap = [savedJCList count] < rememberNumPref ? [savedJCList count] : rememberNumPref;
|
||||
loadRange = NSMakeRange(0, rangeCap);
|
||||
NSArray *toBeRestoredClips = [[[savedJCList subarrayWithRange:loadRange] reverseObjectEnumerator] allObjects];
|
||||
for( NSDictionary *aSavedClipping in toBeRestoredClips)
|
||||
[clippingStore addClipping:[aSavedClipping objectForKey:@"Contents"]
|
||||
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"];
|
||||
if ( [savedJCList isKindOfClass:[NSArray class]] ) {
|
||||
rememberNumPref = [[NSUserDefaults standardUserDefaults]
|
||||
integerForKey:@"favoritesRememberNum"];
|
||||
rangeCap = [savedJCList count] < rememberNumPref ? [savedJCList count] : rememberNumPref;
|
||||
loadRange = NSMakeRange(0, rangeCap);
|
||||
toBeRestoredClips = [[[savedJCList subarrayWithRange:loadRange] reverseObjectEnumerator] allObjects];
|
||||
for( NSDictionary *aSavedClipping in toBeRestoredClips)
|
||||
[favoritesStore addClipping:[aSavedClipping objectForKey:@"Contents"]
|
||||
ofType:[aSavedClipping objectForKey:@"Type"]
|
||||
fromAppLocalizedName:[aSavedClipping objectForKey:@"AppLocalizedName"]
|
||||
fromAppBundleURL:[aSavedClipping objectForKey:@"AppBundleURL"]
|
||||
atTimestamp:[[aSavedClipping objectForKey:@"Timestamp"] integerValue]];
|
||||
}
|
||||
} else DLog(@"Not array");
|
||||
[loadDict release];
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
if ( loadDict != nil ) {
|
||||
bool success = NO;
|
||||
success |= [self loadEngineFrom:loadDict key:@"jcList" into:clippingStore];
|
||||
success |= [self loadEngineFrom:loadDict key:@"favoritesList" into:favoritesStore];
|
||||
[loadDict release];
|
||||
return success;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
-(bool)setStackPositionToOneLessRecent
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue