mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
also hide run-buttons when configuration isn't defined fixed point 3 from IDEA-194893 MacBook touch bar: improve Configurations popover
425 lines
16 KiB
Objective-C
425 lines
16 KiB
Objective-C
#import <Foundation/Foundation.h>
|
|
#import <Cocoa/Cocoa.h>
|
|
#import "JTypes.h"
|
|
#import "Utils.h"
|
|
|
|
const NSSize g_defaultMinSize = {72, 30}; // empiric value
|
|
|
|
@interface NSButtonJAction : NSButton
|
|
@property (nonatomic) NSButtonType btype;
|
|
@property (nonatomic) CGFloat bwidth;
|
|
@property (nonatomic) execute jaction;
|
|
@property (nonatomic) NSString * uid; // for debug only
|
|
- (id)init;
|
|
- (void)doAction;
|
|
+ (Class)cellClass;
|
|
@end
|
|
|
|
@interface NSButtonCellEx : NSButtonCell
|
|
@property (nonatomic) CGFloat myBorder;
|
|
@property (nonatomic) CGFloat myMargin;
|
|
@property (retain) NSImage * myArrowImg;
|
|
@end
|
|
|
|
@implementation NSButtonJAction
|
|
- (id)init {
|
|
self = [super init];
|
|
if (self) {
|
|
self.btype = NSButtonTypeMomentaryLight;
|
|
self.bwidth = 0;
|
|
NSCell * cell = [self cell];
|
|
[cell setLineBreakMode:NSLineBreakByTruncatingTail];
|
|
[self setBezelStyle:NSRoundedBezelStyle];
|
|
[self setMargins:3 border:8];
|
|
// NSLog(@"created button [%@]: cell-class=%@", self, [[self cell] className]);
|
|
}
|
|
return self;
|
|
}
|
|
- (void)setMargins:(int)margin border:(int)border {
|
|
NSCell * cell = [self cell];
|
|
if ([cell isKindOfClass:[NSButtonCellEx class]]) {
|
|
NSButtonCellEx * cellEx = cell;
|
|
cellEx.myBorder = border;
|
|
cellEx.myMargin = margin;
|
|
} else
|
|
nserror(@"setMargin mustn't be called because internal cell isn't kind of NSButtonCellEx");
|
|
}
|
|
- (void)setArrowImg:(NSImage *)arrowImg {
|
|
NSCell * cell = [self cell];
|
|
if ([cell isKindOfClass:[NSButtonCellEx class]]) {
|
|
NSButtonCellEx * cellEx = cell;
|
|
cellEx.myArrowImg = arrowImg;
|
|
} else
|
|
nserror(@"setArrowImg mustn't be called because internal cell isn't kind of NSButtonCellEx");
|
|
}
|
|
- (void)doAction {
|
|
if (self.jaction) {
|
|
nstrace(@"button [%@]: doAction", self);
|
|
(*self.jaction)();
|
|
} else
|
|
nstrace(@"button [%@]: empty action, nothing to execute", self);
|
|
}
|
|
// Uncomment for visual debug
|
|
//
|
|
//- (void) drawRect:(NSRect)dirtyRect {
|
|
// [[NSGraphicsContext currentContext] saveGraphicsState];
|
|
//
|
|
//// NSColor* backgroundColor = [NSColor clearColor];
|
|
//// NSColor* backgroundColor = [NSColor yellowColor];
|
|
//// [backgroundColor setFill];
|
|
//// NSRectFill(dirtyRect);
|
|
//
|
|
// [NSGraphicsContext restoreGraphicsState];
|
|
//
|
|
// NSLog(@"drawRect [%@]: %@", self.uid, NSStringFromRect(dirtyRect));
|
|
// [super drawRect:dirtyRect];
|
|
//}
|
|
|
|
+ (Class)cellClass
|
|
{
|
|
return [NSButtonCellEx class];
|
|
}
|
|
@end
|
|
|
|
@implementation NSButtonCellEx
|
|
// Uncomment for visual debug
|
|
//
|
|
//-(void)drawBezelWithFrame:(NSRect)frame inView:(NSView *)controlView
|
|
//{
|
|
// NSLog(@"\tdrawBezelWithFrame: %@", NSStringFromRect(frame));
|
|
// [super drawBezelWithFrame:frame inView:controlView];
|
|
//
|
|
// [[NSGraphicsContext currentContext] saveGraphicsState];
|
|
//
|
|
// CGFloat dashPattern[] = {6,4}; //make your pattern here
|
|
// NSBezierPath *textViewSurround = [NSBezierPath bezierPathWithRoundedRect:frame xRadius:10 yRadius:10];
|
|
// [textViewSurround setLineWidth:1.0f];
|
|
// [textViewSurround setLineDash:dashPattern count:2 phase:0];
|
|
// [[NSColor colorWithRed:105/255.0 green:211/255.0 blue:232/255.0 alpha:1.0] set];
|
|
// [textViewSurround stroke];
|
|
//
|
|
// [NSGraphicsContext restoreGraphicsState];
|
|
//}
|
|
//- (void)drawDebugRect:(NSRect)frame color:(NSColor *)col {
|
|
// // NSLog(@"drawDebugRect: %@", NSStringFromRect(frame));
|
|
//
|
|
// [[NSGraphicsContext currentContext] saveGraphicsState];
|
|
//
|
|
// if (col == nil)
|
|
// col = [NSColor colorWithRed:105/255.0 green:211/255.0 blue:232/255.0 alpha:1.0];
|
|
// [col set];
|
|
// [col setStroke];
|
|
// [col setFill];
|
|
// NSFrameRect(frame);
|
|
//
|
|
// [NSGraphicsContext restoreGraphicsState];
|
|
//}
|
|
- (NSSize)cellSizeForBounds:(NSRect)rect {
|
|
if (self.title == nil) {
|
|
// NSLog(@"\t empty text, use default size %@", NSStringFromSize(g_defaultMinSize));
|
|
return g_defaultMinSize;
|
|
}
|
|
const NSSize txtSize = [self.title sizeWithAttributes:@{ NSFontAttributeName:self.font }];
|
|
const CGFloat imgW = self.image == nil ? 0 : self.image.size.width;
|
|
NSSize result = g_defaultMinSize;
|
|
result.width = txtSize.width + imgW + 2*_myBorder + _myMargin;
|
|
if (result.width < g_defaultMinSize.width)
|
|
result.width = g_defaultMinSize.width;
|
|
// NSLog(@"\t text='%@', text-size = %@, result = %@", self.title, NSStringFromSize(txtSize), NSStringFromSize(result));
|
|
return result;
|
|
}
|
|
- (void)drawInteriorWithFrame:(NSRect)frame inView:(NSView *)controlView {
|
|
// NSLog(@"\tdrawInteriorWithFrame: %@", NSStringFromRect(frame));
|
|
if (self.title == nil || self.title.length <= 0) {
|
|
[super drawImage:self.image withFrame:frame inView:controlView];
|
|
return;
|
|
}
|
|
|
|
const CGFloat imgW = self.image == nil ? 0 : self.image.size.width;
|
|
const CGFloat arrowImgW = self.myArrowImg == nil ? 0 : self.myArrowImg.size.width;
|
|
const CGFloat arrowMarginL = 2;
|
|
const CGFloat arrowMarginR = 2;
|
|
|
|
const CGFloat imgWithMargin = imgW > 0 ? self.myMargin + imgW : 0;
|
|
const CGFloat arrowImgWithMargins = arrowImgW > 0 ? arrowMarginL + arrowImgW + arrowMarginR : 0;
|
|
|
|
NSSize txtSize = [self.title sizeWithAttributes:@{ NSFontAttributeName:self.font }];
|
|
const CGFloat txtW = txtSize.width;
|
|
const CGFloat borderL = self.myBorder;
|
|
const CGFloat borderR = arrowImgW > 0 ? 2 : self.myBorder;
|
|
const CGFloat fullContentW = borderL + imgWithMargin + txtW + borderR;
|
|
const CGFloat availableWidth = frame.size.width - arrowImgWithMargins;
|
|
NSRect rcImg = frame;
|
|
rcImg.size.width = imgW;
|
|
NSRect rcTxt = frame;
|
|
if (fullContentW <= availableWidth) {
|
|
const CGFloat delta = availableWidth - fullContentW;
|
|
if (imgW > 0) {
|
|
rcImg.origin.x = delta/2 + borderL;
|
|
rcTxt.origin.x = rcImg.origin.x + imgWithMargin;
|
|
} else {
|
|
rcTxt.origin.x = delta/2 + borderL;
|
|
}
|
|
rcTxt.size.width = txtW + 1;
|
|
} else {
|
|
if (imgW > 0) {
|
|
rcImg.origin.x = borderL;
|
|
rcTxt.origin.x = rcImg.origin.x + imgWithMargin;
|
|
rcTxt.size.width = availableWidth - borderR - rcTxt.origin.x;
|
|
} else {
|
|
rcTxt.origin.x = borderL;
|
|
rcTxt.size.width = availableWidth - borderL - borderR;
|
|
}
|
|
}
|
|
|
|
if (arrowImgW > 0) {
|
|
NSRect rcArrow = frame;
|
|
rcArrow.size.width = arrowImgW;
|
|
rcArrow.origin.x = frame.size.width - arrowMarginR - arrowImgW;
|
|
[super drawImage:self.myArrowImg withFrame:rcArrow inView:controlView];
|
|
}
|
|
|
|
if (imgW > 0) {
|
|
[super drawImage:self.image withFrame:rcImg inView:controlView];
|
|
// NSColor * col = [NSColor colorWithRed:105/255.0 green:211/255.0 blue:0/255.0 alpha:1.0];
|
|
// [self drawDebugRect:rcImg color:col];
|
|
}
|
|
if (rcTxt.size.width > 0) {
|
|
[super drawTitle:self.attributedTitle withFrame:rcTxt inView:controlView];
|
|
// NSColor * col = [NSColor colorWithRed:105/255.0 green:0/255.0 blue:255/255.0 alpha:1.0];
|
|
// [self drawDebugRect:rcTxt color:col];
|
|
}
|
|
// [self drawDebugRect:frame color:nil];
|
|
}
|
|
@end
|
|
|
|
const int BUTTON_UPDATE_LAYOUT = 1;
|
|
const int BUTTON_UPDATE_FLAGS = 1 << 1;
|
|
const int BUTTON_UPDATE_TEXT = 1 << 2;
|
|
const int BUTTON_UPDATE_IMG = 1 << 3;
|
|
const int BUTTON_UPDATE_ACTION = 1 << 4;
|
|
const int BUTTON_UPDATE_ALL = ~0;
|
|
|
|
const int BUTTON_FLAG_DISABLED = 1;
|
|
const int BUTTON_FLAG_SELECTED = 1 << 1;
|
|
const int BUTTON_FLAG_COLORED = 1 << 2;
|
|
const int BUTTON_FLAG_TOGGLE = 1 << 3;
|
|
const int BUTTON_FLAG_TRANSPARENT_BG = 1 << 4;
|
|
|
|
const unsigned int LAYOUT_WIDTH_MASK = 0x0FFF;
|
|
const unsigned int LAYOUT_FLAG_MIN_WIDTH = 1 << 15;
|
|
const unsigned int LAYOUT_FLAG_MAX_WIDTH = 1 << 14;
|
|
|
|
const unsigned int LAYOUT_MARGIN_SHIFT = 2*8;
|
|
const unsigned int LAYOUT_MARGIN_MASK = 0xFF << LAYOUT_MARGIN_SHIFT;
|
|
const unsigned int LAYOUT_BORDER_SHIFT = 3*8;
|
|
const unsigned int LAYOUT_BORDER_MASK = 0xFF << LAYOUT_BORDER_SHIFT;
|
|
|
|
const int BUTTON_PRIORITY_SHIFT = 3*8;
|
|
const unsigned int BUTTON_PRIORITY_MASK = 0xFF << BUTTON_PRIORITY_SHIFT;
|
|
|
|
static int _getPriority(int flags) {
|
|
return (flags & BUTTON_PRIORITY_MASK) >> BUTTON_PRIORITY_SHIFT;
|
|
}
|
|
|
|
static int _getWidth(int layoutBits) {
|
|
return (layoutBits & LAYOUT_WIDTH_MASK);
|
|
}
|
|
|
|
static int _getMargin(int layoutBits) {
|
|
return (layoutBits & LAYOUT_MARGIN_MASK) >> LAYOUT_MARGIN_SHIFT;
|
|
}
|
|
|
|
static int _getBorder(int layoutBits) {
|
|
return (layoutBits & LAYOUT_BORDER_MASK) >> LAYOUT_BORDER_SHIFT;
|
|
}
|
|
|
|
static void _setButtonData(NSButtonJAction *button, int updateOptions, int layoutBits, int buttonFlags, NSString *nstext, NSImage *img, execute jaction) {
|
|
if (updateOptions & BUTTON_UPDATE_ACTION) {
|
|
button.jaction = jaction;
|
|
if (jaction) {
|
|
[button setTarget:button];
|
|
[button setAction:@selector(doAction)];
|
|
[button setEnabled:YES];
|
|
} else {
|
|
[button setTarget:nil];
|
|
[button setAction:NULL];
|
|
[button setEnabled:NO];
|
|
}
|
|
}
|
|
|
|
if (updateOptions & BUTTON_UPDATE_TEXT) {
|
|
if (nstext != nil) {
|
|
[button setFont:[NSFont systemFontOfSize:0]];
|
|
}
|
|
[button setTitle:nstext];
|
|
}
|
|
|
|
if (updateOptions & BUTTON_UPDATE_IMG)
|
|
[button setImage:img];
|
|
|
|
if (updateOptions & BUTTON_UPDATE_LAYOUT) {
|
|
const int width = _getWidth(layoutBits);
|
|
button.bwidth = width;
|
|
if (width > 0) {
|
|
bool isMinWidth = (layoutBits & LAYOUT_FLAG_MIN_WIDTH) != 0;
|
|
bool isMaxWidth = (layoutBits & LAYOUT_FLAG_MAX_WIDTH) != 0;
|
|
if (isMinWidth && isMaxWidth) {
|
|
nserror(@"invalid arguments specified: both min and max bits are 1");
|
|
}
|
|
if (isMinWidth) {
|
|
//NSLog(@"set min width %d", width);
|
|
[button.widthAnchor constraintGreaterThanOrEqualToConstant:button.bwidth].active = YES;
|
|
} else if (isMaxWidth) {
|
|
//NSLog(@"set max width %d", width);
|
|
[button.widthAnchor constraintLessThanOrEqualToConstant:button.bwidth].active = YES;
|
|
} else {
|
|
//NSLog(@"set const width %d", width);
|
|
[button.widthAnchor constraintEqualToConstant:button.bwidth].active = YES;
|
|
}
|
|
} else
|
|
[button.widthAnchor constraintEqualToAnchor:button.widthAnchor].active = NO;
|
|
|
|
{ // process margins
|
|
const int margin = _getMargin(layoutBits);
|
|
const int border = _getBorder(layoutBits);
|
|
if (margin > 0 || border > 0) {
|
|
[button setMargins:margin border:border];
|
|
//NSLog(@"set insets: m=%d b=%d", margin, border);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updateOptions & BUTTON_UPDATE_FLAGS) {
|
|
const bool toggle = (buttonFlags & BUTTON_FLAG_TOGGLE) != 0;
|
|
const enum NSButtonType btype = toggle ? NSButtonTypePushOnPushOff : NSButtonTypeMomentaryLight;
|
|
if (btype != button.btype) {
|
|
[button setButtonType:btype];
|
|
if (toggle) {
|
|
button.bezelColor = NSColor.selectedControlColor;
|
|
} else {
|
|
button.bezelColor = NSColor.controlColor;
|
|
}
|
|
}
|
|
|
|
if (buttonFlags & BUTTON_FLAG_COLORED) {
|
|
button.bezelColor = [NSColor colorWithRed:0 green:130/255.f blue:215/255.f alpha:1];
|
|
} else if (buttonFlags & BUTTON_FLAG_SELECTED) {
|
|
if (toggle) {
|
|
button.state = NSOnState;
|
|
} else {
|
|
button.bezelColor = NSColor.selectedControlColor;
|
|
}
|
|
} else {
|
|
if (toggle) {
|
|
button.state = NSOffState;
|
|
} else {
|
|
button.bezelColor = NSColor.controlColor;
|
|
}
|
|
}
|
|
|
|
const bool enabled = (buttonFlags & BUTTON_FLAG_DISABLED) == 0;
|
|
if (enabled != button.enabled) {
|
|
[button setEnabled:enabled];
|
|
}
|
|
|
|
if (buttonFlags & BUTTON_FLAG_TRANSPARENT_BG)
|
|
[button setBordered:NO];
|
|
}
|
|
|
|
if (button.image != nil) {
|
|
if (button.title != nil && button.title.length > 0)
|
|
[button setImagePosition:NSImageLeft];
|
|
else
|
|
[button setImagePosition:NSImageOnly];
|
|
} else {
|
|
[button setImagePosition:NSNoImage];
|
|
}
|
|
}
|
|
|
|
// NOTE: called from AppKit-thread (creation when TB becomes visible), uses default autorelease-pool (create before event processing)
|
|
id createButton(
|
|
const char *uid,
|
|
int layoutBits,
|
|
int buttonFlags,
|
|
const char *text,
|
|
const char *raster4ByteRGBA, int w, int h,
|
|
execute jaction
|
|
) {
|
|
NSString *nsUid = createStringFromUTF8(uid);
|
|
// NSLog(@"create button [%@] (thread: %@)", nsUid, [NSThread currentThread]);
|
|
|
|
NSCustomTouchBarItem *customItemForButton = [[NSCustomTouchBarItem alloc] initWithIdentifier:nsUid]; // create non-autorelease object to be owned by java-wrapper
|
|
NSButtonJAction *button = [[[NSButtonJAction alloc] init] autorelease];
|
|
button.uid = nsUid;
|
|
|
|
NSImage *img = createImgFrom4ByteRGBA((const unsigned char *) raster4ByteRGBA, w, h);
|
|
NSString *nstext = createStringFromUTF8(text);
|
|
_setButtonData(button, BUTTON_UPDATE_ALL, layoutBits, buttonFlags, nstext, img, jaction);
|
|
customItemForButton.view = button; // NOTE: view is strong
|
|
|
|
const int prio = _getPriority(buttonFlags);
|
|
if (prio != 0) {
|
|
// empiric: when prio <= -1.f then item won't be shown when at least 1 item with 0-priority (normal, default) presented => use interval from -0.5 to 0.5
|
|
customItemForButton.visibilityPriority = (float)(prio - 127)/256;
|
|
// NSLog(@"item [%@], prio=%1.5f", nsUid, customItemForButton.visibilityPriority);
|
|
}
|
|
return customItemForButton;
|
|
}
|
|
|
|
// NOTE: called from EDT (when update UI)
|
|
void updateButton(
|
|
id buttObj,
|
|
int updateOptions,
|
|
int layoutBits,
|
|
int buttonFlags,
|
|
const char *text,
|
|
const char *raster4ByteRGBA, int w, int h,
|
|
execute jaction
|
|
) {
|
|
NSCustomTouchBarItem *container = buttObj; // TODO: check types
|
|
NSButtonJAction *button = (container).view; // TODO: check types
|
|
|
|
NSAutoreleasePool *edtPool = [NSAutoreleasePool new];
|
|
NSImage *img = createImgFrom4ByteRGBA((const unsigned char *) raster4ByteRGBA, w, h);
|
|
NSString *nstext = createStringFromUTF8(text);
|
|
|
|
if ([NSThread isMainThread]) {
|
|
nstrace(@"sync update button [%@] (main thread: %@)", container.identifier, [NSThread currentThread]);
|
|
_setButtonData(button, updateOptions, layoutBits, buttonFlags, nstext, img, jaction);
|
|
} else {
|
|
nstrace(@"async update button [%@] (thread: %@)", container.identifier, [NSThread currentThread]);
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// NOTE: block is copied, img/text objects is automatically retained
|
|
// nstrace(@"\tperform update button [%@] (thread: %@)", container.identifier, [NSThread currentThread]);
|
|
_setButtonData(button, updateOptions, layoutBits, buttonFlags, nstext, img, jaction);
|
|
});
|
|
}
|
|
|
|
[edtPool release];
|
|
}
|
|
|
|
// NOTE: now is called from AppKit-thread (creation when TB becomes visible), but can be called from EDT (when update UI)
|
|
void setArrowImage(id buttObj, const char *raster4ByteRGBA, int w, int h) {
|
|
NSCustomTouchBarItem *container = buttObj; // TODO: check types
|
|
NSButtonJAction *button = (container).view; // TODO: check types
|
|
NSAutoreleasePool *edtPool = [NSAutoreleasePool new];
|
|
|
|
NSImage *img = nil;
|
|
if (raster4ByteRGBA != NULL && w > 0)
|
|
img = createImgFrom4ByteRGBA((const unsigned char *) raster4ByteRGBA, w, h);
|
|
|
|
if ([NSThread isMainThread]) {
|
|
[button setArrowImg:img];
|
|
//NSLog(@"sync set arrow: w=%d h=%d", w, h);
|
|
} else {
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// NOTE: block is copied, img/text objects is automatically retained
|
|
// nstrace(@"\tperform update button [%@] (thread: %@)", container.identifier, [NSThread currentThread]);
|
|
[button setArrowImg:img];
|
|
//NSLog(@"async set arrow: w=%d h=%d", w, h);
|
|
});
|
|
}
|
|
|
|
[edtPool release];
|
|
} |