frontend/mac/workbench/WBMainController.mm (838 lines of code) (raw):

/* * Copyright (c) 2009, 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 */ #include "base/string_utilities.h" #include "workbench/wb_context.h" #import "WBMainController.h" #import "MainWindowController.h" #import "MCppUtilities.h" // for _NSGetArg* #include <crt_externs.h> #import "WBPluginPanel.h" #import "WBPluginEditorBase.h" #import "WBPluginWindowBase.h" #import "WBPluginWindowController.h" #import "WBModelDiagramPanel.h" #include "workbench/wb_context_ui.h" #include "model/wb_context_model.h" #include "grtui/gui_plugin_base.h" #import "WBSQLQueryUI.h" #import "WBMenuManager.h" #import "WBDiagramSizeController.h" #import "WBModelDiagramPanel.h" #import "ScintillaView.h" #include "mforms/toolbar.h" #import "MFCodeEditor.h" #include "workbench/wb_module.h" #include "base/log.h" #include "base/file_utilities.h" #include "wb_command_ui.h" static GThread *mainthread = 0; DEFAULT_LOG_DOMAIN("Workbench") @interface WBMainController () { wb::WBContext *_wb; wb::WBOptions *_options; std::map<std::string, FormPanelFactory> _formPanelFactories; NSMutableArray *_editorWindows; BOOL _initFinished; BOOL _showingUnhandledException; // Define a set of methods which backend can call to interact with user and frontend wb::WBFrontendCallbacks wbcallbacks; MainWindowController *mainController; NSMutableArray *pageSetupNibObjects; IBOutlet NSPanel *pageSetup; IBOutlet NSPopUpButton *paperSize; IBOutlet NSTextField *paperSizeLabel; IBOutlet NSButton *landscapeButton; IBOutlet NSButton *portraitButton; // We need to keep extra reference, so it's not released too early. std::shared_ptr<wb::WBContextUI> _wbContext; } @end @implementation WBMainController static std::string showFileDialog(const std::string &type, const std::string &title, const std::string &extensions) { NSMutableArray *fileTypes = [NSMutableArray array]; std::vector<std::string> exts(base::split(extensions, ",")); for (std::vector<std::string>::const_iterator iter = exts.begin(); iter != exts.end(); ++iter) { if (iter->find('|') != std::string::npos) { std::string ext = iter->substr(iter->find('|') + 1); ext = base::replaceString(ext, "*.", ""); [fileTypes addObject:@(ext.c_str())]; } else [fileTypes addObject:@(iter->c_str())]; } if (type == "open") { NSOpenPanel *panel = [NSOpenPanel openPanel]; panel.title = @(title.c_str()); panel.allowedFileTypes = fileTypes; if ([panel runModal] == NSModalResponseOK) return (panel.URL.path).UTF8String; } else if (type == "save") { NSSavePanel *panel = [NSSavePanel savePanel]; panel.title = @(title.c_str()); panel.allowedFileTypes = fileTypes; if ([panel runModal] == NSModalResponseOK) return (panel.URL.path).UTF8String; } return ""; } - (IBAction)inputDialogClose:(id)sender { [NSApp stopModalWithCode: [sender tag]]; } static void windowShowStatusText(const std::string &text, MainWindowController *controller) { NSString *string = @(text.c_str()); // setStatusText must release the param if ([NSThread isMainThread]) controller.statusText = string; else [controller performSelectorOnMainThread: @selector(setStatusText:) withObject: string waitUntilDone: NO]; } static NativeHandle windowOpenPlugin(grt::Module *ownerModule, const std::string &shlib, const std::string &class_name, const grt::BaseListRef &args, bec::GUIPluginFlags flags, MainWindowController *controller) { std::string path = ownerModule->path(); // Check if this is a bundle plugin or a plain dylib plugin if (g_str_has_suffix(shlib.c_str(), ".mwbplugin")) { NSBundle *pluginBundle; NSString *bundlePath; // For bundled plugins, we load it, find the requested class and instantiate it. // determine the path for the plugin bundle by stripping Contents/Framework/dylibname bundlePath = [NSString stringWithCPPString: path] .stringByDeletingLastPathComponent.stringByDeletingLastPathComponent.stringByDeletingLastPathComponent; logDebug("Opening plugin bundle %s\n", shlib.c_str()); pluginBundle = [NSBundle bundleWithPath: bundlePath]; if (pluginBundle == nil) { logError("Plugin bundle %s could not be found\n", bundlePath.UTF8String); NSAlert *alert = [NSAlert new]; alert.messageText = @"Missing Plugin"; alert.informativeText = [NSString stringWithFormat: @"The plugin %s could not be found.", shlib.c_str()]; alert.alertStyle = NSAlertStyleCritical; [alert addButtonWithTitle: @"Close"]; [alert runModal]; return 0; } if (!pluginBundle.isLoaded) { NSError *error = nil; [pluginBundle loadAndReturnError: &error]; if (error != nil) { NSString *s = [NSString stringWithFormat: @"%@", error]; logError("Error loading bundle: %s\n", s.UTF8String); NSAlert *alert = [NSAlert new]; alert.messageText = @"Error Loading Plugin"; alert.informativeText = [NSString stringWithFormat: @"The plugin %s could not be loaded. See log file for details", shlib.c_str()]; alert.alertStyle = NSAlertStyleCritical; [alert addButtonWithTitle: @"Close"]; [alert runModal]; return 0; } } Class pclass = [pluginBundle classNamed: @(class_name.c_str())]; if (!pclass) { logError("Plugin class %s was not found in bundle %s\n", class_name.c_str(), bundlePath.UTF8String); NSAlert *alert = [NSAlert new]; alert.messageText = @"Error Opening Plugin"; alert.informativeText = [NSString stringWithFormat: @"The plugin %s does not contain the published object %s", shlib.c_str(), class_name.c_str()]; alert.alertStyle = NSAlertStyleCritical; [alert addButtonWithTitle: @"Close"]; [alert runModal]; return 0; } if ((flags & bec::StandaloneWindowFlag)) { // create a window for the panel further down } else if (!(flags & bec::ForceNewWindowFlag)) { // Check if there is already a panel with an editor for this plugin type. id existingPanel = [controller findPanelForPluginType: [pclass class]]; if (existingPanel != nil) { // check if it can be closed if ([existingPanel respondsToSelector: @selector(willClose)] && ![existingPanel willClose]) { flags = (bec::GUIPluginFlags)(flags | bec::ForceNewWindowFlag); } else { // drop the old plugin->handle mapping bec::GRTManager::get()->get_plugin_manager()->forget_gui_plugin_handle((__bridge NativeHandle)existingPanel); if ([existingPanel respondsToSelector: @selector(pluginEditor)]) { id editor = [existingPanel pluginEditor]; if ([editor respondsToSelector: @selector(reinitWithArguments:)]) { id oldIdentifier = [editor panelId]; [controller closeBottomPanelWithIdentifier: oldIdentifier]; [editor reinitWithArguments: args]; [controller addBottomPanel: existingPanel]; } } return (__bridge NativeHandle)existingPanel; } } } // Instantiate and initialize the plugin. id plugin = [[pclass alloc] initWithModule: ownerModule arguments:args]; if ([plugin isKindOfClass: [WBPluginEditorBase class]]) { if ((flags & bec::StandaloneWindowFlag)) { WBPluginWindowController *editor = [[WBPluginWindowController alloc] initWithPlugin: plugin]; [controller.owner->_editorWindows addObject: editor]; return (__bridge NativeHandle)editor; } else { WBPluginPanel *panel = [[WBPluginPanel alloc] initWithPlugin: plugin]; [controller addBottomPanel: panel]; return (__bridge NativeHandle)panel; } } else if ([plugin isKindOfClass: WBPluginWindowBase.class]) { [plugin showModal]; return nil; } else { logWarning("Plugin %s is of unknown type\n", [[plugin className] UTF8String]); return nil; } } else { logWarning("open_plugin() called for an unknown plugin type\n"); return 0; } } static void windowShowPlugin(NativeHandle handle, MainWindowController *controller) { id plugin = (__bridge id)handle; if ([plugin respondsToSelector: @selector(setHidden:)]) [plugin setHidden: NO]; else if ([plugin respondsToSelector: @selector(topView)]) [controller reopenEditor: plugin]; } static void windowHidePlugin(NativeHandle handle, MainWindowController *controller) { id plugin = (__bridge id)handle; if ([plugin respondsToSelector: @selector(setHidden:)]) [plugin setHidden: YES]; } static void windowPerformCommand(const std::string &command, MainWindowController *controller, WBMainController *main) { if (command == "reset_layout") [controller resetWindowLayout]; else if (command == "overview.mysql_model") [controller showMySQLOverview: nil]; else if (command == "diagram_size") [main showDiagramProperties: nil]; else if (command == "wb.page_setup") [main showPageSetup: nil]; else [controller forwardCommandToPanels: command]; } static mdc::CanvasView *windowCreateView(const model_DiagramRef &diagram, MainWindowController *controller) { return [controller createView: diagram.id().c_str() name: diagram->name().c_str()]; } static void windowDestroyView(mdc::CanvasView *view, MainWindowController *controller) { [controller destroyView:view]; } static void windowSwitchedView(mdc::CanvasView *cview, MainWindowController *controller) { [controller switchToDiagramWithIdentifier: cview->get_tag().c_str()]; } static void windowCreateMainFormView(const std::string &type, std::shared_ptr<bec::UIForm> form, WBMainController *main, MainWindowController *controller) { if (main->_formPanelFactories.find(type) == main->_formPanelFactories.end()) { throw std::logic_error("Form type " + type + " not supported by frontend"); } else { WBBasePanel *panel = main->_formPanelFactories[type](controller, form); [controller addTopPanelAndSwitch: panel]; } } static void windowDestroyMainFormView(bec::UIForm *form, MainWindowController *controller) { } static void windowToolChanged(mdc::CanvasView *canvas, MainWindowController *controller) { if ([controller.selectedMainPanel isKindOfClass: [WBModelDiagramPanel class]]) { [(WBModelDiagramPanel *)controller.selectedMainPanel canvasToolChanged: canvas]; } } static void windowRefreshGui(wb::RefreshType type, const std::string &arg1, NativeHandle arg2, MainWindowController *controller) { [controller refreshGUI: type argument1: arg1 argument2: arg2]; } static void windowLockGui(bool lock, MainWindowController *controller) { [controller blockGUI: lock]; } static bool quitApplication(MainWindowController *controller) { if (!controller.closeAllPanels) return false; [NSApp terminate: nil]; return true; } - (void)applicationWillTerminate:(NSNotification *)aNotification { logInfo("Shutting down Workbench\n"); [NSObject cancelPreviousPerformRequestsWithTarget: self]; [[NSNotificationCenter defaultCenter] removeObserver: self]; wb::WBContextUI::get()->get_wb()->finalize(); logInfo("Workbench shutdown done\n"); } static void call_copy() { if (![NSApp.keyWindow.firstResponder tryToPerform: @selector(copy:) with: nil]) if (wb::WBContextUI::get()->get_active_form() && wb::WBContextUI::get()->get_active_form()->can_copy()) wb::WBContextUI::get()->get_active_form()->copy(); } static void call_cut() { if (![NSApp.keyWindow.firstResponder tryToPerform: @selector(cut:) with: nil]) if (wb::WBContextUI::get()->get_active_form() && wb::WBContextUI::get()->get_active_form()->can_cut()) wb::WBContextUI::get()->get_active_form()->cut(); } static void call_paste() { if (![NSApp.keyWindow.firstResponder tryToPerform: @selector(paste:) with: nil]) if (wb::WBContextUI::get()->get_active_form() && wb::WBContextUI::get()->get_active_form()->can_paste()) wb::WBContextUI::get()->get_active_form()->paste(); } static void call_select_all() { if (![NSApp.keyWindow.firstResponder tryToPerform: @selector(selectAll:) with: nil]) if (wb::WBContextUI::get()->get_active_form()) wb::WBContextUI::get()->get_active_form()->select_all(); } static void call_delete() { id responder = NSApp.keyWindow.firstResponder; if (![responder tryToPerform: @selector(delete:) with: nil]) if (![responder tryToPerform: @selector(deleteBackward:) with: nil]) if (wb::WBContextUI::get()->get_active_form()) wb::WBContextUI::get()->get_active_form()->delete_selection(); } //-------------------------------------------------------------------------------------------------- static bool validate_copy() { if ([NSApp.keyWindow.firstResponder respondsToSelector: @selector(selectedRange)]) { NSRange textRange = [(id)NSApp.keyWindow.firstResponder selectedRange]; return textRange.length > 0; } if (/*[[[NSApp keyWindow] firstResponder] isKindOfClass: [NSTableView class]] &&*/ [NSApp.keyWindow.firstResponder respondsToSelector: @selector(copy:)]) return true; //[[(NSTableView*)[[NSApp keyWindow] firstResponder] selectedRowIndexes] count] > 0; return (wb::WBContextUI::get()->get_active_form() && wb::WBContextUI::get()->get_active_form()->can_copy()); } //-------------------------------------------------------------------------------------------------- static bool validate_cut() { if ([NSApp.keyWindow.firstResponder respondsToSelector: @selector(selectedRange)]) { NSRange textRange = [(id)NSApp.keyWindow.firstResponder selectedRange]; if (textRange.length > 0) { if ([NSApp.keyWindow.firstResponder respondsToSelector: @selector(isEditable)]) return [(id)NSApp.keyWindow.firstResponder isEditable]; return true; } return false; } else if ([NSApp.keyWindow.firstResponder respondsToSelector: @selector(cut:)]) return true; return (wb::WBContextUI::get()->get_active_form() && wb::WBContextUI::get()->get_active_form()->can_cut()); } //-------------------------------------------------------------------------------------------------- static bool validate_paste() { if ([NSApp.keyWindow.firstResponder respondsToSelector: @selector(isEditable)]) { // Two conditions must be met if target can be considered as pastable. // 1) The target is editable. BOOL isEditable = [(id)NSApp.keyWindow.firstResponder isEditable]; // 2) The pasteboard contains text. NSArray *supportedTypes = @[ NSPasteboardTypeString ]; NSString *bestType = [[NSPasteboard generalPasteboard] availableTypeFromArray: supportedTypes]; return isEditable && (bestType != nil); } else if ([NSApp.keyWindow.firstResponder respondsToSelector: @selector(paste:)]) return true; return (wb::WBContextUI::get()->get_active_form() && wb::WBContextUI::get()->get_active_form()->can_paste()); } //-------------------------------------------------------------------------------------------------- static bool validate_select_all() { if ([NSApp.keyWindow.firstResponder respondsToSelector: @selector(isSelectable)]) return [(id)NSApp.keyWindow.firstResponder isSelectable]; return (wb::WBContextUI::get()->get_active_form()); } //-------------------------------------------------------------------------------------------------- static bool validate_delete() { NSResponder *responder = NSApp.keyWindow.firstResponder; SEL selector = NSSelectorFromString(@"canDeleteItem:"); if ([responder respondsToSelector: selector]) { #pragma clang diagnostic ignored "-Warc-performSelector-leaks" return [responder performSelector: selector withObject: nil]; } if ([responder respondsToSelector: @selector(selectedRange)]) { NSRange textRange = [(id)NSApp.keyWindow.firstResponder selectedRange]; return textRange.length > 0; } return (wb::WBContextUI::get()->get_active_form() && wb::WBContextUI::get()->get_active_form()->can_delete()); } //-------------------------------------------------------------------------------------------------- // XXX deprecated, remove it eventually static void call_closetab_old(MainWindowController *controller) { if (controller.window.isKeyWindow) { id activePanel = controller.activePanel; bec::UIForm *form = [activePanel formBE]; if (form && form->get_form_context_name() == "home") return; if (![activePanel respondsToSelector: @selector(closeActiveEditorTab)] || ![activePanel closeActiveEditorTab]) [controller closePanel: activePanel]; } } static void call_close_tab(MainWindowController *controller) { if (controller.window.isKeyWindow) { id activePanel = controller.activePanel; bec::UIForm *form = [activePanel formBE]; if (form && form->get_form_context_name() == "home") return; [controller closePanel: activePanel]; } } static void call_close_editor(MainWindowController *controller) { if (controller.window.isKeyWindow) { id activePanel = controller.activePanel; bec::UIForm *form = [activePanel formBE]; if (form && form->get_form_context_name() == "home") return; [activePanel closeActiveEditorTab]; } } //-------------------------------------------------------------------------------------------------- static bool validate_closetab_old(MainWindowController *controller) { WBBasePanel *activePanel = controller.activePanel; bec::UIForm *form = activePanel.formBE; if (form && form->get_form_context_name() == "home") return false; // find where this belongs to return activePanel != nil && controller.window.isKeyWindow; } static bool validate_close_tab(MainWindowController *controller) { WBBasePanel *activePanel = controller.activePanel; bec::UIForm *form = activePanel.formBE; if (form && form->get_form_context_name() == "home") return false; return activePanel != nil && controller.window.isKeyWindow; } static bool validate_close_editor(MainWindowController *controller) { WBBasePanel *activePanel = controller.activePanel; bec::UIForm *form = activePanel.formBE; if (form && form->get_form_context_name() == "home") return false; return activePanel != nil && controller.window.isKeyWindow && [activePanel respondsToSelector: @selector(closeActiveEditorTab)]; } //-------------------------------------------------------------------------------------------------- static void call_toggle_fullscreen(MainWindowController *controller) { [controller.window toggleFullScreen: controller.window]; } //-------------------------------------------------------------------------------------------------- static bool validate_toggle_fullscreen(MainWindowController *controller) { return NSAppKitVersionNumber > NSAppKitVersionNumber10_6; } //-------------------------------------------------------------------------------------------------- static bool validate_find_replace() { id firstResponder = NSApp.keyWindow.firstResponder; if ([firstResponder isKindOfClass: [ScintillaView class]] || [firstResponder isKindOfClass: [NSTextView class]] || [firstResponder isKindOfClass: [SCIContentView class]]) return true; return false; } //-------------------------------------------------------------------------------------------------- static void call_find_replace(bool do_replace) { if (validate_find_replace()) { id firstResponder = NSApp.keyWindow.firstResponder; if ([firstResponder isKindOfClass: [SCIContentView class]]) { while (firstResponder && ![firstResponder isKindOfClass: [ScintillaView class]]) firstResponder = [firstResponder superview]; } if ([firstResponder isKindOfClass: [MFCodeEditor class]]) [firstResponder showFindPanel: do_replace]; } else NSBeep(); } //-------------------------------------------------------------------------------------------------- static void call_find(MainWindowController *controller) { id firstResponder = NSApp.keyWindow.firstResponder; if ([firstResponder isKindOfClass: [SCIContentView class]]) { while (firstResponder && ![firstResponder isKindOfClass: [ScintillaView class]]) firstResponder = [firstResponder superview]; } if ([firstResponder isKindOfClass: [MFCodeEditor class]]) [firstResponder showFindPanel:NO]; else [controller performSearchObject: nil]; } static bool validate_find(MainWindowController *controller) { bec::UIForm *form = wb::WBContextUI::get()->get_active_main_form(); if (form && form->get_toolbar() && form->get_toolbar()->find_item("find")) return true; return validate_find_replace(); } //-------------------------------------------------------------------------------------------------- static void call_undo(MainWindowController *controller) { id firstResponder = NSApp.keyWindow.firstResponder; SEL selector = NSSelectorFromString(@"undo:"); if ([firstResponder respondsToSelector: selector]) [firstResponder tryToPerform: selector with: nil]; else if ([firstResponder isKindOfClass: [NSTextView class]]) return [[firstResponder undoManager] undo]; else if (wb::WBContextUI::get()->get_active_main_form()) wb::WBContextUI::get()->get_active_main_form()->undo(); } static bool validate_undo(MainWindowController *controller) { id firstResponder = NSApp.keyWindow.firstResponder; if ([firstResponder respondsToSelector: @selector(canUndo)]) return [firstResponder canUndo]; else if ([firstResponder isKindOfClass: [NSTextView class]]) return [firstResponder undoManager].canUndo; else if ([firstResponder isKindOfClass: [SCIContentView class]]) return true; else if (wb::WBContextUI::get()->get_active_main_form()) return wb::WBContextUI::get()->get_active_main_form()->can_undo(); return false; } static void call_redo(MainWindowController *controller) { id firstResponder = NSApp.keyWindow.firstResponder; SEL selector = NSSelectorFromString(@"redo:"); if ([firstResponder respondsToSelector: selector]) [firstResponder tryToPerform: selector with: nil]; else if ([firstResponder isKindOfClass: [NSTextView class]]) return [[firstResponder undoManager] redo]; else if (wb::WBContextUI::get()->get_active_main_form()) wb::WBContextUI::get()->get_active_main_form()->redo(); } static bool validate_redo(MainWindowController *controller) { id firstResponder = NSApp.keyWindow.firstResponder; if ([firstResponder respondsToSelector: @selector(canRedo)]) return [firstResponder canRedo]; else if ([firstResponder isKindOfClass: [NSTextView class]]) return [firstResponder undoManager].canRedo; else if ([firstResponder isKindOfClass: [SCIContentView class]]) return true; else if (wb::WBContextUI::get()->get_active_main_form()) return wb::WBContextUI::get()->get_active_main_form()->can_redo(); return false; } - (void)textSelectionChanged:(NSNotification *)notification { id firstResponder = NSApp.keyWindow.firstResponder; if (notification.object == firstResponder || ([firstResponder respondsToSelector: @selector(superview)] && notification.object == [firstResponder superview])) { // refresh edit menu wb::WBContextUI::get()->get_command_ui()->revalidate_edit_menu_items(); } } //-------------------------------------------------------------------------------------------------- - (void)registerCommandsWithBackend { std::list<std::string> commands; commands.push_back("overview.mysql_model"); commands.push_back("diagram_size"); commands.push_back("wb.page_setup"); commands.push_back("wb.toggleSidebar"); commands.push_back("wb.toggleSecondarySidebar"); commands.push_back("wb.toggleOutputArea"); commands.push_back("wb.next_tab"); commands.push_back("wb.back_tab"); commands.push_back("wb.next_query_tab"); commands.push_back("wb.back_query_tab"); auto commandUI = wb::WBContextUI::get()->get_command_ui(); commandUI->add_frontend_commands(commands); commandUI->add_builtin_command( "closetab", std::bind(call_closetab_old, mainController), std::bind(validate_closetab_old, mainController)); commandUI->add_builtin_command("close_tab", std::bind(call_close_tab, mainController), std::bind(validate_close_tab, mainController)); commandUI->add_builtin_command( "close_editor", std::bind(call_close_editor, mainController), std::bind(validate_close_editor, mainController)); commandUI->add_builtin_command("toggle_fullscreen", std::bind(call_toggle_fullscreen, mainController), std::bind(validate_toggle_fullscreen, mainController)); commandUI->add_builtin_command("find", std::bind(call_find, mainController), std::bind(validate_find, mainController)); commandUI->add_builtin_command("find_replace", std::bind(call_find_replace, true), std::bind(validate_find_replace)); commandUI->add_builtin_command("undo", std::bind(call_undo, mainController), std::bind(validate_undo, mainController)); commandUI->add_builtin_command("redo", std::bind(call_redo, mainController), std::bind(validate_redo, mainController)); commandUI->add_builtin_command("copy", std::bind(call_copy), std::bind(validate_copy)); commandUI->add_builtin_command("cut", std::bind(call_cut), std::bind(validate_cut)); commandUI->add_builtin_command("paste", std::bind(call_paste), std::bind(validate_paste)); commandUI->add_builtin_command("delete", std::bind(call_delete), std::bind(validate_delete)); commandUI->add_builtin_command("selectAll", std::bind(call_select_all), std::bind(validate_select_all)); } static void flush_main_thread() { // flush stuff that could be called with performSelectorOnMainThread: [[NSRunLoop currentRunLoop] acceptInputForMode: NSDefaultRunLoopMode beforeDate: [NSDate date]]; } - (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename { std::string path = filename.fileSystemRepresentation; if (_initFinished) { if (_wb->open_file_by_extension(path, true)) return YES; } else { _options->open_at_startup = path; return YES; } return NO; } - (void)applicationDidFinishLaunching:(NSNotification *)notification { wb::WBContextUI::get()->init_finish(_options); _initFinished = YES; [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(windowDidBecomeKey:) name: NSWindowDidBecomeKeyNotification object: nil]; } #include "mforms/../cocoa/MFMenuBar.h" - (void)windowDidBecomeKey:(NSNotification *)notification { if (notification.object != mainController.window) { cf_swap_edit_menu(); } else { cf_unswap_edit_menu(); bec::UIForm *form = _wb->get_active_main_form(); if (form && form->get_menubar()) form->get_menubar()->validate(); } } - (void)applicationDidBecomeActive:(NSNotification *)notification { base::NotificationInfo info; base::NotificationCenter::get()->send("GNApplicationActivated", nullptr, info); } - (void)applicationDidResignActive:(NSNotification *)notification { base::NotificationInfo info; base::NotificationCenter::get()->send("GNApplicationDeactivated", nullptr, info); } static NSString *applicationSupportFolder() { NSArray *res = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); if (res.count > 0) return res[0]; return @"/tmp/"; } - (void)setupBackend { mainthread = g_thread_self(); try { // Setup backend stuff _wb = wb::WBContextUI::get()->get_wb(); bec::GRTManager::get()->get_dispatcher()->set_main_thread_flush_and_wait(flush_main_thread); mainController.owner = self; [mainController setup]; // Assign those callback methods wbcallbacks.show_file_dialog = std::bind(showFileDialog, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3); wbcallbacks.show_status_text = std::bind(windowShowStatusText, std::placeholders::_1, mainController); wbcallbacks.open_editor = std::bind(windowOpenPlugin, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, mainController); wbcallbacks.show_editor = std::bind(windowShowPlugin, std::placeholders::_1, mainController); wbcallbacks.hide_editor = std::bind(windowHidePlugin, std::placeholders::_1, mainController); wbcallbacks.perform_command = std::bind(windowPerformCommand, std::placeholders::_1, mainController, self); wbcallbacks.create_diagram = std::bind(windowCreateView, std::placeholders::_1, mainController); wbcallbacks.destroy_view = std::bind(windowDestroyView, std::placeholders::_1, mainController); wbcallbacks.switched_view = std::bind(windowSwitchedView, std::placeholders::_1, mainController); wbcallbacks.create_main_form_view = std::bind(windowCreateMainFormView, std::placeholders::_1, std::placeholders::_2, self, mainController); wbcallbacks.destroy_main_form_view = std::bind(windowDestroyMainFormView, std::placeholders::_1, mainController); wbcallbacks.tool_changed = std::bind(windowToolChanged, std::placeholders::_1, mainController); wbcallbacks.refresh_gui = std::bind(windowRefreshGui, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, mainController); wbcallbacks.lock_gui = std::bind(windowLockGui, std::placeholders::_1, mainController); wbcallbacks.quit_application = std::bind(quitApplication, mainController); // Add shipped python module search path to PYTHONPATH. { char *path = getenv("PYTHONPATH"); if (path) { path = g_strdup_printf("PYTHONPATH=%s:%s", path, _options->library_search_path.c_str()); putenv(path); // path should not be freed } else { path = g_strdup_printf("PYTHONPATH=%s", _options->library_search_path.c_str()); putenv(path); // path should not be freed } } wb::WBContextUI::get()->init(&wbcallbacks, _options); _wb->flush_idle_tasks(false); } catch (std::exception &exc) { MShowCPPException(exc); } } //-------------------------------------------------------------------------------------------------- /** * Instantiates the option class for the backend and parses the command line. */ - (void)setupOptionsAndParseCommandline { int argc = *_NSGetArgc(); char **argv = *_NSGetArgv(); _options = new wb::WBOptions(argv[0]); _options->basedir = [NSBundle mainBundle].resourcePath.fileSystemRepresentation; _options->struct_search_path = _options->basedir + "/grt"; _options->plugin_search_path = std::string([NSBundle mainBundle].builtInPlugInsPath.fileSystemRepresentation); _options->module_search_path = std::string([NSBundle mainBundle].builtInPlugInsPath.fileSystemRepresentation) + ":" + std::string([NSBundle mainBundle].resourcePath.fileSystemRepresentation) + "/plugins"; _options->library_search_path = std::string([NSBundle mainBundle].resourcePath.fileSystemRepresentation) + "/libraries"; _options->cdbc_driver_search_path = std::string([NSBundle mainBundle].privateFrameworksPath.fileSystemRepresentation); try { int rc = 0; if (!_options->programOptions->parse(std::vector<std::string>(argv + 1, argv + argc), rc)) { logInfo("Exiting with rc %i after parsing arguments\n", rc); exit(rc); } _options->analyzeCommandLineArguments(); } catch (std::exception &exc) { logInfo("Exiting with error message: %s\n", exc.what()); exit(1); } if (!_options->user_data_dir.empty()) { _options->user_data_dir = [NSString stringWithCPPString:_options->user_data_dir].stringByExpandingTildeInPath.UTF8String; if (!base::is_directory(_options->user_data_dir)) { try { if (!base::copyDirectoryRecursive( [applicationSupportFolder() stringByAppendingString:@"/MySQL/Workbench"].fileSystemRepresentation, _options->user_data_dir)) { logError("Unable to prepare new config directory: %s\n", _options->user_data_dir.c_str()); exit(1); } } catch (std::exception &exc) { logError("There was a problem preparing new config directory. The error was: %s\n", exc.what()); exit(1); } } } else _options->user_data_dir = [applicationSupportFolder() stringByAppendingString:@"/MySQL/Workbench"].fileSystemRepresentation; // no dock icon when the app will quit when finished running script if (_options->quit_when_done) [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; } //-------------------------------------------------------------------------------------------------- extern "C" { extern void mforms_cocoa_init(); extern void mforms_cocoa_check(); }; static void init_mforms() { logDebug("Initializing mforms\n"); extern void cf_record_grid_init(); static BOOL inited = NO; if (!inited) { inited = YES; mforms_cocoa_init(); cf_record_grid_init(); } } - (instancetype)init { self = [super init]; if (self != nil) { [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(textSelectionChanged:) name: NSTextViewDidChangeSelectionNotification object: nil]; _editorWindows = [NSMutableArray array]; } return self; } - (void)registerFormPanelFactory:(FormPanelFactory)fac forFormType:(const std::string &)type { _formPanelFactories[type] = fac; } - (void)awakeFromNib { // Since we use this controller to load multiple xibs the awakeFromNib function is called each time of such a load // (include the own loading). We can use the mainWindow as indicator (which is set up here). // TODO: refactor out classes with own xib files into own controller classes and use them here instead. if (mainController == nil) { // Prepare the logger to be ready as first part. base::Logger([applicationSupportFolder() stringByAppendingString:@"/MySQL/Workbench"].fileSystemRepresentation); logInfo("Starting up Workbench\n"); [self setupOptionsAndParseCommandline]; init_mforms(); NSApplication.sharedApplication.delegate = self; mainController = [MainWindowController new]; [self setupBackend]; NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 0.5 target: self selector: @selector(idleTasks:) userInfo: nil repeats:YES]; [[NSRunLoop mainRunLoop] addTimer: timer forMode:NSModalPanelRunLoopMode]; NSToolbar *toolbar = mainController.window.toolbar; [toolbar setShowsBaselineSeparator:NO]; [self registerCommandsWithBackend]; setupSQLQueryUI(self, mainController); // don't show the main window if we'll quit after running a script if ((_options->quit_when_done && !_options->run_at_startup.empty())) [mainController.window orderOut: nil]; else [mainController showWindow: nil]; // do the final setup for after the window is shown [mainController setupReady]; mforms_cocoa_check(); // XXX hack to work-around problem with opening object editors { NSString *pluginsPath = [NSBundle mainBundle].builtInPlugInsPath; NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:pluginsPath]; NSString *file; while ((file = [dirEnum nextObject])) { if ([file.pathExtension isEqualToString:@"mwbplugin"]) { NSString *path = [pluginsPath stringByAppendingPathComponent:file]; NSBundle *bundle = [NSBundle bundleWithPath:path]; [bundle load]; } } } _wbContext = wb::WBContextUI::get(); } } - (void)flushIdleTasks:(id)arg { if (!_showingUnhandledException) { _showingUnhandledException = YES; try { _wb->flush_idle_tasks(false); } catch (const std::exception &exc) { NSAlert *alert = [NSAlert new]; alert.messageText = @"Unhandled Exception"; alert.informativeText = [NSString stringWithFormat:@"An unhandled exception has occurred: %s", exc.what()]; alert.alertStyle = NSAlertStyleCritical; [alert addButtonWithTitle:@"Close"]; [alert runModal]; } _showingUnhandledException = NO; } } - (void)idleTasks:(NSTimer *)timer { static NSArray *modes = nil; if (!modes) modes = @[NSDefaultRunLoopMode, NSModalPanelRunLoopMode]; // if we call flush_idle_tasks() directly here, we could get blocked by a plugin with a // modal loop. In that case, the timer would not get fired again until the modal loop // terminates, which is a problem. So we defer the execution to after this method returns. [[NSRunLoop currentRunLoop] performSelector: @selector(flushIdleTasks:) target: self argument: self order: 0 modes: modes]; } - (void)requestRefresh { @autoreleasepool { [self performSelector: @selector(idleTasks:) withObject: nil afterDelay:0]; } } - (IBAction)menuItemClicked:(id)sender { // identifiers from the App menu. These are set in the nib #define APP_MENU_ABOUT 100 #define APP_MENU_PREFERENCES 101 #define APP_MENU_QUIT 102 // special handling for some Application menu items switch ([sender tag]) { case APP_MENU_ABOUT: wb::WBContextUI::get()->get_command_ui()->activate_command("builtin:show_about"); break; case APP_MENU_PREFERENCES: wb::WBContextUI::get()->get_command_ui()->activate_command("plugin:wb.form.showOptions"); break; case APP_MENU_QUIT: quitApplication(mainController); break; } } - (IBAction)showDiagramProperties:(id)sender { WBDiagramSizeController *controller = [[WBDiagramSizeController alloc] init]; [controller showModal]; } - (IBAction)buttonClicked:(id)sender { if ([sender tag] == 10) { [NSApp stopModalWithCode:NSModalResponseOK]; [pageSetup orderOut: nil]; } else if ([sender tag] == 11) { [NSApp stopModalWithCode:NSModalResponseCancel]; [pageSetup orderOut: nil]; } else if (sender == landscapeButton) { landscapeButton.state = NSControlStateValueOn; portraitButton.state = NSControlStateValueOff; } else if (sender == portraitButton) { landscapeButton.state = NSControlStateValueOff; portraitButton.state = NSControlStateValueOn; } } - (void)selectCollectionItem:(id)sender { if (sender == paperSize) { paperSizeLabel.stringValue = paperSize.selectedItem.representedObject; [paperSizeLabel sizeToFit]; } } - (void)showPageSetup:(id)sender { logDebug("Showing page setup dialog\n"); app_PageSettingsRef settings(wb::WBContextUI::get()->get_page_settings()); if (!settings.is_valid()) return; if (!pageSetup) { NSMutableArray *temp; if (![NSBundle.mainBundle loadNibNamed:@"PageSetup" owner: self topLevelObjects:&temp]) return; pageSetupNibObjects = temp; if ([[NSFileManager defaultManager] fileExistsAtPath:@"/System/Library/PrivateFrameworks/PrintingPrivate.framework/" @"Versions/A/Plugins/PrintingCocoaPDEs.bundle/Contents/" @"Resources/Landscape.tiff"]) { landscapeButton.image = [[NSImage alloc] initWithContentsOfFile:@"/System/Library/PrivateFrameworks/PrintingPrivate.framework/Versions/A/Plugins/" @"PrintingCocoaPDEs.bundle/Contents/Resources/Landscape.tiff"]; portraitButton.image = [[NSImage alloc] initWithContentsOfFile:@"/System/Library/PrivateFrameworks/PrintingPrivate.framework/Versions/A/Plugins/" @"PrintingCocoaPDEs.bundle/Contents/Resources/Portrait.tiff"]; } else { landscapeButton.image = [[NSImage alloc] initWithContentsOfFile:@"/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/Print.framework/" @"Versions/A/Plugins/PrintingCocoaPDEs.bundle/Contents/Resources/Landscape.tiff"]; portraitButton.image = [[NSImage alloc] initWithContentsOfFile:@"/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/Print.framework/" @"Versions/A/Plugins/PrintingCocoaPDEs.bundle/Contents/Resources/Portrait.tiff"]; } } std::list<wb::WBPaperSize> paper_sizes = wb::WBContextUI::get()->get_paper_sizes(false); [paperSize removeAllItems]; for (std::list<wb::WBPaperSize>::const_iterator iter = paper_sizes.begin(); iter != paper_sizes.end(); ++iter) { [paperSize addItemWithTitle: [NSString stringWithCPPString:iter->name]]; [paperSize itemAtIndex:paperSize.numberOfItems - 1].representedObject = [NSString stringWithCPPString: iter->description]; } if (settings->paperType().is_valid()) { [paperSize selectItemWithTitle: [NSString stringWithCPPString: *settings->paperType()->name()]]; [self selectCollectionItem:paperSize]; } if (settings->orientation() == "landscape") { landscapeButton.state = NSControlStateValueOn; portraitButton.state = NSControlStateValueOff; } else { landscapeButton.state = NSControlStateValueOff; portraitButton.state = NSControlStateValueOn; } if ([NSApp runModalForWindow:pageSetup] == NSModalResponseOK) { logDebug("Page settings accepted. Updating model...\n"); std::string type = paperSize.titleOfSelectedItem.UTF8String; app_PaperTypeRef paperType(grt::find_named_object_in_list(_wb->get_root()->options()->paperTypes(), type)); std::string orientation; if (paperType != settings->paperType()) settings->paperType(paperType); if (landscapeButton.state == NSControlStateValueOn) orientation = "landscape"; else orientation = "portrait"; if (orientation != *settings->orientation()) settings->orientation(orientation); _wb->get_model_context()->update_page_settings(); } logDebug("Page setup dialog done\n"); } @end