diff --git a/mac/Keyman4MacIM/Keyman4MacIM/Base.lproj/MainMenu.xib b/mac/Keyman4MacIM/Keyman4MacIM/Base.lproj/MainMenu.xib index 4062663c00f..0696c81eba2 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/Base.lproj/MainMenu.xib +++ b/mac/Keyman4MacIM/Keyman4MacIM/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -19,19 +19,16 @@ - + + + - - - - - - + - + diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m index 14ee3c9c351..70937eecbf3 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMConfiguration/KMConfigurationWindowController.m @@ -364,6 +364,8 @@ - (void)checkBoxAction:(id)sender { [self saveActiveKeyboards]; } else if (checkBox.state == NSOffState) { + if ([self.AppDelegate debugMode]) + NSLog(@"Disabling active keyboard: %@", kmxFilePath); [self.activeKeyboards removeObject:kmxFilePath]; [self saveActiveKeyboards]; } diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m index 1607e915e07..47d2bfceb58 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputController.m @@ -183,65 +183,40 @@ - (KMXFile *)kmx { return self.AppDelegate.kmx; } - - (void)menuAction:(id)sender { NSMenuItem *mItem = [sender objectForKey:kIMKCommandMenuItemName]; NSInteger itag = mItem.tag; if ([self.AppDelegate debugMode]) NSLog(@"Keyman menu clicked - tag: %lu", itag); - if (itag == 2) { - // Using `showConfigurationWindow` instead of `showPreferences:` because `showPreferences:` is missing in - // High Sierra (10.13.1 - 10.13.3). See: https://bugreport.apple.com/web/?problemID=35422518 - // rrb: where Apple's API is broken (10.13.1-10.13.3) call our workaround, otherwise, call showPreferences - u_int16_t systemVersion = [KMOSVersion SystemVersion]; - if ([KMOSVersion Version_10_13_1] <= systemVersion && systemVersion <= [KMOSVersion Version_10_13_3]) // between 10.13.1 and 10.13.3 inclusive - { - NSLog(@"Input Menu: calling workaround instead of showPreferences (sys ver %x)", systemVersion); - [self.AppDelegate showConfigurationWindow]; // call our workaround - } - else - { - NSLog(@"Input Menu: calling Apple's showPreferences (sys ver %x)", systemVersion); - [self showPreferences:sender]; // call Apple API - } + if (itag == CONFIG_MENUITEM_TAG) { + [self showConfigurationWindow:sender]; } - else if (itag == 3) { + else if (itag == OSK_MENUITEM_TAG) { [self.AppDelegate showOSK]; } - else if (itag == 4) { + else if (itag == ABOUT_MENUITEM_TAG) { [self.AppDelegate showAboutWindow]; } - else if (itag >= 1000) { - NSMenuItem *keyboards = [self.AppDelegate.menu itemWithTag:1]; - for (NSMenuItem *item in keyboards.submenu.itemArray) { - if (item.tag == itag) - [item setState:NSOnState]; - else - [item setState:NSOffState]; - } - - NSString *path = [self.AppDelegate.activeKeyboards objectAtIndex:itag%1000]; - KMXFile *kmx = [[KMXFile alloc] initWithFilePath:path]; - [self.AppDelegate setKmx:kmx]; - KVKFile *kvk = nil; - NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:path]; - NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey]; - if (kvkFilename != nil) { - NSString *kvkFilePath = [self.AppDelegate kvkFilePathFromFilename:kvkFilename]; - if (kvkFilePath != nil) - kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath]; - } - [self.AppDelegate setKvk:kvk]; - NSString *keyboardName = [kmxInfo objectForKey:kKMKeyboardNameKey]; - if ([self.AppDelegate debugMode]) - NSLog(@"Selected keyboard from menu: %@", keyboardName); - [self.AppDelegate setKeyboardName:keyboardName]; - [self.AppDelegate setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; - [self.AppDelegate setContextBuffer:nil]; - [self.AppDelegate setSelectedKeyboard:path]; - [self.AppDelegate loadSavedStores]; - if (kvk != nil && self.AppDelegate.alwaysShowOSK) - [self.AppDelegate showOSK]; + else if (itag >= KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG) { + [self.AppDelegate selectKeyboardFromMenu:itag]; } } + +- (void)showConfigurationWindow:(id)sender { + // Using `showConfigurationWindow` instead of `showPreferences:` because `showPreferences:` is missing in + // High Sierra (10.13.1 - 10.13.3). See: https://bugreport.apple.com/web/?problemID=35422518 + // rrb: where Apple's API is broken (10.13.1-10.13.3) call our workaround, otherwise, call showPreferences + u_int16_t systemVersion = [KMOSVersion SystemVersion]; + if ([KMOSVersion Version_10_13_1] <= systemVersion && systemVersion <= [KMOSVersion Version_10_13_3]) // between 10.13.1 and 10.13.3 inclusive + { + NSLog(@"Input Menu: calling workaround instead of showPreferences (sys ver %x)", systemVersion); + [self.AppDelegate showConfigurationWindow]; // call our workaround + } + else + { + NSLog(@"Input Menu: calling Apple's showPreferences (sys ver %x)", systemVersion); + [self showPreferences:sender]; // call Apple API + } +} + @end diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h index 5214faf8212..e4e53e5972f 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.h @@ -38,6 +38,21 @@ typedef struct { NSString *apiKeymanCom; } KeymanVersionInfo; +// tags for default menu items, displayed whether keyboards are active or not +static const int DIVIDER_MENUITEM_TAG = -4; +static const int CONFIG_MENUITEM_TAG = -3; +static const int OSK_MENUITEM_TAG = -2; +static const int ABOUT_MENUITEM_TAG = -1; + +// the number of menu items that do not represent active keyboards +static const int DEFAULT_KEYMAN_MENU_ITEM_COUNT = 4; + +// the tag for the first keyboard dynamically added to the menu +static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG = 0; + +// the menu index for the first keyboard dynamically added to the menu +static const int KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX = 0; + @interface KMInputMethodAppDelegate : NSObject #define USE_ALERT_SHOW_HELP_TO_FORCE_EASTER_EGG_CRASH_FROM_ENGINE 1 #ifdef USE_ALERT_SHOW_HELP_TO_FORCE_EASTER_EGG_CRASH_FROM_ENGINE @@ -55,6 +70,7 @@ typedef struct { @property (nonatomic, strong) NSMutableArray *kmxFileList; @property (nonatomic, strong) NSString *selectedKeyboard; @property (nonatomic, strong) NSMutableArray *activeKeyboards; +@property (assign) int numberOfKeyboardMenuItems; @property (nonatomic, strong) NSMutableString *contextBuffer; @property (nonatomic, assign) NSEventModifierFlags currentModifierFlags; @property (nonatomic, assign) CFMachPortRef lowLevelEventTap; @@ -88,6 +104,7 @@ typedef struct { - (void)showAboutWindow; - (void)showOSK; - (void)showConfigurationWindow; +- (void)selectKeyboardFromMenu:(NSInteger)tag; - (void)sleepFollowingDeactivationOfServer:(id)lastServer; - (void)wakeUpWith:(id)newServer; - (void)handleKeyEvent:(NSEvent *)event; diff --git a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m index 44df2ef6fa2..b04236ed5b8 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m +++ b/mac/Keyman4MacIM/Keyman4MacIM/KMInputMethodAppDelegate.m @@ -700,14 +700,14 @@ - (void)saveActiveKeyboards { [userData setObject:_activeKeyboards forKey:kKMActiveKeyboardsKey]; [userData synchronize]; [self resetActiveKeyboards]; - [self setKeyboardsSubMenu]; + [self updateKeyboardMenuItems]; } - (void)clearActiveKeyboards { NSUserDefaults *userData = [NSUserDefaults standardUserDefaults]; [userData setObject:nil forKey:kKMActiveKeyboardsKey]; [userData synchronize]; - [self setKeyboardsSubMenu]; + [self updateKeyboardMenuItems]; } - (void)resetActiveKeyboards { @@ -760,90 +760,174 @@ - (void)setContextBuffer:(NSMutableString *)contextBuffer { } - (void)awakeFromNib { - [self setKeyboardsSubMenu]; - - NSMenuItem *config = [self.menu itemWithTag:2]; - if (config) - [config setAction:@selector(menuAction:)]; - - NSMenuItem *osk = [self.menu itemWithTag:3]; - if (osk) - [osk setAction:@selector(menuAction:)]; - - NSMenuItem *about = [self.menu itemWithTag:4]; - if (about) - [about setAction:@selector(menuAction:)]; -} - -- (void)setKeyboardsSubMenu { - NSMenuItem *keyboards = [self.menu itemWithTag:1]; - if (keyboards) { - KVKFile *kvk = nil; - BOOL didSetKeyboard = NO; - NSInteger itag = 1000; - [keyboards.submenu removeAllItems]; - for (NSString *path in self.activeKeyboards) { - NSDictionary *infoDict = [KMXFile keyboardInfoFromKmxFile:path]; - if (!infoDict) - continue; - //NSString *str = [NSString stringWithFormat:@"%@ (%@)", [infoDict objectForKey:kKMKeyboardNameKey], [infoDict objectForKey:kKMKeyboardVersionKey]]; - NSString *str = [infoDict objectForKey:kKMKeyboardNameKey]; - NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:str action:@selector(menuAction:) keyEquivalent:@""]; - [item setTag:itag++]; - if ([path isEqualToString:self.selectedKeyboard]) { - [item setState:NSOnState]; - KMXFile *kmx = [[KMXFile alloc] initWithFilePath:path]; - [self setKmx:kmx]; - NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:path]; - NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey]; - if (kvkFilename != nil) { - NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename]; - if (kvkFilePath != nil) - kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath]; - } - [self setKvk:kvk]; - [self setKeyboardName:[kmxInfo objectForKey:kKMKeyboardNameKey]]; - [self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; - [self loadSavedStores]; + [self setDefaultKeymanMenuItems]; + [self updateKeyboardMenuItems]; +} - didSetKeyboard = YES; - } - else - [item setState:NSOffState]; +- (void)setDefaultKeymanMenuItems { + NSMenuItem *config = [self.menu itemWithTag:CONFIG_MENUITEM_TAG]; + if (config) { + [config setAction:@selector(menuAction:)]; + } + + NSMenuItem *osk = [self.menu itemWithTag:OSK_MENUITEM_TAG]; + if (osk) { + [osk setAction:@selector(menuAction:)]; + } + + NSMenuItem *about = [self.menu itemWithTag:ABOUT_MENUITEM_TAG]; + if (about) { + [about setAction:@selector(menuAction:)]; + } +} - [keyboards.submenu addItem:item]; +- (void)updateKeyboardMenuItems { + self.numberOfKeyboardMenuItems = [self calculateNumberOfKeyboardMenuItems]; + [self removeDynamicKeyboardMenuItems]; + [self addDynamicKeyboardMenuItems]; +} + +- (int)calculateNumberOfKeyboardMenuItems { + if (self.activeKeyboards.count == 0) { + // if there are no active keyboards, then we will insert one placeholder menu item 'No Active Keyboards' + return 1; + } else { + return (int) self.activeKeyboards.count; + } +} + +- (void)removeDynamicKeyboardMenuItems { + int numberToRemove = (int) self.menu.numberOfItems - DEFAULT_KEYMAN_MENU_ITEM_COUNT; + + if (self.debugMode) { + NSLog(@"*** removeDynamicKeyboardMenuItems, self.menu.numberOfItems = %ld, number of items to remove = %d", (long)self.menu.numberOfItems, numberToRemove); + } + + if (numberToRemove > 0) { + for (int i = 0; i < numberToRemove; i++) { + [self.menu removeItemAtIndex:KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX]; + } + } +} + +- (void)addDynamicKeyboardMenuItems { + BOOL didSetSelectedKeyboard = NO; + NSInteger itag = KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG; + NSString *keyboardMenuName = @""; + int menuItemIndex = KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX; + + if (self.debugMode) { + NSLog(@"*** populateKeyboardMenuItems, number of active keyboards=%lu", self.activeKeyboards.count); + } + + // loop through the active keyboards list and add them to the menu + for (NSString *path in self.activeKeyboards) { + NSDictionary *infoDict = [KMXFile keyboardInfoFromKmxFile:path]; + if (!infoDict) { + continue; } + keyboardMenuName = [infoDict objectForKey:kKMKeyboardNameKey]; + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:keyboardMenuName action:@selector(menuAction:) keyEquivalent:@""]; + [item setTag:itag++]; + + // if this is the selected keyboard, then configure it as selected + if ([path isEqualToString:self.selectedKeyboard]) { + [self setSelectedKeyboard:path inMenuItem:item]; + didSetSelectedKeyboard = YES; + } + else { + [item setState:NSOffState]; + } + + [self.menu insertItem:item atIndex:menuItemIndex++]; + } + + if (self.activeKeyboards.count == 0) { + [self addKeyboardPlaceholderMenuItem]; + } else if (!didSetSelectedKeyboard) { + [self setDefaultSelectedKeyboard]; + } +} - if (keyboards.submenu.numberOfItems == 0) { - NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:@"(None)" action:NULL keyEquivalent:@""]; - [keyboards.submenu addItem:item]; - [self setKmx:nil]; - [self setKvk:nil]; - [self setKeyboardName:nil]; - [self setKeyboardIcon:nil]; - [self setContextBuffer:nil]; - [self setSelectedKeyboard:nil]; +- (void) setSelectedKeyboard:(NSString*)keyboardName inMenuItem:(NSMenuItem*) menuItem { + KVKFile *kvk = nil; + + [menuItem setState:NSOnState]; + KMXFile *kmx = [[KMXFile alloc] initWithFilePath:keyboardName]; + [self setKmx:kmx]; + NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:keyboardName]; + NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey]; + if (kvkFilename != nil) { + NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename]; + if (kvkFilePath != nil) { + kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath]; } - else if (!didSetKeyboard) { - [keyboards.submenu itemAtIndex:0].state = NSOnState; - NSString *path = [self.activeKeyboards objectAtIndex:0]; - KMXFile *kmx = [[KMXFile alloc] initWithFilePath:path]; - [self setKmx:kmx]; - NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:path]; - NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey]; - if (kvkFilename != nil) { - NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename]; - if (kvkFilePath != nil) - kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath]; + } + [self setKvk:kvk]; + [self setKeyboardName:[kmxInfo objectForKey:kKMKeyboardNameKey]]; + [self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; + [self loadSavedStores]; +} + +// defaults to the whatever keyboard happens to be first in the list +- (void) setDefaultSelectedKeyboard { + NSMenuItem* menuItem = [self.menu itemAtIndex:KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX]; + NSString *keyboardName = [self.activeKeyboards objectAtIndex:0]; + [self setSelectedKeyboard:keyboardName inMenuItem:menuItem]; + [self setSelectedKeyboard:keyboardName]; + [self setContextBuffer:nil]; +} + +- (void) addKeyboardPlaceholderMenuItem { + NSString* placeholder = NSLocalizedString(@"no-keyboard-configured-menu-placeholder", nil); + NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:placeholder action:NULL keyEquivalent:@""]; + [self.menu insertItem:item atIndex:KEYMAN_FIRST_KEYBOARD_MENUITEM_INDEX]; + [self setKmx:nil]; + [self setKvk:nil]; + [self setKeyboardName:nil]; + [self setKeyboardIcon:nil]; + [self setContextBuffer:nil]; + [self setSelectedKeyboard:nil]; +} + +- (void)selectKeyboardFromMenu:(NSInteger)tag { + NSMenuItem *menuItem = [self.menu itemWithTag:tag]; + NSString *title = menuItem.title; + NSLog(@"Input Menu, selected Keyboards menu, itag: %lu, title: %@", tag, title); + for (NSMenuItem *item in self.menu.itemArray) { + // set the state of the keyboard items in the Keyman menu based on the new selection + if (item.tag >= KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG) { + if (item.tag == tag) { + [item setState:NSOnState]; + } + else { + [item setState:NSOffState]; } - [self setKvk:kvk]; - [self setKeyboardName:[kmxInfo objectForKey:kKMKeyboardNameKey]]; - [self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; - [self setContextBuffer:nil]; - [self loadSavedStores]; - [self setSelectedKeyboard:path]; } } + + NSString *path = [self.activeKeyboards objectAtIndex:tag-KEYMAN_FIRST_KEYBOARD_MENUITEM_TAG]; + KMXFile *kmx = [[KMXFile alloc] initWithFilePath:path]; + [self setKmx:kmx]; + KVKFile *kvk = nil; + NSDictionary *kmxInfo = [KMXFile keyboardInfoFromKmxFile:path]; + NSString *kvkFilename = [kmxInfo objectForKey:kKMVisualKeyboardKey]; + if (kvkFilename != nil) { + NSString *kvkFilePath = [self kvkFilePathFromFilename:kvkFilename]; + if (kvkFilePath != nil) + kvk = [[KVKFile alloc] initWithFilePath:kvkFilePath]; + } + [self setKvk:kvk]; + NSString *keyboardName = [kmxInfo objectForKey:kKMKeyboardNameKey]; + if ([self debugMode]) + NSLog(@"Selected keyboard from menu: %@", keyboardName); + [self setKeyboardName:keyboardName]; + [self setKeyboardIcon:[kmxInfo objectForKey:kKMKeyboardIconKey]]; + [self setContextBuffer:nil]; + [self setSelectedKeyboard:path]; + [self loadSavedStores]; + if (kvk != nil && self.alwaysShowOSK) + [self showOSK]; } - (NSArray *)KMXFiles { diff --git a/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/Localizable.strings b/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/Localizable.strings index f14a66b0b4a..6b815a7b176 100644 --- a/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/Localizable.strings +++ b/mac/Keyman4MacIM/Keyman4MacIM/en.lproj/Localizable.strings @@ -71,4 +71,6 @@ /* message displayed to alert user to need grant accessibility permission */ "privacy-alert-text" = "To function properly, Keyman requires accessibility features:\n\nGrant access in System Preferences, Security & Privacy.\nRestart your system."; +/* Text of menu item in Input Menu when no Keyboards are configured -- include parentheses */ +"no-keyboard-configured-menu-placeholder" = "(No Keyboard Configured)";