diff options
Diffstat (limited to 'lvc/widgets/osx/window.py')
-rw-r--r-- | lvc/widgets/osx/window.py | 896 |
1 files changed, 896 insertions, 0 deletions
diff --git a/lvc/widgets/osx/window.py b/lvc/widgets/osx/window.py new file mode 100644 index 0000000..53b1091 --- /dev/null +++ b/lvc/widgets/osx/window.py @@ -0,0 +1,896 @@ +# @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 lvc import signals +from lvc.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 |