IDEA-163208: initial impl of touchbar support

NOTES: объект-делегат (для создания TB-итемов по запросу) определен в нативной либе, присоединяется к NSApplication в момент инициализации; в качестве альтернативы можно создавать и настраивать объекты типа TouchBar и TouchBarItem через Foundation, однако такое решение выглядит менее гибким (+ неудобочитаемый и некомпактный код)
This commit is contained in:
Artem Bochkarev
2018-03-06 14:31:12 +07:00
parent 49c6b5e9ce
commit dd7a334894
8 changed files with 278 additions and 0 deletions
BIN
View File
Binary file not shown.
+117
View File
@@ -0,0 +1,117 @@
#import <Cocoa/Cocoa.h>
typedef void (*callback)(void);
@interface ItemDesc : NSObject
{
NSString * uid;
NSString * type;
NSString * text;
callback action;
}
- (id)init:(char*)puid type:(char*)ptype text:(char*)ptext act:(callback)act;
// TODO: make props non-atomic
@property (readonly) NSString * getUid;
@property (readonly) NSString * getType;
@property (readonly) NSString * getText;
@property (readonly) callback getAction;
@end
@implementation ItemDesc
- (id)init:(char*)puid type:(char*)ptype text:(char*)ptext act:(callback)act {
self = [super init];
if (self) {
uid = [NSString stringWithUTF8String:puid];
type = [NSString stringWithUTF8String:ptype];
text = [NSString stringWithUTF8String:ptext];
action = act;
}
return self;
}
@synthesize getUid = uid, getType = type, getText = text, getAction = action;
@end
static NSMutableDictionary *g_uid2desc = nil;
void registerItem(char* uid, char* type, char* text, callback action) {
ItemDesc * idesc = [[ItemDesc alloc] init:uid type:type text:text act:action];
if (idesc == nil) {
// TODO: log error
return;
}
if (g_uid2desc == nil)
g_uid2desc = [[NSMutableDictionary alloc] init];
g_uid2desc[idesc.getUid] = idesc;
}
@interface NSButtonWithID : NSButton
{
NSString * uid;
}
@property (retain) NSString * uid;
@end
@implementation NSButtonWithID
@synthesize uid;
@end
@interface NSTDelegate : NSObject<NSTouchBarDelegate>
- (void)buttonExec:(id)sender;
@end
// TODO: implement normal error logging
@implementation NSTDelegate
// This gets called while the NSTouchBar is being constructed, for each NSTouchBarItem to be created.
- (nullable NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier
{
if (g_uid2desc == nil) {
NSLog(@"ERROR: try makeTouchBarItem for item '%@', but global items registry is empty", identifier);
return nil;
}
ItemDesc * idesc = g_uid2desc[identifier];
if (idesc == nil) {
NSLog(@"ERROR: called makeTouchBarItem for item '%@' that wasn't registered", identifier);
return nil;
}
if ([idesc.getType isEqualToString:@"label"]) {
NSTextField *theLabel = [NSTextField labelWithString:NSLocalizedString(@"MyLabel", @"")];
NSCustomTouchBarItem *customItemForLabel =
[[NSCustomTouchBarItem alloc] initWithIdentifier:idesc.getText];
customItemForLabel.view = theLabel;
// We want this label to always be visible no matter how many items are in the NSTouchBar instance.
customItemForLabel.visibilityPriority = NSTouchBarItemPriorityHigh;
return customItemForLabel;
}
if ([idesc.getType isEqualToString:@"button"]) {
SEL selAction = @selector(buttonExec:);
NSButtonWithID *theButton = [NSButtonWithID buttonWithTitle:idesc.getText target:self action:selAction];
theButton.uid = idesc.getUid;
NSCustomTouchBarItem *customItemForButton =
[[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
customItemForButton.view = theButton;
// We want this label to always be visible no matter how many items are in the NSTouchBar instance.
customItemForButton.visibilityPriority = NSTouchBarItemPriorityHigh;
return customItemForButton;
}
NSLog(@"ERROR: called makeTouchBarItem for item '%@' that has unsopported type '%@'", identifier, idesc.getType);
return nil;
}
- (void)buttonExec:(id)sender {
NSButtonWithID * butSender = (NSButtonWithID *)sender;
ItemDesc * idesc = g_uid2desc[butSender.uid];
(*idesc.getAction)();
}
@end
@@ -22,6 +22,7 @@ import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.platform.PlatformProjectOpenProcessor;
import com.intellij.ui.mac.foundation.Foundation;
import com.intellij.ui.mac.foundation.ID;
import com.intellij.ui.mac.touchbar.TouchBar;
import com.sun.jna.Callback;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -136,6 +137,7 @@ public class MacOSApplicationProvider {
});
});
installAutoUpdateMenu();
TouchBar.initialize();
}
private static void installAutoUpdateMenu() {
@@ -0,0 +1,8 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide;
import com.sun.jna.Library;
public interface NSTLibrary extends Library {
void registerItem(String uid, String type, String text, TBItemCallback action);
}
@@ -0,0 +1,36 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.application.ApplicationManager;
import com.sun.jna.Callback;
import java.awt.*;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import static java.awt.event.ComponentEvent.COMPONENT_FIRST;
public class TBItemCallback implements Callback {
final String myActionId;
public TBItemCallback(String actionId) {
myActionId = actionId;
}
public void callback () {
ApplicationManager.getApplication().invokeLater(() -> _performAction());
}
private void _performAction() {
final ActionManagerEx actionManagerEx = ActionManagerEx.getInstanceEx();
final AnAction act = ActionManager.getInstance().getAction(myActionId);
final KeyboardFocusManager focusManager=KeyboardFocusManager.getCurrentKeyboardFocusManager();
final Component focusOwner = focusManager.getFocusedWindow();
// TODO: create key-event with proper key-codes OR try to use ActionCommand.getInputEvent
InputEvent ie = new KeyEvent(focusOwner, COMPONENT_FIRST, System.currentTimeMillis(), 0, 0, '\0');
actionManagerEx.tryToExecute(act, ie, focusOwner, ActionPlaces.UNKNOWN, false);
}
}
@@ -0,0 +1,103 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui.mac.touchbar;
import com.intellij.ide.TBItemCallback;
import com.intellij.ide.NSTLibrary;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.ui.mac.foundation.Foundation;
import com.intellij.ui.mac.foundation.ID;
import com.intellij.util.lang.UrlClassLoader;
import com.sun.jna.Native;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
public class TouchBar {
private static final Logger ourLog = Logger.getInstance(TouchBar.class);
private static final NSTLibrary ourNSTLibrary;
private static final Map<String, TBItemCallback> ourActions = new HashMap<>();
static {
// TODO: check macOS version with use of CoreServices:
// SInt32 major, minor, bugfix;
// Gestalt(gestaltSystemVersionMajor, &major);
// Gestalt(gestaltSystemVersionMinor, &minor);
// Gestalt(gestaltSystemVersionBugFix, &bugfix);
// NSString *systemVersion = [NSString stringWithFormat:@"%d.%d.%d", major, minor, bugfix];
// NOTE: can also check existence of process 'ControlStrip' to determine touchbar availability
final boolean isTouchbarAvailable = SystemInfo.isMac && Registry.is("ide.mac.touchbar.use", false);
NSTLibrary lib = null;
if (isTouchbarAvailable) {
try {
UrlClassLoader.loadPlatformLibrary("nst");
// Set JNA to convert java.lang.String to char* using UTF-8, and match that with
// the way we tell CF to interpret our char*
// May be removed if we use toStringViaUTF16
System.setProperty("jna.encoding", "UTF8");
final Map<String, Object> nstOptions = new HashMap<>();
lib = Native.loadLibrary("nst", NSTLibrary.class, nstOptions);
} catch (Throwable e) {
ourLog.error("Failed to load nst library for touchbar: ", e);
}
}
ourNSTLibrary = lib;
// TODO: must show only functional-keys when corresponding settings key is true
}
public static boolean isAvailable() { return ourNSTLibrary != null; }
public static void initialize() {
if (!isAvailable())
return;
final String[] testItems = new String[] {"Run"};
final String[] testItemIds = new String[testItems.length];
// fill available actions
// register action on native side
for (int c = 0; c < testItems.length; ++c) {
String key = testItems[c];
final TBItemCallback act = new TBItemCallback(key);
ourActions.put(key, act);
final String uid = "button."+key;
testItemIds[c] = uid;
ourNSTLibrary.registerItem(uid, "button", key, act);
}
final ID pool = Foundation.invoke("NSAutoreleasePool", "new");
try {
final ID app = Foundation.invoke("NSApplication", "sharedApplication");
Foundation.invoke(app, "setAutomaticCustomizeTouchBarMenuItemEnabled:", true);
final ID tb = Foundation.invoke(Foundation.invoke("NSTouchBar", "alloc"), "init");
final ID tbd = Foundation.invoke(Foundation.invoke("NSTDelegate", "alloc"), "init");
Foundation.invoke(tb, "setDelegate:", tbd);
final Object[] nsTestItemIds = _str2ids(testItemIds);
final ID items = Foundation.invoke("NSArray", "arrayWithObjects:", nsTestItemIds);
Foundation.invoke(tb, "setDefaultItemIdentifiers:", items);
// TODO: select best placement in responder-chain (probably need attach to main-window controller)
Foundation.invoke(app, "setTouchBar:", tb);
} finally {
Foundation.invoke(pool, "release");
}
}
private static @NotNull Object[] _str2ids(@NotNull String[] strs) {
Object[] result = new Object[strs.length];
for (int c = 0; c < strs.length; ++c)
result[c] = Foundation.nsString(strs[c]);
return result;
}
}
@@ -0,0 +1,10 @@
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ui.mac.touchbar;
public class TouchBarItem {
final String myName;
public TouchBarItem(String name) {
myName = name;
}
}
@@ -1204,6 +1204,8 @@ markdown.clear.cache.interval=600000
ide.mac.new.color.picker=false
ide.mac.touchbar.use = false
trace.focus.on.app.activation=false
trace.focus.on.app.activation.description=Focus tracing on application activation and deactivation
trace.focus.on.app.restartRequired=false