library/forms/cocoa/MHudController.mm (141 lines of code) (raw):

/* * Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, * as published by the Free Software Foundation. * * This program is designed to work with certain software (including * but not limited to OpenSSL) that is licensed under separate terms, as * designated in a particular file or component or in included license * documentation. The authors of MySQL hereby grant you an additional * permission to link the program and your derivative works with the * separately licensed software that they have either included with * the program or referenced in the documentation. * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU General Public License, version 2.0, for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #import "MHudController.h" //---------------------------------------------------------------------------------------------------------------------- @interface MHudController() { IBOutlet NSPanel* hudPanel; IBOutlet NSTextField* shortHudDescription; IBOutlet NSTextField* longHudDescription; IBOutlet NSButton* cancelButton; NSModalSession modalSession; BOOL stopped; std::function<bool ()> cancelAction; NSMutableArray *nibObjects; } @end //---------------------------------------------------------------------------------------------------------------------- @implementation MHudController static MHudController* instance = nil; - (instancetype)init { self = [super init]; if (self != nil) { NSMutableArray *temp; if ([NSBundle.mainBundle loadNibNamed: @"HUDPanel" owner: self topLevelObjects: &temp]) { nibObjects = temp; [hudPanel setBecomesKeyOnlyIfNeeded: YES]; } } return self; } //---------------------------------------------------------------------------------------------------------------------- - (void)dealloc { [NSObject cancelPreviousPerformRequestsWithTarget: self]; } //---------------------------------------------------------------------------------------------------------------------- + (void)showHudWithTitle: (NSString*) title andDescription: (NSString*) description { // TODO: perhaps this should be made thread safe (even tho it must never be called outside the main thread)? if (instance == nil) instance = [[MHudController alloc] init]; NSWindow* mainWindow = NSApplication.sharedApplication.mainWindow; if (mainWindow != nil) { // The applications main window can be nil if the app has not finished loading or is hidden. // In those cases we don't need to show the hud either. NSRect parentFrame = mainWindow.frame; NSSize popupSize = instance.hud.frame.size; NSRect newFrame = NSMakeRect(parentFrame.origin.x + (parentFrame.size.width - popupSize.width) / 2, parentFrame.origin.y + (parentFrame.size.height - popupSize.height) / 2, popupSize.width, popupSize.height); [instance->cancelButton setHidden: YES]; [instance showAnimatedWithFrame: newFrame title: title andDescription: description]; } } //---------------------------------------------------------------------------------------------------------------------- static NSCondition *modalLoopRunningCond = nil; static BOOL modalHUDRunning = NO; //---------------------------------------------------------------------------------------------------------------------- + (void)initialize { modalLoopRunningCond = [[NSCondition alloc] init]; } //---------------------------------------------------------------------------------------------------------------------- + (BOOL)runModalHudWithTitle: (NSString*) title andDescription: (NSString*) description notifyReady: (std::function<void ()>)signalReady cancelAction: (std::function<bool ()>)cancelAction { if (instance == nil) instance = [[MHudController alloc] init]; NSWindow* mainWindow = [NSApplication sharedApplication].mainWindow; // The applications main window can be nil if the app has not finished loading or is hidden. NSRect parentFrame = mainWindow ? mainWindow.frame : [NSScreen mainScreen].frame; NSSize popupSize = instance.hud.frame.size; NSRect newFrame = NSMakeRect(parentFrame.origin.x + (parentFrame.size.width - popupSize.width) / 2, parentFrame.origin.y + (parentFrame.size.height - popupSize.height) / 2, popupSize.width, popupSize.height); instance->cancelAction = cancelAction; [instance->cancelButton setHidden: NO]; [instance->cancelButton setNeedsDisplay: YES]; instance->stopped = NO; [instance showAnimatedWithFrame: newFrame title: title andDescription: description]; [modalLoopRunningCond lock]; instance->modalSession = [NSApp beginModalSessionForWindow: instance.hud]; modalHUDRunning = YES; [modalLoopRunningCond signal]; [modalLoopRunningCond unlock]; if (signalReady) signalReady(); NSInteger ret = -1; // Can't use runModalForWindow because it will just block until some event happens // (like mouse move), even after stopModal is called. for (;;) { if (instance->stopped || (ret = [NSApp runModalSession: instance->modalSession]) != NSModalResponseContinue) break; usleep(1000); } modalHUDRunning = NO; [NSApp endModalSession: instance->modalSession]; instance->modalSession = nil; // Make sure shared_refs bound to it are not kept dangling. instance->cancelAction = std::function<bool()>(); if (ret == NSModalResponseAbort) { [instance hideAnimated]; return NO; // cancelled } [instance hideAnimated]; return YES; } //---------------------------------------------------------------------------------------------------------------------- + (void)stopModalHud { if (instance) { modalHUDRunning = NO; if (!instance->stopped) { dispatch_sync(dispatch_get_main_queue(), ^{ [NSApp stopModal]; }); } instance->stopped = YES; } } //---------------------------------------------------------------------------------------------------------------------- + (BOOL)hideHud { if (instance != nil) { BOOL result = instance.hud.visible; if (result) [instance hideAnimated]; return result; } return NO; } //---------------------------------------------------------------------------------------------------------------------- - (NSPanel*)hud { return hudPanel; } //---------------------------------------------------------------------------------------------------------------------- - (void)showAnimatedWithFrame: (NSRect) frame title: (NSString*) title andDescription: (NSString*) description { shortHudDescription.stringValue = title; longHudDescription.stringValue = description; [hudPanel setFrame: frame display: NO]; [hudPanel makeKeyAndOrderFront: nil]; [NSAnimationContext currentContext].duration = 0.5; [hudPanel animator].alphaValue = 1; } //---------------------------------------------------------------------------------------------------------------------- - (void)orderOutPanel { [hudPanel orderOut: nil]; } //---------------------------------------------------------------------------------------------------------------------- - (void)hideAnimated { [NSAnimationContext currentContext].duration = 0.5; [hudPanel animator].alphaValue = 0; [self performSelector: @selector(orderOutPanel) withObject: nil afterDelay: 0.5]; } //---------------------------------------------------------------------------------------------------------------------- - (IBAction)cancelClicked:(id)sender { if (cancelAction()) { [modalLoopRunningCond lock]; if (modalHUDRunning) { modalHUDRunning = NO; if (!stopped) [NSApp abortModal]; stopped = YES; } [modalLoopRunningCond unlock]; } } @end //----------------------------------------------------------------------------------------------------------------------