# @Base: Miro - an RSS based video player application # Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011 # Participatory Culture Foundation # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # 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 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 # # In addition, as a special exception, the copyright holders give # permission to link the code of portions of this program with the OpenSSL # library. # # You must obey the GNU General Public License in all respects for all of # the code used other than OpenSSL. If you modify file(s) with this # exception, you may extend this exception to your version of the file(s), # but you are not obligated to do so. If you do not wish to do so, delete # this exception statement from your version. If you delete this exception # statement from all source files in the program, then also delete it here. """.window -- Top-level Window class. """ import logging from AppKit import * from Foundation import * from objc import YES, NO, nil from PyObjCTools import AppHelper from mvc import signals from mvc.widgets import widgetconst import wrappermap import osxmenus from .helpers import NotificationForwarder from .base import Widget, FlippedView from .layout import VBox, HBox, Alignment from .control import Button from .simple import Label from .rect import Rect, NSRectWrapper from .utils import filename_to_unicode # Tracks all windows that haven't been destroyed. This makes sure there # object stay alive as long as the window is alive. alive_windows = set() class MiroResponderInterceptor(NSResponder): """Intercepts cocoa events and gives our wrappers and chance to handle them first. """ def initWithResponder_(self, responder): """Initialize a MiroResponderInterceptor We will give the wrapper for responder a chance to handle the event, then pass it along to responder. """ self = super(MiroResponderInterceptor, self).init() self.responder = responder return self def keyDown_(self, event): if self.sendKeyDownToWrapper_(event): return # signal handler returned True, stop processing # If our responder is the last in the chain, we can stop intercepting if self.responder.nextResponder() is None: self.responder.keyDown_(event) return # Here's the tricky part, we want to call keyDown_ on our responder, # but if it doesn't handle the event, then it will pass it along to # it's next responder. We need to set things up so that we will # intercept that call. # Make a new MiroResponderInterceptor whose responder is the next # responder down the chain. next_intercepter = MiroResponderInterceptor.alloc().initWithResponder_( self.responder.nextResponder()) # Install the interceptor self.responder.setNextResponder_(next_intercepter) # Send event along self.responder.keyDown_(event) # Restore old nextResponder value self.responder.setNextResponder_(next_intercepter.responder) def sendKeyDownToWrapper_(self, event): """Give a keyDown event to the wrapper for our responder Return True if the wrapper handled the event """ key = event.charactersIgnoringModifiers() if len(key) != 1 or not key.isalnum(): key = osxmenus.REVERSE_KEYS_MAP.get(key) mods = osxmenus.translate_event_modifiers(event) wrapper = wrappermap.wrapper(self.responder) if isinstance(wrapper, Widget) or isinstance(wrapper, Window): if wrapper.emit('key-press', key, mods): return True return False class MiroWindow(NSWindow): def initWithContentRect_styleMask_backing_defer_(self, rect, mask, backing, defer): self = NSWindow.initWithContentRect_styleMask_backing_defer_(self, rect, mask, backing, defer) self._last_focus_chain = None return self def handleKeyDown_(self, event): if self.handle_tab_navigation(event): return interceptor = MiroResponderInterceptor.alloc().initWithResponder_( self.firstResponder()) interceptor.keyDown_(event) def handle_tab_navigation(self, event): """Handle tab navigation through the window. :returns: True if we handled the event """ keystr = event.charactersIgnoringModifiers() if keystr[0] == NSTabCharacter: # handle cycling through views with Tab. self.focusNextKeyView_(True) return True elif keystr[0] == NSBackTabCharacter: self.focusNextKeyView_(False) return True return False def acceptsMouseMovedEvents(self): # HACK: for some reason calling setAcceptsMouseMovedEvents_() doesn't # work, we have to forcefully override this method. return NO def sendEvent_(self, event): if event.type() == NSKeyDown: self.handleKeyDown_(event) else: NSWindow.sendEvent_(self, event) def _calc_current_focus_wrapper(self): responder = self.firstResponder() while responder: wrapper = wrappermap.wrapper(responder) # check if we have a wrapper for the view, if not try the parent # view if wrapper is not None: return wrapper responder = responder.superview() return None def focusNextKeyView_(self, is_forward): current_focus = self._calc_current_focus_wrapper() my_wrapper = wrappermap.wrapper(self) next_focus = my_wrapper.get_next_tab_focus(current_focus, is_forward) if next_focus is not None: next_focus.focus() def draggingEntered_(self, info): wrapper = wrappermap.wrapper(self) return wrapper.draggingEntered_(info) or NSDragOperationNone def draggingUpdated_(self, info): wrapper = wrappermap.wrapper(self) return wrapper.draggingUpdated_(info) or NSDragOperationNone def draggingExited_(self, info): wrapper = wrappermap.wrapper(self) wrapper.draggingExited_(info) def prepareForDragOperation_(self, info): wrapper = wrappermap.wrapper(self) return wrapper.prepareForDragOperation_(info) or NO def performDragOperation_(self, info): wrapper = wrappermap.wrapper(self) return wrapper.performDragOperation_(info) or NO class MainMiroWindow(MiroWindow): def isMovableByWindowBackground(self): return YES class Window(signals.SignalEmitter): """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" def __init__(self, title, rect=None): signals.SignalEmitter.__init__(self) self.create_signal('active-change') self.create_signal('will-close') self.create_signal('did-move') self.create_signal('key-press') self.create_signal('show') self.create_signal('hide') self.create_signal('on-shown') self.create_signal('file-drag-motion') self.create_signal('file-drag-received') self.create_signal('file-drag-leave') self.is_closing = False if rect is None: rect = Rect(0, 0, 470, 600) self.nswindow = MainMiroWindow.alloc().initWithContentRect_styleMask_backing_defer_( rect.nsrect, self.get_style_mask(), NSBackingStoreBuffered, NO) self.nswindow.setTitle_(title) self.nswindow.setMinSize_(NSSize(470, 600)) self.nswindow.setReleasedWhenClosed_(NO) self.content_view = FlippedView.alloc().initWithFrame_(rect.nsrect) self.content_view.setAutoresizesSubviews_(NO) self.nswindow.setContentView_(self.content_view) self.content_widget = None self.view_notifications = NotificationForwarder.create(self.content_view) self.view_notifications.connect(self.on_frame_change, 'NSViewFrameDidChangeNotification') self.window_notifications = NotificationForwarder.create(self.nswindow) self.window_notifications.connect(self.on_activate, 'NSWindowDidBecomeMainNotification') self.window_notifications.connect(self.on_deactivate, 'NSWindowDidResignMainNotification') self.window_notifications.connect(self.on_did_move, 'NSWindowDidMoveNotification') self.window_notifications.connect(self.on_will_close, 'NSWindowWillCloseNotification') wrappermap.add(self.nswindow, self) alive_windows.add(self) def get_next_tab_focus(self, current, is_forward): """Return the next widget to cycle through for keyboard focus Subclasses can override this to for find-grained control of keyboard focus. :param current: currently-focused widget :param is_forward: are we tabbing forward? """ return None # XXX Use MainWindow not Window for MVCStyle/MiroStyle def get_style_mask(self): return (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask) def set_title(self, title): self.nswindow.setTitle_(title) def get_title(self): return self.nswindow.title() def on_frame_change(self, notification): self.place_child() def on_activate(self, notification): self.emit('active-change') def on_deactivate(self, notification): self.emit('active-change') def on_did_move(self, notification): self.emit('did-move') def on_will_close(self, notification): # unset the first responder. This allows text entry widgets to get # the NSControlTextDidEndEditingNotification if self.is_closing: logging.info('on_will_close: already closing') return self.is_closing = True self.nswindow.makeFirstResponder_(nil) self.emit('will-close') self.emit('hide') self.is_closing = False def is_active(self): return self.nswindow.isMainWindow() def is_visible(self): return self.nswindow.isVisible() def show(self): if self not in alive_windows: raise ValueError("Window destroyed") self.nswindow.makeKeyAndOrderFront_(nil) self.nswindow.makeMainWindow() self.emit('show') # Cocoa doesn't apply default selections as forcefully as GTK, so # currently there's no need for on-shown to actually wait until the # window has been shown here self.emit('on-shown') def close(self): self.nswindow.close() def destroy(self): self.close() self.window_notifications.disconnect() self.view_notifications.disconnect() self.nswindow.setContentView_(nil) wrappermap.remove(self.nswindow) alive_windows.discard(self) self.nswindow = None def place_child(self): rect = self.nswindow.contentRectForFrameRect_(self.nswindow.frame()) self.content_widget.place(NSRect(NSPoint(0, 0), rect.size), self.content_view) def hookup_content_widget_signals(self): self.size_req_handler = self.content_widget.connect('size-request-changed', self.on_content_widget_size_request_change) def unhook_content_widget_signals(self): self.content_widget.disconnect(self.size_req_handler) self.size_req_handler = None def on_content_widget_size_request_change(self, widget, old_size): self.update_size_constraints() def set_content_widget(self, widget): if self.content_widget: self.content_widget.remove_viewport() self.unhook_content_widget_signals() self.content_widget = widget self.hookup_content_widget_signals() self.place_child() self.update_size_constraints() def update_size_constraints(self): width, height = self.content_widget.get_size_request() # It is possible the window is torn down between the size invalidate # request and the actual size invalidation invocation. So check # to see if nswindow is there if not then do not do anything. if self.nswindow: # FIXME: I'm not sure that this code does what we want it to do. # It enforces the min-size when the user drags the window, but I # think it should also call setContentSize_ if the window is # currently too small to fit the content - BDK self.nswindow.setContentMinSize_(NSSize(width, height)) rect = self.nswindow.contentRectForFrameRect_(self.nswindow.frame()) if rect.size.width < width or rect.size.height < height: logging.warn("Content widget too large for this window " "size available: %dx%d widget size: %dx%d", rect.size.width, rect.size.height, width, height) def get_content_widget(self): return self.content_widget def get_frame(self): frame = self.nswindow.frame() frame.size.height -= 22 return NSRectWrapper(frame) def connect_menu_keyboard_shortcuts(self): # All OS X windows are connected to the menu shortcuts pass def accept_file_drag(self, val): if not val: self.drag_dest = None else: self.drag_dest = NSDragOperationCopy self.nswindow.registerForDraggedTypes_([NSFilenamesPboardType]) def prepareForDragOperation_(self, info): return NO if self.drag_dest is None else YES def performDragOperation_(self, info): pb = info.draggingPasteboard() available_types = set(pb.types()) & set([NSFilenamesPboardType]) drag_ok = False if available_types: type_ = available_types.pop() # DANCE! Everybody dance for portable Python code! values = [unicode( NSURL.fileURLWithPath_(v).filePathURL()).encode('utf-8') for v in list(pb.propertyListForType_(type_))] self.emit('file-drag-received', values) drag_ok = True self.draggingExited_(info) return drag_ok def draggingEntered_(self, info): return self.draggingUpdated_(info) def draggingUpdated_(self, info): self.emit('file-drag-motion') return self.drag_dest def draggingExited_(self, info): self.emit('file-drag-leave') def center(self): self.nswindow.center() class MainWindow(Window): def __init__(self, title, rect): Window.__init__(self, title, rect) self.nswindow.setReleasedWhenClosed_(NO) def close(self): self.nswindow.orderOut_(nil) class DialogBase(object): def __init__(self): self.sheet_parent = None def set_transient_for(self, window): self.sheet_parent = window class MiroPanel(NSPanel): def cancelOperation_(self, event): wrappermap.wrapper(self).end_with_code(-1) class Dialog(DialogBase): def __init__(self, title, description=None): DialogBase.__init__(self) self.title = title self.description = description self.buttons = [] self.extra_widget = None self.window = None self.running = False def add_button(self, text): button = Button(text) button.set_size(widgetconst.SIZE_NORMAL) button.connect('clicked', self.on_button_clicked, len(self.buttons)) self.buttons.append(button) def on_button_clicked(self, button, code): self.end_with_code(code) def end_with_code(self, code): if self.sheet_parent is not None: NSApp().endSheet_returnCode_(self.window, code) else: NSApp().stopModalWithCode_(code) def build_text(self): vbox = VBox(spacing=6) if self.description is not None: description_label = Label(self.description, wrap=True) description_label.set_bold(True) description_label.set_size_request(360, -1) vbox.pack_start(description_label) return vbox def build_buttons(self): hbox = HBox(spacing=12) for button in reversed(self.buttons): hbox.pack_start(button) alignment = Alignment(xalign=1.0, yscale=1.0) alignment.add(hbox) return alignment def build_content(self): vbox = VBox(spacing=12) vbox.pack_start(self.build_text()) if self.extra_widget: vbox.pack_start(self.extra_widget) vbox.pack_start(self.build_buttons()) alignment = Alignment(xscale=1.0, yscale=1.0) alignment.set_padding(12, 12, 17, 17) alignment.add(vbox) return alignment def build_window(self): self.content_widget = self.build_content() width, height = self.content_widget.get_size_request() width = max(width, 400) window = MiroPanel.alloc() window.initWithContentRect_styleMask_backing_defer_( NSMakeRect(400, 400, width, height), NSTitledWindowMask, NSBackingStoreBuffered, NO) view = FlippedView.alloc().initWithFrame_(NSMakeRect(0, 0, width, height)) window.setContentView_(view) window.setTitle_(self.title) self.content_widget.place(view.frame(), view) if self.buttons: self.buttons[0].make_default() return window def hookup_content_widget_signals(self): self.size_req_handler = self.content_widget.connect( 'size-request-changed', self.on_content_widget_size_request_change) def unhook_content_widget_signals(self): self.content_widget.disconnect(self.size_req_handler) self.size_req_handler = None def on_content_widget_size_request_change(self, widget, old_size): width, height = self.content_widget.get_size_request() # It is possible the window is torn down between the size invalidate # request and the actual size invalidation invocation. So check # to see if nswindow is there if not then do not do anything. if self.window and (width, height) != old_size: self.change_content_size(width, height) def change_content_size(self, width, height): content_rect = self.window.contentRectForFrameRect_( self.window.frame()) # Cocoa's coordinate system is funky, adjust y so that the top stays # in place content_rect.origin.y += (content_rect.size.height - height) # change our frame to fit the new content. It would be nice to # animate the change, but timers don't work when we are displaying a # modal dialog content_rect.size = NSSize(width, height) new_frame = self.window.frameRectForContentRect_(content_rect) self.window.setFrame_display_(new_frame, NO) # Need to call place() again, since our window has changed size contentView = self.window.contentView() self.content_widget.place(contentView.frame(), contentView) def run(self): self.window = self.build_window() wrappermap.add(self.window, self) self.hookup_content_widget_signals() self.running = True if self.sheet_parent is None: response = NSApp().runModalForWindow_(self.window) if self.window: self.window.close() else: delegate = SheetDelegate.alloc().init() NSApp().beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_( self.window, self.sheet_parent.nswindow, delegate, 'sheetDidEnd:returnCode:contextInfo:', 0) response = NSApp().runModalForWindow_(self.window) if self.window: # self.window won't be around if we call destroy() to cancel # the dialog self.window.orderOut_(nil) self.running = False self.unhook_content_widget_signals() if response < 0: return -1 return response def destroy(self): if self.running: NSApp().stopModalWithCode_(-1) if self.window is not None: self.window.setContentView_(None) self.window.close() self.window = None self.buttons = None self.extra_widget = None def set_extra_widget(self, widget): self.extra_widget = widget def get_extra_widget(self): return self.extra_widget class SheetDelegate(NSObject): @AppHelper.endSheetMethod def sheetDidEnd_returnCode_contextInfo_(self, sheet, return_code, info): NSApp().stopModalWithCode_(return_code) class FileDialogBase(DialogBase): def __init__(self): DialogBase.__init__(self) self._types = None self._filename = None self._directory = None self._filter_on_run = True def run(self): self._panel.setAllowedFileTypes_(self._types) if self.sheet_parent is None: if self._filter_on_run: response = self._panel.runModalForDirectory_file_types_(self._directory, self._filename, self._types) else: response = self._panel.runModalForDirectory_file_(self._directory, self._filename) else: delegate = SheetDelegate.alloc().init() if self._filter_on_run: self._panel.beginSheetForDirectory_file_types_modalForWindow_modalDelegate_didEndSelector_contextInfo_( self._directory, self._filename, self._types, self.sheet_parent.nswindow, delegate, 'sheetDidEnd:returnCode:contextInfo:', 0) else: self._panel.beginSheetForDirectory_file_modalForWindow_modalDelegate_didEndSelector_contextInfo_( self._directory, self._filename, self.sheet_parent.nswindow, delegate, 'sheetDidEnd:returnCode:contextInfo:', 0) response = NSApp().runModalForWindow_(self._panel) self._panel.orderOut_(nil) return response class FileSaveDialog(FileDialogBase): def __init__(self, title): FileDialogBase.__init__(self) self._title = title self._panel = NSSavePanel.savePanel() self._panel.setCanChooseFiles_(YES) self._panel.setCanChooseDirectories_(NO) self._filename = None self._filter_on_run = False def set_filename(self, s): self._filename = filename_to_unicode(s) def get_filename(self): # Use encode('utf-8') instead of unicode_to_filename, because # unicode_to_filename has code to make sure nextFilename works, but it's # more important here to not change the filename. return self._filename.encode('utf-8') def run(self): response = FileDialogBase.run(self) if response == NSFileHandlingPanelOKButton: self._filename = self._panel.filename() return 0 self._filename = "" def destroy(self): self._panel = None set_path = set_filename get_path = get_filename class FileOpenDialog(FileDialogBase): def __init__(self, title): FileDialogBase.__init__(self) self._title = title self._panel = NSOpenPanel.openPanel() self._panel.setCanChooseFiles_(YES) self._panel.setCanChooseDirectories_(NO) self._filenames = None def set_select_multiple(self, value): if value: self._panel.setAllowsMultipleSelection_(YES) else: self._panel.setAllowsMultipleSelection_(NO) def set_directory(self, d): self._directory = filename_to_unicode(d) def set_filename(self, s): self._filename = filename_to_unicode(s) def add_filters(self, filters): self._types = [] for _, t in filters: self._types += t def get_filename(self): if self._filenames is None: # canceled return None return self.get_filenames()[0] def get_filenames(self): if self._filenames is None: # canceled return [] # Use encode('utf-8') instead of unicode_to_filename, because # unicode_to_filename has code to make sure nextFilename works, but it's # more important here to not change the filename. return [f.encode('utf-8') for f in self._filenames] def run(self): response = FileDialogBase.run(self) if response == NSFileHandlingPanelOKButton: self._filenames = self._panel.filenames() return 0 self._filename = '' self._filenames = None def destroy(self): self._panel = None set_path = set_filename get_path = get_filename class DirectorySelectDialog(FileDialogBase): def __init__(self, title): FileDialogBase.__init__(self) self._title = title self._panel = NSOpenPanel.openPanel() self._panel.setCanChooseFiles_(NO) self._panel.setCanChooseDirectories_(YES) self._directory = None def set_directory(self, d): self._directory = filename_to_unicode(d) def get_directory(self): # Use encode('utf-8') instead of unicode_to_filename, because # unicode_to_filename has code to make sure nextFilename # works, but it's more important here to not change the # filename. return self._directory.encode('utf-8') def run(self): response = FileDialogBase.run(self) if response == NSFileHandlingPanelOKButton: self._directory = self._panel.filenames()[0] return 0 self._directory = "" def destroy(self): self._panel = None set_path = set_directory get_path = get_directory class AboutDialog(DialogBase): def run(self): optionsDictionary = dict() #revision = app.config.get(prefs.APP_REVISION_NUM) #if revision: # optionsDictionary['Version'] = revision if not optionsDictionary: optionsDictionary = nil NSApplication.sharedApplication().orderFrontStandardAboutPanelWithOptions_(optionsDictionary) def destroy(self): pass class AlertDialog(DialogBase): def __init__(self, title, message, alert_type): DialogBase.__init__(self) self._nsalert = NSAlert.alloc().init(); self._nsalert.setMessageText_(title) self._nsalert.setInformativeText_(message) self._nsalert.setAlertStyle_(alert_type) def add_button(self, text): self._nsalert.addButtonWithTitle_(text) def run(self): self._nsalert.runModal() def destroy(self): self._nsalert = nil class PreferenceItem(NSToolbarItem): def setPanel_(self, panel): self.panel = panel class PreferenceToolbarDelegate(NSObject): def initWithPanels_identifiers_window_(self, panels, identifiers, window): self = super(PreferenceToolbarDelegate, self).init() self.panels = panels self.identifiers = identifiers self.window = window return self def toolbarAllowedItemIdentifiers_(self, toolbar): return self.identifiers def toolbarDefaultItemIdentifiers_(self, toolbar): return self.identifiers def toolbarSelectableItemIdentifiers_(self, toolbar): return self.identifiers def toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar_(self, toolbar, itemIdentifier, flag): panel = self.panels[itemIdentifier] item = PreferenceItem.alloc().initWithItemIdentifier_(itemIdentifier) item.setLabel_(unicode(panel[1])) item.setImage_(NSImage.imageNamed_(u"pref_tab_%s" % itemIdentifier)) item.setAction_("switchPreferenceView:") item.setTarget_(self) item.setPanel_(panel[0]) return item def validateToolbarItem_(self, item): return YES def switchPreferenceView_(self, sender): self.window.do_select_panel(sender.panel, YES) class DialogWindow(Window): def __init__(self, title, rect, allow_miniaturize=False): self.allow_miniaturize = allow_miniaturize Window.__init__(self, title, rect) self.nswindow.setShowsToolbarButton_(NO) def get_style_mask(self): mask = (NSTitledWindowMask | NSClosableWindowMask) if self.allow_miniaturize: mask |= NSMiniaturizableWindowMask return mask class DonateWindow(Window): def __init__(self, title): Window.__init__(self, title, Rect(0, 0, 640, 440)) self.panels = dict() self.identifiers = list() self.first_show = True self.nswindow.setShowsToolbarButton_(NO) self.nswindow.setReleasedWhenClosed_(NO) self.app_notifications = NotificationForwarder.create(NSApp()) self.app_notifications.connect(self.on_app_quit, 'NSApplicationWillTerminateNotification') def destroy(self): super(PreferencesWindow, self).destroy() self.app_notifications.disconnect() def get_style_mask(self): return (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask) def show(self): if self.first_show: self.nswindow.center() self.first_show = False Window.show(self) def on_app_quit(self, notification): self.close() class PreferencesWindow(Window): def __init__(self, title): Window.__init__(self, title, Rect(0, 0, 640, 440)) self.panels = dict() self.identifiers = list() self.first_show = True self.nswindow.setShowsToolbarButton_(NO) self.nswindow.setReleasedWhenClosed_(NO) self.app_notifications = NotificationForwarder.create(NSApp()) self.app_notifications.connect(self.on_app_quit, 'NSApplicationWillTerminateNotification') def destroy(self): super(PreferencesWindow, self).destroy() self.app_notifications.disconnect() def get_style_mask(self): return (NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask) def append_panel(self, name, panel, title, image_name): self.panels[name] = (panel, title) self.identifiers.append(name) def finish_panels(self): self.tbdelegate = PreferenceToolbarDelegate.alloc().initWithPanels_identifiers_window_(self.panels, self.identifiers, self) toolbar = NSToolbar.alloc().initWithIdentifier_(u"Preferences") toolbar.setAllowsUserCustomization_(NO) toolbar.setDelegate_(self.tbdelegate) self.nswindow.setToolbar_(toolbar) def select_panel(self, index): panel = self.identifiers[index] self.nswindow.toolbar().setSelectedItemIdentifier_(panel) self.do_select_panel(self.panels[panel][0], NO) def do_select_panel(self, panel, animate): wframe = self.nswindow.frame() vsize = list(panel.get_size_request()) if vsize[0] < 650: vsize[0] = 650 if vsize[1] < 200: vsize[1] = 200 toolbarHeight = wframe.size.height - self.nswindow.contentView().frame().size.height wframe.origin.y += wframe.size.height - vsize[1] - toolbarHeight wframe.size = (vsize[0], vsize[1] + toolbarHeight) self.set_content_widget(panel) self.nswindow.setFrame_display_animate_(wframe, YES, animate) def show(self): if self.first_show: self.nswindow.center() self.first_show = False Window.show(self) def on_app_quit(self, notification): self.close() def get_first_time_dialog_coordinates(width, height): """Returns the coordinates for the first time dialog. """ # windowFrame is None on first run. in that case, we want # to put librevideoconverter in the middle. mainscreen = NSScreen.mainScreen() rect = mainscreen.frame() x = (rect.size.width - width) / 2 y = (rect.size.height - height) / 2 return x, y