diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index 181a86b4..3637abf6 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -47,6 +47,8 @@ 6AD7CBC815FE555000691B5B /* data-plain-bpmf.txt in Resources */ = {isa = PBXBuildFile; fileRef = 6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */; }; 6AE210B215FC63CC003659FE /* PlainBopomofo.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */; }; 6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */; }; + 6AFF97F2253B299E007F1C49 /* OVNonModalAlertWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */; }; + 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -209,6 +211,9 @@ 6AD7CBC715FE555000691B5B /* data-plain-bpmf.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "data-plain-bpmf.txt"; sourceTree = ""; }; 6AE210B015FC63CC003659FE /* PlainBopomofo.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = PlainBopomofo.tiff; sourceTree = ""; }; 6AE210B115FC63CC003659FE /* PlainBopomofo@2x.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = "PlainBopomofo@2x.tiff"; sourceTree = ""; }; + 6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OVNonModalAlertWindowController.h; sourceTree = ""; }; + 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = OVNonModalAlertWindowController.xib; sourceTree = ""; }; + 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OVNonModalAlertWindowController.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -277,6 +282,8 @@ 6A0D4EC715FC0D6400ABF4B3 /* InputMethodController.mm */, 6A0D4EC815FC0D6400ABF4B3 /* main.m */, 6A0D4EF615FC0DA600ABF4B3 /* McBopomofo-Prefix.pch */, + 6AFF97EF253B299E007F1C49 /* OVNonModalAlertWindowController.h */, + 6AFF97F1253B299E007F1C49 /* OVNonModalAlertWindowController.m */, 6A0D4EC915FC0D6400ABF4B3 /* OVInputSourceHelper.h */, 6A0D4ECA15FC0D6400ABF4B3 /* OVInputSourceHelper.m */, 6A0D4ECB15FC0D6400ABF4B3 /* PreferencesWindowController.h */, @@ -399,6 +406,7 @@ 6A0D4F4715FC0EB900ABF4B3 /* Resources */ = { isa = PBXGroup; children = ( + 6AFF97F0253B299E007F1C49 /* OVNonModalAlertWindowController.xib */, 6A0D4EEE15FC0DA600ABF4B3 /* Images */, 6A0D4EF515FC0DA600ABF4B3 /* McBopomofo-Info.plist */, 6A0D4F4815FC0EE100ABF4B3 /* InfoPlist.strings */, @@ -575,6 +583,7 @@ 6A0D4F5815FC0EF900ABF4B3 /* Localizable.strings in Resources */, 6A2E40F6253A69DA00D1AE1D /* Images.xcassets in Resources */, 6A38BC1515FC117A00A8A51F /* data.txt in Resources */, + 6AFF97F2253B299E007F1C49 /* OVNonModalAlertWindowController.xib in Resources */, 6AE210B215FC63CC003659FE /* PlainBopomofo.tiff in Resources */, 6AE210B315FC63CC003659FE /* PlainBopomofo@2x.tiff in Resources */, 6AD7CBC815FE555000691B5B /* data-plain-bpmf.txt in Resources */, @@ -632,6 +641,7 @@ 6A0D4EFE15FC0DA600ABF4B3 /* VTCandidateController.m in Sources */, 6A0D4EFF15FC0DA600ABF4B3 /* VTHorizontalCandidateController.m in Sources */, 6A0D4F0015FC0DA600ABF4B3 /* VTHorizontalCandidateView.m in Sources */, + 6AFF97F3253B299E007F1C49 /* OVNonModalAlertWindowController.m in Sources */, 6A0D4F0115FC0DA600ABF4B3 /* VTVerticalCandidateController.m in Sources */, 6A0D4F0215FC0DA600ABF4B3 /* VTVerticalCandidateTableView.m in Sources */, 6A0D4F0315FC0DA600ABF4B3 /* VTVerticalKeyLabelStripView.m in Sources */, diff --git a/Source/AppDelegate.h b/Source/AppDelegate.h index eb216be0..88f94c78 100644 --- a/Source/AppDelegate.h +++ b/Source/AppDelegate.h @@ -42,6 +42,7 @@ @private NSWindow *_window; NSURLConnection *_updateCheckConnection; + BOOL _currentUpdateCheckIsForced; NSMutableData *_receivingData; PreferencesWindowController *_preferencesWindowController; UpdateNotificationController *_updateNotificationController; diff --git a/Source/AppDelegate.m b/Source/AppDelegate.m index e972e13e..fa1bc467 100644 --- a/Source/AppDelegate.m +++ b/Source/AppDelegate.m @@ -33,6 +33,7 @@ // #import "AppDelegate.h" +#import "OVNonModalAlertWindowController.h" #import "UpdateNotificationController.h" #import "PreferencesWindowController.h" @@ -44,7 +45,7 @@ static NSString *kUpdateInfoSiteKey = @"UpdateInfoSite"; static const NSTimeInterval kNextCheckInterval = 86400.0; static const NSTimeInterval kTimeoutInterval = 60.0; -@interface AppDelegate () +@interface AppDelegate () @end @implementation AppDelegate @@ -77,6 +78,8 @@ static const NSTimeInterval kTimeoutInterval = 60.0; return; } + _currentUpdateCheckIsForced = forced; + // time for update? if (!forced) { NSDate *now = [NSDate date]; @@ -134,10 +137,22 @@ static const NSTimeInterval kTimeoutInterval = 60.0; - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { + BOOL isForcedCheck = _currentUpdateCheckIsForced; + [_receivingData release]; _receivingData = nil; [_updateCheckConnection release]; _updateCheckConnection = nil; + _currentUpdateCheckIsForced = NO; + + if (isForcedCheck) { + [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Update Check Failed", nil) content:[NSString stringWithFormat:NSLocalizedString(@"There may be no internet connection or the server failed to respond.\n\nError message: %@", nil), [error localizedDescription]] confirmButtonTitle:NSLocalizedString(@"Dismiss", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; + } +} + +- (void)showNoUpdateAvailableAlert +{ + [[OVNonModalAlertWindowController sharedInstance] showWithTitle:NSLocalizedString(@"Check for Update Completed", nil) content:NSLocalizedString(@"You are already using the latest version of McBopomofo.", nil) confirmButtonTitle:NSLocalizedString(@"OK", nil) cancelButtonTitle:nil cancelAsDefault:NO delegate:nil]; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection @@ -147,12 +162,18 @@ static const NSTimeInterval kTimeoutInterval = 60.0; NSLog(@"plist %@",plist); #endif + BOOL isForcedCheck = _currentUpdateCheckIsForced; + [_receivingData release]; _receivingData = nil; [_updateCheckConnection release]; _updateCheckConnection = nil; + _currentUpdateCheckIsForced = NO; if (!plist) { + if (isForcedCheck) { + [self showNoUpdateAvailableAlert]; + } return; } @@ -161,6 +182,9 @@ static const NSTimeInterval kTimeoutInterval = 60.0; NSLog(@"the remoteversion is %@",remoteVersion); #endif if (!remoteVersion) { + if (isForcedCheck) { + [self showNoUpdateAvailableAlert]; + } return; } @@ -172,17 +196,26 @@ static const NSTimeInterval kTimeoutInterval = 60.0; NSComparisonResult result = [currentVersion compare:remoteVersion options:NSNumericSearch]; if (result != NSOrderedAscending) { + if (isForcedCheck) { + [self showNoUpdateAvailableAlert]; + } return; } NSString *siteInfoURLString = [plist objectForKey:kUpdateInfoSiteKey]; if (!siteInfoURLString) { + if (isForcedCheck) { + [self showNoUpdateAvailableAlert]; + } return; } NSURL *siteInfoURL = [NSURL URLWithString:siteInfoURLString]; if (!siteInfoURL) { + if (isForcedCheck) { + [self showNoUpdateAvailableAlert]; + } return; } @@ -210,4 +243,8 @@ static const NSTimeInterval kTimeoutInterval = 60.0; { [_receivingData appendData:data]; } + +- (void)nonModalAlertWindowControllerDidConfirm:(OVNonModalAlertWindowController *)controller +{ +} @end diff --git a/Source/Images/Images.xcassets/AlertIcon.imageset/128X128.png b/Source/Images/Images.xcassets/AlertIcon.imageset/128X128.png new file mode 100644 index 00000000..1a599df9 Binary files /dev/null and b/Source/Images/Images.xcassets/AlertIcon.imageset/128X128.png differ diff --git a/Source/Images/Images.xcassets/AlertIcon.imageset/192x192.png b/Source/Images/Images.xcassets/AlertIcon.imageset/192x192.png new file mode 100644 index 00000000..33fa764a Binary files /dev/null and b/Source/Images/Images.xcassets/AlertIcon.imageset/192x192.png differ diff --git a/Source/Images/Images.xcassets/AlertIcon.imageset/64X64.png b/Source/Images/Images.xcassets/AlertIcon.imageset/64X64.png new file mode 100644 index 00000000..2b3d950b Binary files /dev/null and b/Source/Images/Images.xcassets/AlertIcon.imageset/64X64.png differ diff --git a/Source/Images/Images.xcassets/AlertIcon.imageset/Contents.json b/Source/Images/Images.xcassets/AlertIcon.imageset/Contents.json new file mode 100644 index 00000000..78b259ac --- /dev/null +++ b/Source/Images/Images.xcassets/AlertIcon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "64X64.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "128X128.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "192x192.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Source/OVNonModalAlertWindowController.h b/Source/OVNonModalAlertWindowController.h new file mode 100644 index 00000000..6ca4902b --- /dev/null +++ b/Source/OVNonModalAlertWindowController.h @@ -0,0 +1,39 @@ +// +// OVNonModalAlertWindowController.h +// OpenVanilla +// +// Created by Lukhnos Liu on 10/17/12. +// Copyright (c) 2012 The OpenVanilla Project. All rights reserved. +// + +#import + +@class OVNonModalAlertWindowController; + +@protocol OVNonModalAlertWindowControllerDelegate +- (void)nonModalAlertWindowControllerDidConfirm:(OVNonModalAlertWindowController *)controller; + +@optional +- (void)nonModalAlertWindowControllerDidCancel:(OVNonModalAlertWindowController *)controller; +@end + +@interface OVNonModalAlertWindowController : NSWindowController +{ +@private + NSTextField *_titleTextField; + NSTextField *_contentTextField; + NSButton *_confirmButton; + NSButton *_cancelButton; + id _delegate; +} + ++ (OVNonModalAlertWindowController *)sharedInstance; +- (void)showWithTitle:(NSString *)title content:(NSString *)content confirmButtonTitle:(NSString *)confirmTitle cancelButtonTitle:(NSString *)cancelButtonTitle cancelAsDefault:(BOOL)cancelAsDefault delegate:(id)delegate; +- (IBAction)confirmButtonAction:(id)sender; +- (IBAction)cancelButtonAction:(id)sender; +@property (assign, nonatomic) IBOutlet NSTextField *titleTextField; +@property (assign, nonatomic) IBOutlet NSTextField *contentTextField; +@property (assign, nonatomic) IBOutlet NSButton *confirmButton; +@property (assign, nonatomic) IBOutlet NSButton *cancelButton; +@property (assign, nonatomic) id delegate; +@end diff --git a/Source/OVNonModalAlertWindowController.m b/Source/OVNonModalAlertWindowController.m new file mode 100644 index 00000000..25ce7a24 --- /dev/null +++ b/Source/OVNonModalAlertWindowController.m @@ -0,0 +1,131 @@ +// +// OVNonModalAlertWindowController.m +// OpenVanilla +// +// Created by Lukhnos Liu on 10/17/12. +// Copyright (c) 2012 The OpenVanilla Project. All rights reserved. +// + +#import "OVNonModalAlertWindowController.h" + +@implementation OVNonModalAlertWindowController +@synthesize titleTextField = _titleTextField; +@synthesize contentTextField = _contentTextField; +@synthesize confirmButton = _confirmButton; +@synthesize cancelButton = _cancelButton; +@synthesize delegate = _delegate; + ++ (OVNonModalAlertWindowController *)sharedInstance +{ + static OVNonModalAlertWindowController *instance; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + instance = [[OVNonModalAlertWindowController alloc] initWithWindowNibName:@"OVNonModalAlertWindowController"]; + [instance window]; + }); + return instance; +} + +// Suppress the use of the MIN/MAX macros +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wgnu-statement-expression" + +- (void)showWithTitle:(NSString *)title content:(NSString *)content confirmButtonTitle:(NSString *)confirmTitle cancelButtonTitle:(NSString *)cancelButtonTitle cancelAsDefault:(BOOL)cancelAsDefault delegate:(id)delegate; +{ + // cancel previous alert + if ([[self window] isVisible]) { + if ([_delegate respondsToSelector:@selector(nonModalAlertWindowControllerDidCancel:)]) { + [_delegate nonModalAlertWindowControllerDidCancel:self]; + } + } + + _delegate = delegate; + + NSRect oldFrame = [self.confirmButton frame]; + [self.confirmButton setTitle:confirmTitle]; + [self.confirmButton sizeToFit]; + + NSRect newFrame = [self.confirmButton frame]; + + newFrame.size.width = MAX(90.0, (newFrame.size.width + 10.0)); + newFrame.origin.x += (oldFrame.size.width - newFrame.size.width); + [self.confirmButton setFrame:newFrame]; + + if (cancelButtonTitle) { + [self.cancelButton setTitle:cancelButtonTitle]; + [self.cancelButton sizeToFit]; + NSRect adjustedFrame = [self.cancelButton frame]; + adjustedFrame.size.width = MAX(90.0, (adjustedFrame.size.width + 10.0)); + adjustedFrame.origin.x = newFrame.origin.x - adjustedFrame.size.width; + [self.cancelButton setFrame:adjustedFrame]; + [self.cancelButton setHidden:NO]; + } + else { + [self.cancelButton setHidden:YES]; + } + + [self.cancelButton setNextKeyView:self.confirmButton]; + [self.confirmButton setNextKeyView:self.cancelButton]; + + if (cancelButtonTitle) { + if (cancelAsDefault) { + [[self window] setDefaultButtonCell:[self.cancelButton cell]]; + } + else { + [self.cancelButton setKeyEquivalent:@" "]; + [[self window] setDefaultButtonCell:[self.confirmButton cell]]; + } + } + else { + [[self window] setDefaultButtonCell:[self.confirmButton cell]]; + } + + [self.titleTextField setStringValue:title]; + + oldFrame = [self.contentTextField frame]; + [self.contentTextField setStringValue:content]; + + NSRect infiniteHeightFrame = oldFrame; + infiniteHeightFrame.size.width -= 4.0; + infiniteHeightFrame.size.height = 10240; + newFrame = [content boundingRectWithSize:infiniteHeightFrame.size options:NSStringDrawingUsesLineFragmentOrigin attributes:[NSDictionary dictionaryWithObjectsAndKeys:[self.contentTextField font], NSFontAttributeName, nil]]; + newFrame.size.width = MAX(newFrame.size.width, oldFrame.size.width); + newFrame.size.height += 4.0; + newFrame.origin = oldFrame.origin; + newFrame.origin.y -= (newFrame.size.height - oldFrame.size.height); + [self.contentTextField setFrame:newFrame]; + + NSRect windowFrame = [[self window] frame]; + windowFrame.size.height += (newFrame.size.height - oldFrame.size.height); + + [[self window] setLevel:CGShieldingWindowLevel() + 1]; + [[self window] setFrame:windowFrame display:YES]; + [[self window] center]; + [[self window] makeKeyAndOrderFront:self]; + [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; +} +#pragma GCC diagnostic pop + +- (IBAction)confirmButtonAction:(id)sender +{ + [_delegate nonModalAlertWindowControllerDidConfirm:self]; + [[self window] orderOut:self]; +} + +- (IBAction)cancelButtonAction:(id)sender +{ + [self cancel:sender]; +} + +- (void)cancel:(id)sender +{ + if ([_delegate respondsToSelector:@selector(nonModalAlertWindowControllerDidCancel:)]) { + [_delegate nonModalAlertWindowControllerDidCancel:self]; + } + + _delegate = nil; + + [[self window] orderOut:self]; +} + +@end diff --git a/Source/OVNonModalAlertWindowController.xib b/Source/OVNonModalAlertWindowController.xib new file mode 100644 index 00000000..430b3b36 --- /dev/null +++ b/Source/OVNonModalAlertWindowController.xib @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/en.lproj/Localizable.strings b/Source/en.lproj/Localizable.strings index 8024204c..671501f5 100644 --- a/Source/en.lproj/Localizable.strings +++ b/Source/en.lproj/Localizable.strings @@ -27,3 +27,22 @@ /* No comment provided by engineer. */ "Check for Updates…" = "Check for Updates…"; + +/* No comment provided by engineer. */ +"Check for Update Completed" = "Check for Update Completed"; + +/* No comment provided by engineer. */ +"You are already using the latest version of McBopomofo." = "You are already using the latest version of McBopomofo."; + +/* No comment provided by engineer. */ +"Update Check Failed" = "Update Check Failed"; + +/* No comment provided by engineer. */ +"There may be no internet connection or the server failed to respond.\n\nError message: %@" = "There may be no internet connection or the server failed to respond.\n\nError message: %@"; + +/* No comment provided by engineer. */ +"OK" = "OK"; + +/* No comment provided by engineer. */ +"Dismiss" = "Dismiss"; + diff --git a/Source/zh-Hant.lproj/Localizable.strings b/Source/zh-Hant.lproj/Localizable.strings index 2ebe96de..d13db3d2 100644 --- a/Source/zh-Hant.lproj/Localizable.strings +++ b/Source/zh-Hant.lproj/Localizable.strings @@ -27,3 +27,22 @@ /* No comment provided by engineer. */ "Check for Updates…" = "檢查是否有新版…"; + +/* No comment provided by engineer. */ +"Check for Update Completed" = "新版檢查完畢"; + +/* No comment provided by engineer. */ +"You are already using the latest version of McBopomofo." = "目前使用的已經是最新版本。"; + +/* No comment provided by engineer. */ +"Update Check Failed" = "無法檢查新版"; + +/* No comment provided by engineer. */ +"There may be no internet connection or the server failed to respond.\n\nError message: %@" = "網路連線失敗,或是伺服器沒有回應。\n\n錯誤說明:%@"; + +/* No comment provided by engineer. */ +"OK" = "好"; + +/* No comment provided by engineer. */ +"Dismiss" = "關閉本視窗"; +