diff options
Diffstat (limited to 'mvc/widgets/osx')
25 files changed, 0 insertions, 8227 deletions
diff --git a/mvc/widgets/osx/Resources-Widgets/MainMenu.nib/designable.nib b/mvc/widgets/osx/Resources-Widgets/MainMenu.nib/designable.nib deleted file mode 100644 index b7fefd6..0000000 --- a/mvc/widgets/osx/Resources-Widgets/MainMenu.nib/designable.nib +++ /dev/null @@ -1,145 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="8.00"> - <data> - <int key="IBDocument.SystemTarget">1060</int> - <string key="IBDocument.SystemVersion">12A269</string> - <string key="IBDocument.InterfaceBuilderVersion">2549</string> - <string key="IBDocument.AppKitVersion">1187</string> - <string key="IBDocument.HIToolboxVersion">624.00</string> - <object class="NSMutableDictionary" key="IBDocument.PluginVersions"> - <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin</string> - <string key="NS.object.0">2549</string> - </object> - <array key="IBDocument.IntegratedClassDependencies"> - <string>NSCustomObject</string> - <string>NSMenu</string> - <string>NSMenuItem</string> - </array> - <array key="IBDocument.PluginDependencies"> - <string>com.apple.InterfaceBuilder.CocoaPlugin</string> - </array> - <object class="NSMutableDictionary" key="IBDocument.Metadata"> - <string key="NS.key.0">PluginDependencyRecalculationVersion</string> - <integer value="1" key="NS.object.0"/> - </object> - <array class="NSMutableArray" key="IBDocument.RootObjects" id="864178278"> - <object class="NSCustomObject" id="422340081"> - <object class="NSMutableString" key="NSClassName"> - <characters key="NS.bytes">NSApplication</characters> - </object> - </object> - <object class="NSCustomObject" id="99063961"> - <string key="NSClassName">FirstResponder</string> - </object> - <object class="NSCustomObject" id="399126242"> - <string key="NSClassName">NSApplication</string> - </object> - <object class="NSMenu" id="603720448"> - <string key="NSTitle">MainMenu</string> - <array class="NSMutableArray" key="NSMenuItems"> - <object class="NSMenuItem" id="726726549"> - <reference key="NSMenu" ref="603720448"/> - <string key="NSTitle">Libre Video Converter</string> - <string key="NSKeyEquiv"/> - <int key="NSKeyEquivModMask">1048576</int> - <int key="NSMnemonicLoc">2147483647</int> - <object class="NSCustomResource" key="NSOnImage"> - <string key="NSClassName">NSImage</string> - <string key="NSResourceName">NSMenuCheckmark</string> - </object> - <object class="NSCustomResource" key="NSMixedImage"> - <string key="NSClassName">NSImage</string> - <string key="NSResourceName">NSMenuMixedState</string> - </object> - <string key="NSAction">submenuAction:</string> - <object class="NSMenu" key="NSSubmenu" id="530441688"> - <string key="NSTitle">Libre Video Converter</string> - <array class="NSMutableArray" key="NSMenuItems"/> - <string key="NSName">_NSAppleMenu</string> - </object> - </object> - </array> - <string key="NSName">_NSMainMenu</string> - </object> - </array> - <object class="IBObjectContainer" key="IBDocument.Objects"> - <array class="NSMutableArray" key="connectionRecords"/> - <object class="IBMutableOrderedSet" key="objectRecords"> - <array key="orderedObjects"> - <object class="IBObjectRecord"> - <int key="objectID">0</int> - <array key="object" id="0"/> - <reference key="children" ref="864178278"/> - <nil key="parent"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">-2</int> - <reference key="object" ref="422340081"/> - <reference key="parent" ref="0"/> - <string key="objectName">File's Owner</string> - </object> - <object class="IBObjectRecord"> - <int key="objectID">-1</int> - <reference key="object" ref="99063961"/> - <reference key="parent" ref="0"/> - <string key="objectName">First Responder</string> - </object> - <object class="IBObjectRecord"> - <int key="objectID">29</int> - <reference key="object" ref="603720448"/> - <array class="NSMutableArray" key="children"> - <reference ref="726726549"/> - </array> - <reference key="parent" ref="0"/> - <string key="objectName">MainMenu</string> - </object> - <object class="IBObjectRecord"> - <int key="objectID">56</int> - <reference key="object" ref="726726549"/> - <array class="NSMutableArray" key="children"> - <reference ref="530441688"/> - </array> - <reference key="parent" ref="603720448"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">57</int> - <reference key="object" ref="530441688"/> - <reference key="parent" ref="726726549"/> - </object> - <object class="IBObjectRecord"> - <int key="objectID">-3</int> - <reference key="object" ref="399126242"/> - <reference key="parent" ref="0"/> - <string key="objectName">Application</string> - </object> - </array> - </object> - <dictionary class="NSMutableDictionary" key="flattenedProperties"> - <string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <string key="29.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <string key="56.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - <string key="57.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string> - </dictionary> - <dictionary class="NSMutableDictionary" key="unlocalizedProperties"/> - <nil key="activeLocalization"/> - <dictionary class="NSMutableDictionary" key="localizations"/> - <nil key="sourceID"/> - <int key="maxID">248</int> - </object> - <object class="IBClassDescriber" key="IBDocument.Classes"/> - <int key="IBDocument.localizationMode">0</int> - <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaFramework</string> - <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDependencies"> - <string key="NS.key.0">com.apple.InterfaceBuilder.CocoaPlugin.macosx</string> - <real value="1060" key="NS.object.0"/> - </object> - <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool> - <int key="IBDocument.defaultPropertyAccessControl">3</int> - <dictionary class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes"> - <string key="NSMenuCheckmark">{11, 11}</string> - <string key="NSMenuMixedState">{10, 3}</string> - </dictionary> - </data> -</archive> diff --git a/mvc/widgets/osx/Resources-Widgets/MainMenu.nib/keyedobjects.nib b/mvc/widgets/osx/Resources-Widgets/MainMenu.nib/keyedobjects.nib Binary files differdeleted file mode 100644 index 963b444..0000000 --- a/mvc/widgets/osx/Resources-Widgets/MainMenu.nib/keyedobjects.nib +++ /dev/null diff --git a/mvc/widgets/osx/__init__.py b/mvc/widgets/osx/__init__.py deleted file mode 100644 index f227b35..0000000 --- a/mvc/widgets/osx/__init__.py +++ /dev/null @@ -1,74 +0,0 @@ -import sys - -from objc import * -from Foundation import * -from AppKit import * - -from PyObjCTools import AppHelper - -size_request_manager = None - -class AppController(NSObject): - def applicationDidFinishLaunching_(self, notification): - from mvc.widgets.osx.osxmenus import MenuBar - self.portableApp.menubar = MenuBar() - self.portableApp.startup() - self.portableApp.run() - - def setPortableApp_(self, portableApp): - self.portableApp = portableApp - - def handleMenuActivate_(self, menu_item): - from mvc.widgets.osx import osxmenus - osxmenus.handle_menu_activate(menu_item) - -def initialize(app): - nsapp = NSApplication.sharedApplication() - delegate = AppController.alloc().init() - delegate.setPortableApp_(app) - nsapp.setDelegate_(delegate) - - global size_request_manager - from mvc.widgets.osx.widgetupdates import SizeRequestManager - size_request_manager = SizeRequestManager() - - NSApplicationMain(sys.argv) - -def attach_menubar(): - pass - -def mainloop_start(): - pass - -def mainloop_stop(): - NSApplication.sharedApplication().terminate_(nil) - -def idle_add(callback, periodic=None): - def wrapper(): - callback() - if periodic is not None: - AppHelper.callLater(periodic, wrapper) - if periodic is not None and periodic < 0: - raise ValueError('periodic cannot be negative') - # XXX: we have a lousy thread API that doesn't allocate pools for us... - pool = NSAutoreleasePool.alloc().init() - if periodic is not None: - AppHelper.callLater(periodic, wrapper) - else: - AppHelper.callAfter(wrapper) - del pool - -def idle_remove(id_): - pass - -def reveal_file(filename): - # XXX: dumb lousy type conversions ... - path = NSURL.fileURLWithPath_(filename.decode('utf-8')).path() - NSWorkspace.sharedWorkspace().selectFile_inFileViewerRootedAtPath_( - path, nil) - -def get_conversion_directory(): - url, error = NSFileManager.defaultManager().URLForDirectory_inDomain_appropriateForURL_create_error_(NSMoviesDirectory, NSUserDomainMask, nil, YES, None) - if error: - return None - return url.path().encode('utf-8') diff --git a/mvc/widgets/osx/base.py b/mvc/widgets/osx/base.py deleted file mode 100644 index 913b372..0000000 --- a/mvc/widgets/osx/base.py +++ /dev/null @@ -1,367 +0,0 @@ -# @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. - -""".base.py -- Widget base classes.""" - -from AppKit import * -from Foundation import * -from objc import YES, NO, nil - -from mvc import signals -import wrappermap -from .viewport import Viewport, BorrowedViewport - -class Widget(signals.SignalEmitter): - """Base class for Cocoa widgets. - - attributes: - - CREATES_VIEW -- Does the widget create a view for itself? If this is True - the widget must have an attribute named view, which is the view that the - widget uses. - - placement -- What portion of view the widget occupies. - """ - - CREATES_VIEW = True - - def __init__(self): - signals.SignalEmitter.__init__(self, 'size-request-changed', - 'size-allocated', 'key-press', 'focus-out') - self.create_signal('place-in-scroller') - self.viewport = None - self.parent_is_scroller = False - self.manual_size_request = None - self.cached_size_request = None - self._disabled = False - - def set_can_focus(self, allow): - assert isinstance(self.view, NSControl) - self.view.setRefusesFirstResponder_(not allow) - - def set_size_request(self, width, height): - self.manual_size_request = (width, height) - self.invalidate_size_request() - - def clear_size_request_cache(self): - from mvc.widgets.osx import size_request_manager - if size_request_manager is not None: - while size_request_manager.widgets_to_request: - size_request_manager._run_requests() - - def get_size_request(self): - if self.manual_size_request: - width, height = self.manual_size_request - if width == -1: - width = self.get_natural_size_request()[0] - if height == -1: - height = self.get_natural_size_request()[1] - return width, height - return self.get_natural_size_request() - - def get_natural_size_request(self): - if self.cached_size_request: - return self.cached_size_request - else: - self.cached_size_request = self.calc_size_request() - return self.cached_size_request - - def invalidate_size_request(self): - from mvc.widgets.osx import size_request_manager - if size_request_manager is not None: - size_request_manager.add_widget(self) - - def do_invalidate_size_request(self): - """Recalculate the size request for this widget.""" - old_size_request = self.cached_size_request - self.cached_size_request = None - self.emit('size-request-changed', old_size_request) - - def calc_size_request(self): - """Return the minimum size needed to display this widget. - Must be Implemented by subclasses. - """ - raise NotImplementedError() - - def _debug_size_request(self, nesting_level=0): - """Debug size request calculations. - - This method recursively prints out the size request for each widget. - """ - request = self.calc_size_request() - width = int(request[0]) - height = int(request[1]) - indent = ' ' * nesting_level - me = str(self.__class__).split('.')[-1] - print '%s%s: %sx%s' % (indent, me, width, height) - - def place(self, rect, containing_view): - """Place this widget on a view. """ - if self.viewport is None: - if self.CREATES_VIEW: - self.viewport = Viewport(self.view, rect) - containing_view.addSubview_(self.view) - wrappermap.add(self.view, self) - else: - self.viewport = BorrowedViewport(containing_view, rect) - self.viewport_created() - else: - if not self.viewport.at_position(rect): - self.viewport.reposition(rect) - self.viewport_repositioned() - self.emit('size-allocated', rect.size.width, rect.size.height) - - def remove_viewport(self): - if self.viewport is not None: - self.viewport.remove() - self.viewport = None - if self.CREATES_VIEW: - wrappermap.remove(self.view) - - def viewport_created(self): - """Called after we first create a viewport. Subclasses can override - this method if they want to handle this event. - """ - - def viewport_repositioned(self): - """Called when we reposition our viewport. Subclasses can override - this method if they want to handle this event. - """ - - def viewport_scrolled(self): - """Called by the Scroller widget on it's child widget when it is - scrolled. - """ - - def get_width(self): - return int(self.viewport.get_width()) - width = property(get_width) - - def get_height(self): - return int(self.viewport.get_height()) - height = property(get_height) - - def get_window(self): - if not self.viewport.view: - return None - return wrappermap.wrapper(self.viewport.view.window()) - - def queue_redraw(self): - if self.viewport: - self.viewport.queue_redraw() - - def redraw_now(self): - if self.viewport: - self.viewport.redraw_now() - - def relative_position(self, other_widget): - """Get the position of another widget, relative to this widget.""" - basePoint = self.viewport.view.convertPoint_fromView_( - other_widget.viewport.area().origin, - other_widget.viewport.view) - return (basePoint.x - self.viewport.area().origin.x, - basePoint.y - self.viewport.area().origin.y) - - def make_color(self, (red, green, blue)): - return NSColor.colorWithDeviceRed_green_blue_alpha_(red, green, blue, - 1.0) - - def enable(self): - self._disabled = False - - def disable(self): - self._disabled = True - - def set_disabled(self, disabled): - if disabled: - self.disable() - else: - self.enable() - - def get_disabled(self): - return self._disabled - -class Container(Widget): - """Widget that holds other widgets. """ - - def __init__(self): - Widget.__init__(self) - self.callback_handles = {} - - def on_child_size_request_changed(self, child, old_size): - self.invalidate_size_request() - - def connect_child_signals(self, child): - handle = child.connect_weak('size-request-changed', - self.on_child_size_request_changed) - self.callback_handles[child] = handle - - def disconnect_child_signals(self, child): - child.disconnect(self.callback_handles.pop(child)) - - def remove_viewport(self): - for child in self.children: - child.remove_viewport() - Widget.remove_viewport(self) - - def child_added(self, child): - """Must be called by subclasses when a child is added to the - Container.""" - self.connect_child_signals(child) - self.children_changed() - - def child_removed(self, child): - """Must be called by subclasses when a child is removed from the - Container.""" - self.disconnect_child_signals(child) - child.remove_viewport() - self.children_changed() - - def child_changed(self, old_child, new_child): - """Must be called by subclasses when a child is replaced by a new - child in the Container. To simplify things a bit for subclasses, - old_child can be None in which case this is the same as - child_added(new_child). - """ - if old_child is not None: - self.disconnect_child_signals(old_child) - old_child.remove_viewport() - self.connect_child_signals(new_child) - self.children_changed() - - def children_changed(self): - """Invoked when the set of children for this widget changes.""" - self.do_invalidate_size_request() - - def do_invalidate_size_request(self): - Widget.do_invalidate_size_request(self) - if self.viewport: - self.place_children() - - def viewport_created(self): - self.place_children() - - def viewport_repositioned(self): - self.place_children() - - def viewport_scrolled(self): - for child in self.children: - child.viewport_scrolled() - - def place_children(self): - """Layout our child widgets. Must be implemented by subclasses.""" - raise NotImplementedError() - - def _debug_size_request(self, nesting_level=0): - for child in self.children: - child._debug_size_request(nesting_level+1) - Widget._debug_size_request(self, nesting_level) - -class Bin(Container): - """Container that only has one child widget.""" - - def __init__(self, child=None): - Container.__init__(self) - self.child = None - if child is not None: - self.add(child) - - def get_children(self): - if self.child: - return [self.child] - else: - return [] - children = property(get_children) - - def add(self, child): - if self.child is not None: - raise ValueError("Already have a child: %s" % self.child) - self.child = child - self.child_added(self.child) - - def remove(self): - if self.child is not None: - old_child = self.child - self.child = None - self.child_removed(old_child) - - def set_child(self, new_child): - old_child = self.child - self.child = new_child - self.child_changed(old_child, new_child) - - def enable(self): - Container.enable(self) - self.child.enable() - - def disable(self): - Container.disable(self) - self.child.disable() - -class SimpleBin(Bin): - """Bin that whose child takes up it's entire space.""" - - def calc_size_request(self): - if self.child is None: - return (0, 0) - else: - return self.child.get_size_request() - - def place_children(self): - if self.child: - self.child.place(self.viewport.area(), self.viewport.view) - -class FlippedView(NSView): - """Flipped NSView. We use these internally to lessen the differences - between Cocoa and GTK. - """ - - def init(self): - self = super(FlippedView, self).init() - self.background = None - return self - - def initWithFrame_(self, rect): - self = super(FlippedView, self).initWithFrame_(rect) - self.background = None - return self - - def isFlipped(self): - return YES - - def isOpaque(self): - return self.background is not None - - def setBackgroundColor_(self, color): - self.background = color - - def drawRect_(self, rect): - if self.background: - self.background.set() - NSBezierPath.fillRect_(rect) diff --git a/mvc/widgets/osx/const.py b/mvc/widgets/osx/const.py deleted file mode 100644 index ae0da40..0000000 --- a/mvc/widgets/osx/const.py +++ /dev/null @@ -1,44 +0,0 @@ -# @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. - -from AppKit import * - -"""const.py -- Constants""" - -DRAG_ACTION_NONE = NSDragOperationNone -DRAG_ACTION_COPY = NSDragOperationCopy -DRAG_ACTION_MOVE = NSDragOperationMove -DRAG_ACTION_LINK = NSDragOperationLink -DRAG_ACTION_ALL = (DRAG_ACTION_COPY | DRAG_ACTION_MOVE | DRAG_ACTION_LINK) - -ITEM_TITLE_FONT = "Helvetica" -ITEM_DESC_FONT = "Helvetica" -ITEM_INFO_FONT = "Lucida Grande" - -TOOLBAR_GRAY = (0.19, 0.19, 0.19) diff --git a/mvc/widgets/osx/contextmenu.py b/mvc/widgets/osx/contextmenu.py deleted file mode 100644 index 7a8fa55..0000000 --- a/mvc/widgets/osx/contextmenu.py +++ /dev/null @@ -1,84 +0,0 @@ -from AppKit import * -from objc import nil - -from .base import Widget - -class ContextMenuHandler(NSObject): - def initWithCallback_widget_i_(self, callback, widget, i): - self = super(ContextMenuHandler, self).init() - self.callback = callback - self.widget = widget - self.i = i - return self - - def handleMenuItem_(self, sender): - self.callback(self.widget, self.i) - - -class MiroContextMenu(NSMenu): - # Works exactly like NSMenu, except it keeps a reference to the menu - # handler objects. - def init(self): - self = super(MiroContextMenu, self).init() - self.handlers = set() - return self - - def addItem_(self, item): - if isinstance(item.target(), ContextMenuHandler): - self.handlers.add(item.target()) - return NSMenu.addItem_(self, item) - - -class ContextMenu(object): - - def __init__(self, options): - super(ContextMenu, self).__init__() - self.menu = MiroContextMenu.alloc().init() - for i, item_info in enumerate(options): - if item_info is None: - nsitem = NSMenuItem.separatorItem() - else: - label, callback = item_info - nsitem = NSMenuItem.alloc().init() - font_size = NSFont.systemFontSize() - font = NSFont.fontWithName_size_("Lucida Sans Italic", font_size) - if font is None: - font = NSFont.systemFontOfSize_(font_size) - attributes = {NSFontAttributeName: font} - attributed_label = NSAttributedString.alloc().initWithString_attributes_(label, attributes) - nsitem.setAttributedTitle_(attributed_label) - else: - nsitem.setTitle_(label) - if isinstance(callback, list): - submenu = ContextMenu(callback) - self.menu.setSubmenu_forItem_(submenu.menu, nsitem) - else: - handler = ContextMenuHandler.alloc().initWithCallback_widget_i_(callback, self, i) - nsitem.setTarget_(handler) - nsitem.setAction_('handleMenuItem:') - self.menu.addItem_(nsitem) - - def popup(self): - # support for non-window based popups thanks to - # http://stackoverflow.com/questions/9033534/how-can-i-pop-up-nsmenu-at-mouse-cursor-position - location = NSEvent.mouseLocation() - frame = NSMakeRect(location.x, location.y, 200, 200) - window = NSWindow.alloc().initWithContentRect_styleMask_backing_defer_( - frame, - NSBorderlessWindowMask, - NSBackingStoreBuffered, - NO) - window.setAlphaValue_(0) - window.makeKeyAndOrderFront_(NSApp) - location_in_window = window.convertScreenToBase_(location) - event = NSEvent.mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure_( - NSLeftMouseDown, - location_in_window, - 0, - 0, - window.windowNumber(), - nil, - 0, - 0, - 0) - NSMenu.popUpContextMenu_withEvent_forView_(self.menu, event, window.contentView()) diff --git a/mvc/widgets/osx/control.py b/mvc/widgets/osx/control.py deleted file mode 100644 index ed6ea34..0000000 --- a/mvc/widgets/osx/control.py +++ /dev/null @@ -1,530 +0,0 @@ -# @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. - -""".control - Controls.""" - -from AppKit import * -from Foundation import * -from objc import YES, NO, nil - -from mvc.widgets import widgetconst -import wrappermap -from .base import Widget -from .helpers import NotificationForwarder - -class SizedControl(Widget): - def set_size(self, size): - if size == widgetconst.SIZE_NORMAL: - self.view.cell().setControlSize_(NSRegularControlSize) - font = NSFont.systemFontOfSize_(NSFont.systemFontSize()) - self.font_size = NSFont.systemFontSize() - elif size == widgetconst.SIZE_SMALL: - font = NSFont.systemFontOfSize_(NSFont.smallSystemFontSize()) - self.view.cell().setControlSize_(NSSmallControlSize) - self.font_size = NSFont.smallSystemFontSize() - else: - self.view.cell().setControlSize_(NSRegularControlSize) - font = NSFont.systemFontOfSize_(NSFont.systemFontSize() * size) - self.font_size = NSFont.systemFontSize() * size - self.view.setFont_(font) - -class BaseTextEntry(SizedControl): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, initial_text=None): - SizedControl.__init__(self) - self.view = self.make_view() - self.font = NSFont.systemFontOfSize_(NSFont.systemFontSize()) - self.view.setFont_(self.font) - self.view.setEditable_(YES) - self.view.cell().setScrollable_(YES) - self.view.cell().setLineBreakMode_(NSLineBreakByClipping) - self.sizer_cell = self.view.cell().copy() - if initial_text: - self.view.setStringValue_(initial_text) - self.set_width(len(initial_text)) - else: - self.set_width(10) - - self.notifications = NotificationForwarder.create(self.view) - - self.create_signal('activate') - self.create_signal('changed') - self.create_signal('validate') - - def focus(self): - if self.view.window() is not None: - self.view.window().makeFirstResponder_(self.view) - - def start_editing(self, initial_text): - self.set_text(initial_text) - self.focus() - # unselect the text and locate the cursor at the end of the entry - text_field = self.view.window().fieldEditor_forObject_(YES, self.view) - text_field.setSelectedRange_(NSMakeRange(len(self.get_text()), 0)) - - def viewport_created(self): - SizedControl.viewport_created(self) - self.notifications.connect(self.on_changed, 'NSControlTextDidChangeNotification') - self.notifications.connect(self.on_end_editing, - 'NSControlTextDidEndEditingNotification') - - def remove_viewport(self): - SizedControl.remove_viewport(self) - self.notifications.disconnect() - - def baseline(self): - return -self.view.font().descender() + 2 - - def on_changed(self, notification): - self.emit('changed') - - def on_end_editing(self, notification): - self.emit('focus-out') - - def calc_size_request(self): - size = self.sizer_cell.cellSize() - return size.width, size.height - - def set_text(self, text): - self.view.setStringValue_(text) - self.emit('changed') - - def get_text(self): - return self.view.stringValue() - - def set_width(self, chars): - self.sizer_cell.setStringValue_('X' * chars) - self.invalidate_size_request() - - def set_activates_default(self, setting): - pass - - def enable(self): - SizedControl.enable(self) - self.view.setEnabled_(True) - - def disable(self): - SizedControl.disable(self) - self.view.setEnabled_(False) - -class MiroTextField(NSTextField): - def textDidEndEditing_(self, notification): - wrappermap.wrapper(self).emit('activate') - return NSTextField.textDidEndEditing_(self, notification) - -class TextEntry(BaseTextEntry): - def make_view(self): - return MiroTextField.alloc().init() - -class NumberEntry(BaseTextEntry): - def make_view(self): - return MiroTextField.alloc().init() - - def set_max_length(self, length): - # TODO - pass - - def _filter_value(self): - """Discard any non-numeric characters""" - digits = ''.join(x for x in self.view.stringValue() if x.isdigit()) - self.view.setStringValue_(digits) - - def on_changed(self, notification): - # overriding on_changed rather than connecting to it ensures that we - # filter the value before anything else connected to the signal sees it - self._filter_value() - BaseTextEntry.on_changed(self, notification) - - def get_text(self): - # handles get_text between when text is entered and when on_changed - # filters it, in case that's possible - self._filter_value() - return BaseTextEntry.get_text(self) - -class MiroSecureTextField(NSSecureTextField): - def textDidEndEditing_(self, notification): - wrappermap.wrapper(self).emit('activate') - return NSSecureTextField.textDidEndEditing_(self, notification) - -class SecureTextEntry(BaseTextEntry): - def make_view(self): - return MiroSecureTextField.alloc().init() - -class MultilineTextEntry(Widget): - def __init__(self, initial_text=None): - Widget.__init__(self) - if initial_text is None: - initial_text = "" - self.view = NSTextView.alloc().initWithFrame_(NSRect((0,0),(50,50))) - self.view.setMaxSize_((1.0e7, 1.0e7)) - self.view.setHorizontallyResizable_(NO) - self.view.setVerticallyResizable_(YES) - self.notifications = NotificationForwarder.create(self.view) - self.create_signal('changed') - self.create_signal('focus-out') - if initial_text is not None: - self.set_text(initial_text) - self.set_size(widgetconst.SIZE_NORMAL) - - def set_size(self, size): - if size == widgetconst.SIZE_NORMAL: - font = NSFont.systemFontOfSize_(NSFont.systemFontSize()) - elif size == widgetconst.SIZE_SMALL: - self.view.cell().setControlSize_(NSSmallControlSize) - else: - raise ValueError("Unknown size: %s" % size) - self.view.setFont_(font) - - def viewport_created(self): - Widget.viewport_created(self) - self.notifications.connect(self.on_changed, 'NSTextDidChangeNotification') - self.notifications.connect(self.on_end_editing, - 'NSControlTextDidEndEditingNotification') - self.invalidate_size_request() - - def remove_viewport(self): - Widget.remove_viewport(self) - self.notifications.disconnect() - - def focus(self): - if self.view.window() is not None: - self.view.window().makeFirstResponder_(self.view) - - def set_text(self, text): - self.view.setString_(text) - self.invalidate_size_request() - - def get_text(self): - return self.view.string() - - def on_changed(self, notification): - self.invalidate_size_request() - self.emit("changed") - - def on_end_editing(self, notification): - self.emit("focus-out") - - def calc_size_request(self): - layout_manager = self.view.layoutManager() - text_container = self.view.textContainer() - # The next line is there just to force cocoa to layout the text - layout_manager.glyphRangeForTextContainer_(text_container) - rect = layout_manager.usedRectForTextContainer_(text_container) - return rect.size.width, rect.size.height - - def set_editable(self, editable): - if editable: - self.view.setEditable_(YES) - else: - self.view.setEditable_(NO) - - -class MiroButton(NSButton): - - def initWithSignal_(self, signal): - self = super(MiroButton, self).init() - self.signal = signal - return self - - def sendAction_to_(self, action, to): - # We override the Cocoa machinery here and just send it to our wrapper - # widget. - wrappermap.wrapper(self).emit(self.signal) - return YES - -class Checkbox(SizedControl): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, text="", bold=False, color=None): - SizedControl.__init__(self) - self.create_signal('toggled') - self.view = MiroButton.alloc().initWithSignal_('toggled') - self.view.setButtonType_(NSSwitchButton) - self.bold = bold - self.title = text - self.font_size = NSFont.systemFontSize() - self.color = self.make_color(color) - self._set_title() - - def set_size(self, size): - SizedControl.set_size(self, size) - self._set_title() - - def _set_title(self): - if self.color is None: - self.view.setTitle_(self.title) - else: - attributes = { - NSForegroundColorAttributeName: self.color, - NSFontAttributeName: NSFont.systemFontOfSize_(self.font_size) - } - string = NSAttributedString.alloc().initWithString_attributes_( - self.title, attributes) - self.view.setAttributedTitle_(string) - - def calc_size_request(self): - if self.manual_size_request: - width, height = self.manual_size_request - if width == -1: - width = 10000 - if height == -1: - height = 10000 - size = self.view.cell().cellSizeForBounds_( - NSRect((0, 0), (width, height))) - else: - size = self.view.cell().cellSize() - return (size.width, size.height) - - def baseline(self): - return -self.view.font().descender() + 1 - - def get_checked(self): - return self.view.state() == NSOnState - - def set_checked(self, value): - if value: - self.view.setState_(NSOnState) - else: - self.view.setState_(NSOffState) - - def enable(self): - SizedControl.enable(self) - self.view.setEnabled_(True) - - def disable(self): - SizedControl.disable(self) - self.view.setEnabled_(False) - - def get_text_padding(self): - """ - Returns the amount of space the checkbox takes up before the label. - """ - # XXX FIXME - return 18 - -class Button(SizedControl): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, label, style='normal', width=0): - SizedControl.__init__(self) - self.color = None - self.title = label - self.create_signal('clicked') - self.view = MiroButton.alloc().initWithSignal_('clicked') - self.view.setButtonType_(NSMomentaryPushInButton) - self._set_title() - self.setup_style(style) - self.min_width = width - - def set_text(self, label): - self.title = label - self._set_title() - - def set_color(self, color): - self.color = self.make_color(color) - self._set_title() - - def _set_title(self): - if self.color is None: - self.view.setTitle_(self.title) - else: - attributes = { - NSForegroundColorAttributeName: self.color, - NSFontAttributeName: self.view.font() - } - string = NSAttributedString.alloc().initWithString_attributes_( - self.title, attributes) - self.view.setAttributedTitle_(string) - - def setup_style(self, style): - if style == 'normal': - self.view.setBezelStyle_(NSRoundedBezelStyle) - self.pad_height = 0 - self.pad_width = 10 - self.min_width = 112 - elif style == 'smooth': - self.view.setBezelStyle_(NSRoundRectBezelStyle) - self.pad_width = 0 - self.pad_height = 4 - self.paragraph_style = NSMutableParagraphStyle.alloc().init() - self.paragraph_style.setAlignment_(NSCenterTextAlignment) - - def make_default(self): - self.view.setKeyEquivalent_("\r") - - def calc_size_request(self): - size = self.view.cell().cellSize() - width = max(self.min_width, size.width + self.pad_width) - height = size.height + self.pad_height - return width, height - - def baseline(self): - return -self.view.font().descender() + 10 + self.pad_height - - def enable(self): - SizedControl.enable(self) - self.view.setEnabled_(True) - - def disable(self): - SizedControl.disable(self) - self.view.setEnabled_(False) - -class MiroPopupButton(NSPopUpButton): - - def init(self): - self = super(MiroPopupButton, self).init() - self.setTarget_(self) - self.setAction_('handleChange:') - return self - - def handleChange_(self, sender): - wrappermap.wrapper(self).emit('changed', self.indexOfSelectedItem()) - -class OptionMenu(SizedControl): - def __init__(self, options): - SizedControl.__init__(self) - self.create_signal('changed') - self.view = MiroPopupButton.alloc().init() - self.options = options - for option, value in options: - self.view.addItemWithTitle_(option) - - def baseline(self): - if self.view.cell().controlSize() == NSRegularControlSize: - return -self.view.font().descender() + 6 - else: - return -self.view.font().descender() + 5 - - def calc_size_request(self): - return self.view.cell().cellSize() - - def set_selected(self, index): - self.view.selectItemAtIndex_(index) - - def get_selected(self): - return self.view.indexOfSelectedItem() - - def enable(self): - SizedControl.enable(self) - self.view.setEnabled_(True) - - def disable(self): - SizedControl.disable(self) - self.view.setEnabled_(False) - - def set_width(self, width): - # TODO - pass - -class RadioButtonGroup: - def __init__(self): - self._buttons = [] - - def handle_click(self, widget): - self.set_selected(widget) - - def add_button(self, button): - self._buttons.append(button) - button.connect('clicked', self.handle_click) - if len(self._buttons) == 1: - button.view.setState_(NSOnState) - else: - button.view.setState_(NSOffState) - - def get_buttons(self): - return self._buttons - - def get_selected(self): - for mem in self._buttons: - if mem.get_selected(): - return mem - - def set_selected(self, button): - for mem in self._buttons: - if button is mem: - mem.view.setState_(NSOnState) - else: - mem.view.setState_(NSOffState) - -class RadioButton(SizedControl): - def __init__(self, label, group=None, bold=False, color=None): - SizedControl.__init__(self) - self.create_signal('clicked') - self.view = MiroButton.alloc().initWithSignal_('clicked') - self.view.setButtonType_(NSRadioButton) - self.color = self.make_color(color) - self.title = label - self.bold = bold - self.font_size = NSFont.systemFontSize() - self._set_title() - - if group is not None: - self.group = group - else: - self.group = RadioButtonGroup() - - self.group.add_button(self) - - def set_size(self, size): - SizedControl.set_size(self, size) - self._set_title() - - def _set_title(self): - if self.color is None: - self.view.setTitle_(self.title) - else: - attributes = { - NSForegroundColorAttributeName: self.color, - NSFontAttributeName: NSFont.systemFontOfSize_(self.font_size) - } - string = NSAttributedString.alloc().initWithString_attributes_( - self.title, attributes) - self.view.setAttributedTitle_(string) - - def calc_size_request(self): - size = self.view.cell().cellSize() - return (size.width, size.height) - - def baseline(self): - -self.view.font().descender() + 2 - - def get_group(self): - return self.group - - def get_selected(self): - return self.view.state() == NSOnState - - def set_selected(self): - self.group.set_selected(self) - - def enable(self): - SizedControl.enable(self) - self.view.setEnabled_(True) - - def disable(self): - SizedControl.disable(self) - self.view.setEnabled_(False) diff --git a/mvc/widgets/osx/customcontrol.py b/mvc/widgets/osx/customcontrol.py deleted file mode 100644 index d100f33..0000000 --- a/mvc/widgets/osx/customcontrol.py +++ /dev/null @@ -1,436 +0,0 @@ -# @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. - -""".customcontrol -- CustomControl handlers. """ - -import collections - -from AppKit import * -from Foundation import * -from objc import YES, NO, nil - -from mvc.widgets import widgetconst -import wrappermap -from .base import Widget -import drawing -from .layoutmanager import LayoutManager - -class DrawableButtonCell(NSButtonCell): - def startTrackingAt_inView_(self, point, view): - view.setState_(NSOnState) - return YES - - def continueTracking_at_inView_(self, lastPoint, at, view): - view.setState_(NSOnState) - return YES - - def stopTracking_at_inView_mouseIsUp_(self, lastPoint, at, view, mouseIsUp): - if not mouseIsUp: - view.mouse_inside = False - view.setState_(NSOffState) - -class DrawableButton(NSButton): - def init(self): - self = super(DrawableButton, self).init() - self.layout_manager = LayoutManager() - self.tracking_area = None - self.mouse_inside = False - self.custom_cursor = None - return self - - def resetCursorRects(self): - if self.custom_cursor is not None: - self.addCursorRect_cursor_(self.visibleRect(), self.custom_cursor) - self.custom_cursor.setOnMouseEntered_(YES) - - def updateTrackingAreas(self): - # remove existing tracking area if needed - if self.tracking_area: - self.removeTrackingArea_(self.tracking_area) - - # create a new tracking area for the entire view. This allows us to - # get mouseMoved events whenever the mouse is inside our view. - self.tracking_area = NSTrackingArea.alloc() - self.tracking_area.initWithRect_options_owner_userInfo_( - self.visibleRect(), - NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | - NSTrackingActiveInKeyWindow, - self, - nil) - self.addTrackingArea_(self.tracking_area) - - def mouseEntered_(self, event): - window = self.window() - if window is not nil and window.isMainWindow(): - self.mouse_inside = True - self.setNeedsDisplay_(YES) - - def mouseExited_(self, event): - window = self.window() - if window is not nil and window.isMainWindow(): - self.mouse_inside = False - self.setNeedsDisplay_(YES) - - def isOpaque(self): - return wrappermap.wrapper(self).is_opaque() - - def drawRect_(self, rect): - context = drawing.DrawingContext(self, self.bounds(), rect) - context.style = drawing.DrawingStyle() - wrapper = wrappermap.wrapper(self) - wrapper.state = 'normal' - disabled = wrapper.get_disabled() - if not disabled: - if self.state() == NSOnState: - wrapper.state = 'pressed' - elif self.mouse_inside: - wrapper.state = 'hover' - else: - wrapper.state = 'normal' - - wrapper.draw(context, self.layout_manager) - self.layout_manager.reset() - - def sendAction_to_(self, action, to): - # We override the Cocoa machinery here and just send it to our wrapper - # widget. - wrapper = wrappermap.wrapper(self) - disabled = wrapper.get_disabled() - if not disabled: - wrapper.emit('clicked') - # Tell Cocoa we handled it anyway, just not emit the actual clicked - # event. - return YES -DrawableButton.setCellClass_(DrawableButtonCell) - -class ContinousButtonCell(DrawableButtonCell): - def stopTracking_at_inView_mouseIsUp_(self, lastPoint, at, view, mouseIsUp): - view.onStopTracking(at) - NSButtonCell.stopTracking_at_inView_mouseIsUp_(self, lastPoint, at, - view, mouseIsUp) - -class ContinuousDrawableButton(DrawableButton): - def init(self): - self = super(ContinuousDrawableButton, self).init() - self.setContinuous_(YES) - return self - - def mouseDown_(self, event): - self.releaseInbounds = self.stopTracking = self.firedOnce = False - self.cell().trackMouse_inRect_ofView_untilMouseUp_(event, - self.bounds(), self, YES) - wrapper = wrappermap.wrapper(self) - if not wrapper.get_disabled(): - if self.firedOnce: - wrapper.emit('released') - elif self.releaseInbounds: - wrapper.emit('clicked') - - def sendAction_to_(self, action, to): - if self.stopTracking: - return NO - self.firedOnce = True - wrapper = wrappermap.wrapper(self) - if not wrapper.get_disabled(): - wrapper.emit('held-down') - return YES - - def onStopTracking(self, mouseLocation): - self.releaseInbounds = NSPointInRect(mouseLocation, self.bounds()) - self.stopTracking = True -ContinuousDrawableButton.setCellClass_(ContinousButtonCell) - -class DragableButtonCell(NSButtonCell): - def startTrackingAt_inView_(self, point, view): - self.start_x = point.x - return YES - - def continueTracking_at_inView_(self, lastPoint, at, view): - DRAG_THRESHOLD = 15 - wrapper = wrappermap.wrapper(view) - if not wrapper.get_disabled(): - if (view.last_drag_event != 'right' and - at.x > self.start_x + DRAG_THRESHOLD): - wrapper.emit("dragged-right") - view.last_drag_event = 'right' - elif (view.last_drag_event != 'left' and - at.x < self.start_x - DRAG_THRESHOLD): - view.last_drag_event = 'left' - wrapper.emit("dragged-left") - return YES - -class DragableDrawableButton(DrawableButton): - def mouseDown_(self, event): - self.last_drag_event = None - self.cell().trackMouse_inRect_ofView_untilMouseUp_(event, - self.bounds(), self, YES) - - def sendAction_to_(self, action, to): - # only send the click event if we didn't send a - # dragged-left/dragged-right event - wrapper = wrappermap.wrapper(self) - if self.last_drag_event is None and not wrapper.get_disabled(): - wrapper.emit('clicked') - return YES -DragableDrawableButton.setCellClass_(DragableButtonCell) - -MouseTrackingInfo = collections.namedtuple("MouseTrackingInfo", - "start_pos click_pos") - -class CustomSliderCell(NSSliderCell): - def calc_slider_amount(self, view, pos, size): - slider_size = wrappermap.wrapper(view).slider_size() - pos -= slider_size / 2 - size -= slider_size - return max(0, min(1, float(pos) / size)) - - def get_slider_pos(self, view, value=None): - if value is None: - value = view.floatValue() - if view.isVertical(): - size = view.bounds().size.height - else: - size = view.bounds().size.width - slider_size = view.knobThickness() - size -= slider_size - start_pos = slider_size / 2.0 - ratio = ((value - view.minValue()) / - view.maxValue() - view.minValue()) - return start_pos + (ratio * size) - - def startTrackingAt_inView_(self, at, view): - wrapper = wrappermap.wrapper(view) - start_pos = self.get_slider_pos(view) - if self.isVertical(): - click_pos = at.y - else: - click_pos = at.x - # only move the cursor if the click was outside the slider - if abs(click_pos - start_pos) > view.knobThickness() / 2: - self.moveSliderTo(view, click_pos) - start_pos = click_pos - view.mouse_tracking_info = MouseTrackingInfo(start_pos, click_pos) - if not wrapper.get_disabled(): - wrapper.emit('pressed') - return YES - - def moveSliderTo(self, view, pos): - if view.isVertical(): - size = view.bounds().size.height - else: - size = view.bounds().size.width - - slider_amount = self.calc_slider_amount(view, pos, size) - value = (self.maxValue() - self.minValue()) * slider_amount - self.setFloatValue_(value) - wrapper = wrappermap.wrapper(view) - if not wrapper.get_disabled(): - wrapper.emit('moved', value) - if self.isContinuous(): - wrapper.emit('changed', value) - - def continueTracking_at_inView_(self, lastPoint, at, view): - if view.isVertical(): - mouse_pos = at.y - else: - mouse_pos = at.x - - info = view.mouse_tracking_info - new_pos = info.start_pos + (mouse_pos - info.click_pos) - self.moveSliderTo(view, new_pos) - return YES - - def stopTracking_at_inView_mouseIsUp_(self, lastPoint, at, view, mouseUp): - wrapper = wrappermap.wrapper(view) - if not wrapper.get_disabled(): - wrapper.emit('released') - view.mouse_tracking_info = None - -class CustomSliderView(NSSlider): - def init(self): - self = super(CustomSliderView, self).init() - self.layout_manager = LayoutManager() - self.custom_cursor = None - self.mouse_tracking_info = None - return self - - def get_slider_pos(self, value=None): - return self.cell().get_slider_pos(self, value) - - def resetCursorRects(self): - if self.custom_cursor is not None: - self.addCursorRect_cursor_(self.visibleRect(), self.custom_cursor) - self.custom_cursor.setOnMouseEntered_(YES) - - def isOpaque(self): - return wrappermap.wrapper(self).is_opaque() - - def knobThickness(self): - return wrappermap.wrapper(self).slider_size() - - def scrollWheel_(self, event): - wrapper = wrappermap.wrapper(self) - if wrapper.get_disabled(): - return - # NOTE: we ignore the scroll_step value passed into set_increments() - # and calculate the change using deltaY, which is in device - # coordinates. - slider_size = wrapper.slider_size() - if wrapper.is_horizontal(): - size = self.bounds().size.width - else: - size = self.bounds().size.height - size -= slider_size - - range = self.maxValue() - self.minValue() - value_change = (event.deltaY() / size) * range - self.setFloatValue_(self.floatValue() + value_change) - wrapper.emit('pressed') - wrapper.emit('changed', self.floatValue()) - wrapper.emit('released') - - def isVertical(self): - return not wrappermap.wrapper(self).is_horizontal() - - def drawRect_(self, rect): - context = drawing.DrawingContext(self, self.bounds(), rect) - context.style = drawing.DrawingStyle() - wrappermap.wrapper(self).draw(context, self.layout_manager) - self.layout_manager.reset() - - def sendAction_to_(self, action, to): - # We override the Cocoa machinery here and just send it to our wrapper - # widget. - wrapper = wrappermap.wrapper(self) - disabled = wrapper.get_disabled() - if not disabled: - wrapper.emit('changed', self.floatValue()) - # Total Cocoa we handled it anyway to prevent the event passed to - # upper layer. - return YES -CustomSliderView.setCellClass_(CustomSliderCell) - -class CustomControlBase(drawing.DrawingMixin, Widget): - def set_cursor(self, cursor): - if cursor == widgetconst.CURSOR_NORMAL: - self.view.custom_cursor = None - elif cursor == widgetconst.CURSOR_POINTING_HAND: - self.view.custom_cursor = NSCursor.pointingHandCursor() - else: - raise ValueError("Unknown cursor: %s" % cursor) - if self.view.window(): - self.view.window().invalidateCursorRectsForView_(self.view) - -class CustomButton(CustomControlBase): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self): - CustomControlBase.__init__(self) - self.create_signal('clicked') - self.view = DrawableButton.alloc().init() - self.view.setRefusesFirstResponder_(NO) - self.view.setEnabled_(True) - - def enable(self): - Widget.enable(self) - self.view.setNeedsDisplay_(YES) - - def disable(self): - Widget.disable(self) - self.view.setNeedsDisplay_(YES) - -class ContinuousCustomButton(CustomButton): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self): - CustomButton.__init__(self) - self.create_signal('held-down') - self.create_signal('released') - self.view = ContinuousDrawableButton.alloc().init() - self.view.setRefusesFirstResponder_(NO) - - def set_delays(self, initial, repeat): - self.view.cell().setPeriodicDelay_interval_(initial, repeat) - -class DragableCustomButton(CustomButton): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self): - CustomButton.__init__(self) - self.create_signal('dragged-left') - self.create_signal('dragged-right') - self.view = DragableDrawableButton.alloc().init() - -class CustomSlider(CustomControlBase): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self): - CustomControlBase.__init__(self) - self.create_signal('pressed') - self.create_signal('released') - self.create_signal('changed') - self.create_signal('moved') - self.view = CustomSliderView.alloc().init() - self.view.setRefusesFirstResponder_(NO) - if self.is_continuous(): - self.view.setContinuous_(YES) - else: - self.view.setContinuous_(NO) - self.view.setEnabled_(True) - - def get_slider_pos(self, value=None): - return self.view.get_slider_pos(value) - - def viewport_created(self): - self.view.cell().setKnobThickness_(self.slider_size()) - - def get_value(self): - return self.view.floatValue() - - def set_value(self, value): - self.view.setFloatValue_(value) - - def get_range(self): - return self.view.minValue(), self.view.maxValue() - - def set_range(self, min_value, max_value): - self.view.setMinValue_(min_value) - self.view.setMaxValue_(max_value) - - def set_increments(self, small_step, big_step, scroll_step=None): - # NOTE: we ignore all of these parameters. - # - # Cocoa doesn't have a concept of changing the increments for - # NSScroller. scroll_step is isn't really compatible with - # the event object that's passed to scrollWheel_() - pass - - def enable(self): - Widget.enable(self) - self.view.setNeedsDisplay_(YES) - - def disable(self): - Widget.disable(self) - self.view.setNeedsDisplay_(YES) diff --git a/mvc/widgets/osx/drawing.py b/mvc/widgets/osx/drawing.py deleted file mode 100644 index aaad1e9..0000000 --- a/mvc/widgets/osx/drawing.py +++ /dev/null @@ -1,289 +0,0 @@ -# @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. - -"""miro.plat.frontend.widgets.drawing -- Draw on Views.""" - -import math - -from Foundation import * -from AppKit import * -#from Quartz import * -from objc import YES, NO, nil - - -class ImageSurface: - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, image): - """Create a new ImageSurface.""" - self.image = image.nsimage.copy() - self.width = image.width - self.height = image.height - - def get_size(self): - return self.width, self.height - - def draw(self, context, x, y, width, height, fraction=1.0): - if self.width == 0 or self.height == 0: - return - current_context = NSGraphicsContext.currentContext() - current_context.setShouldAntialias_(YES) - current_context.setImageInterpolation_(NSImageInterpolationHigh) - current_context.saveGraphicsState() - flip_context(y + height) - dest_rect = NSMakeRect(x, 0, width, height) - if self.width >= width and self.height >= height: - # drawing to area smaller than our image - source_rect = NSMakeRect(0, 0, width, height) - self.image.drawInRect_fromRect_operation_fraction_( - dest_rect, source_rect, NSCompositeSourceOver, fraction) - else: - # drawing to area larger than our image. Need to tile it. - NSColor.colorWithPatternImage_(self.image).set() - current_context.setPatternPhase_( - self._calc_pattern_phase(context, x, y)) - NSBezierPath.fillRect_(dest_rect) - current_context.restoreGraphicsState() - - def draw_rect(self, context, dest_x, dest_y, source_x, source_y, width, - height, fraction=1.0): - if width == 0 or height == 0: - return - current_context = NSGraphicsContext.currentContext() - current_context.setShouldAntialias_(YES) - current_context.setImageInterpolation_(NSImageInterpolationHigh) - current_context.saveGraphicsState() - flip_context(dest_y + height) - dest_y = 0 - dest_rect = NSMakeRect(dest_x, dest_y, width, height) - source_rect = NSMakeRect(source_x, self.height-source_y-height, - width, height) - self.image.drawInRect_fromRect_operation_fraction_( - dest_rect, source_rect, NSCompositeSourceOver, fraction) - current_context.restoreGraphicsState() - - def _calc_pattern_phase(self, context, x, y): - """Calculate the pattern phase to draw tiled images. - - When we draw with a pattern, we want the image in the pattern to start - at the top-left of where we're drawing to. This function does the - dirty work necessary. - - :returns: NSPoint to send to setPatternPhase_ - """ - # convert to view coords - view_point = NSPoint(context.origin.x + x, context.origin.y + y) - # convert to window coords, which is setPatternPhase_ uses - return context.view.convertPoint_toView_(view_point, nil) - -def convert_cocoa_color(color): - rgb = color.colorUsingColorSpaceName_(NSDeviceRGBColorSpace) - return (rgb.redComponent(), rgb.greenComponent(), rgb.blueComponent()) - -def convert_widget_color(color, alpha=1.0): - return NSColor.colorWithDeviceRed_green_blue_alpha_(color[0], color[1], - color[2], alpha) -def flip_context(height): - """Make the current context's coordinates flipped. - - This is useful for drawing images, since they use the normal cocoa - coordinates and we use flipped versions. - - :param height: height of the current area we are drawing to. - """ - xform = NSAffineTransform.transform() - xform.translateXBy_yBy_(0, height) - xform.scaleXBy_yBy_(1.0, -1.0) - xform.concat() - -class DrawingStyle(object): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, bg_color=None, text_color=None): - self.use_custom_style = True - if text_color is None: - self.text_color = self.default_text_color - else: - self.text_color = convert_cocoa_color(text_color) - if bg_color is None: - self.bg_color = self.default_bg_color - else: - self.bg_color = convert_cocoa_color(bg_color) - - default_text_color = convert_cocoa_color(NSColor.textColor()) - default_bg_color = convert_cocoa_color(NSColor.textBackgroundColor()) - -class DrawingContext: - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, view, drawing_area, rect): - self.view = view - self.path = NSBezierPath.bezierPath() - self.color = NSColor.blackColor() - self.width = drawing_area.size.width - self.height = drawing_area.size.height - self.origin = drawing_area.origin - if drawing_area.origin != NSZeroPoint: - xform = NSAffineTransform.transform() - xform.translateXBy_yBy_(drawing_area.origin.x, - drawing_area.origin.y) - xform.concat() - - def move_to(self, x, y): - self.path.moveToPoint_(NSPoint(x, y)) - - def rel_move_to(self, dx, dy): - self.path.relativeMoveToPoint_(NSPoint(dx, dy)) - - def line_to(self, x, y): - self.path.lineToPoint_(NSPoint(x, y)) - - def rel_line_to(self, dx, dy): - self.path.relativeLineToPoint_(NSPoint(dx, dy)) - - def curve_to(self, x1, y1, x2, y2, x3, y3): - self.path.curveToPoint_controlPoint1_controlPoint2_( - NSPoint(x3, y3), NSPoint(x1, y1), NSPoint(x2, y2)) - - def rel_curve_to(self, dx1, dy1, dx2, dy2, dx3, dy3): - self.path.relativeCurveToPoint_controlPoint1_controlPoint2_( - NSPoint(dx3, dy3), NSPoint(dx1, dy1), NSPoint(dx2, dy2)) - - def arc(self, x, y, radius, angle1, angle2): - angle1 = (angle1 * 360) / (2 * math.pi) - angle2 = (angle2 * 360) / (2 * math.pi) - center = NSPoint(x, y) - self.path.appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_(center, radius, angle1, angle2) - - def arc_negative(self, x, y, radius, angle1, angle2): - angle1 = (angle1 * 360) / (2 * math.pi) - angle2 = (angle2 * 360) / (2 * math.pi) - center = NSPoint(x, y) - self.path.appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_clockwise_(center, radius, angle1, angle2, YES) - - def rectangle(self, x, y, width, height): - rect = NSMakeRect(x, y, width, height) - self.path.appendBezierPathWithRect_(rect) - - def set_color(self, color, alpha=1.0): - self.color = convert_widget_color(color, alpha) - self.color.set() - - def set_shadow(self, color, opacity, offset, blur_radius): - shadow = NSShadow.alloc().init() - # shadow offset is always in the cocoa coordinates, so we need to - # reverse the y part - shadow.setShadowOffset_(NSPoint(offset[0], -offset[1])) - shadow.setShadowBlurRadius_(blur_radius) - shadow.setShadowColor_(convert_widget_color(color, opacity)) - shadow.set() - - def set_line_width(self, width): - self.path.setLineWidth_(width) - - def stroke(self): - self.path.stroke() - self.path.removeAllPoints() - - def stroke_preserve(self): - self.path.stroke() - - def fill(self): - self.path.fill() - self.path.removeAllPoints() - - def fill_preserve(self): - self.path.fill() - - def clip(self): - self.path.addClip() - self.path.removeAllPoints() - - def save(self): - NSGraphicsContext.currentContext().saveGraphicsState() - - def restore(self): - NSGraphicsContext.currentContext().restoreGraphicsState() - - def gradient_fill(self, gradient): - self.gradient_fill_preserve(gradient) - self.path.removeAllPoints() - - def gradient_fill_preserve(self, gradient): - context = NSGraphicsContext.currentContext() - context.saveGraphicsState() - self.path.addClip() - gradient.draw() - context.restoreGraphicsState() - -class Gradient(object): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, x1, y1, x2, y2): - self.x1, self.y1, self.x2, self.y2 = x1, y1, x2, y2 - self.start_color = None - self.end_color = None - - def set_start_color(self, (red, green, blue)): - self.start_color = (red, green, blue) - - def set_end_color(self, (red, green, blue)): - self.end_color = (red, green, blue) - - def draw(self): - start_color = convert_widget_color(self.start_color) - end_color = convert_widget_color(self.end_color) - nsgradient = NSGradient.alloc().initWithStartingColor_endingColor_(start_color, end_color) - start_point = NSPoint(self.x1, self.y1) - end_point = NSPoint(self.x2, self.y2) - nsgradient.drawFromPoint_toPoint_options_(start_point, end_point, 0) - -class DrawingMixin(object): - def calc_size_request(self): - return self.size_request(self.view.layout_manager) - - # squish width / squish height only make sense on GTK - def set_squish_width(self, setting): - pass - - def set_squish_height(self, setting): - pass - - # Default implementations for methods that subclasses override. - - def is_opaque(self): - return False - - def size_request(self, layout_manager): - return 0, 0 - - def draw(self, context, layout_manager): - pass - - def viewport_repositioned(self): - # since this is a Mixin class, we want to make sure that our other - # classes see the viewport_repositioned() call. - super(DrawingMixin, self).viewport_repositioned() - self.queue_redraw() diff --git a/mvc/widgets/osx/drawingwidgets.py b/mvc/widgets/osx/drawingwidgets.py deleted file mode 100644 index 74e8232..0000000 --- a/mvc/widgets/osx/drawingwidgets.py +++ /dev/null @@ -1,67 +0,0 @@ -# @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. - -"""drawingviews.py -- views that support custom drawing.""" - -import wrappermap -import drawing -from .base import Widget, SimpleBin, FlippedView -from .layoutmanager import LayoutManager - -class DrawingView(FlippedView): - def init(self): - self = super(DrawingView, self).init() - self.layout_manager = LayoutManager() - return self - - def isOpaque(self): - return wrappermap.wrapper(self).is_opaque() - - def drawRect_(self, rect): - context = drawing.DrawingContext(self, self.bounds(), rect) - context.style = drawing.DrawingStyle() - wrappermap.wrapper(self).draw(context, self.layout_manager) - -class DrawingArea(drawing.DrawingMixin, Widget): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self): - Widget.__init__(self) - self.view = DrawingView.alloc().init() - -class Background(drawing.DrawingMixin, SimpleBin): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self): - SimpleBin.__init__(self) - self.view = DrawingView.alloc().init() - - def calc_size_request(self): - drawing_size = drawing.DrawingMixin.calc_size_request(self) - container_size = SimpleBin.calc_size_request(self) - return (max(container_size[0], drawing_size[0]), - max(container_size[1], drawing_size[1])) diff --git a/mvc/widgets/osx/fasttypes.c b/mvc/widgets/osx/fasttypes.c deleted file mode 100644 index 72d3b5b..0000000 --- a/mvc/widgets/osx/fasttypes.c +++ /dev/null @@ -1,540 +0,0 @@ -/* -# @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. - */ - -#include <Python.h> - -/* - * fasttypes.c - * - * Datastructures written in C to be fast. This used to be a big C++ file - * that depended on boost. Nowadays we only define LinkedList, which is easy - * enough to implement in pure C. - */ - -static int nodes_deleted = 0; // debugging only - -/* forward define python type objects */ - -static PyTypeObject LinkedListType; -static PyTypeObject LinkedListIterType; - -/* Structure definitions */ - -typedef struct LinkedListNode { - PyObject *obj; - struct LinkedListNode* next; - struct LinkedListNode* prev; - int deleted; // Has this node been removed? - int iter_count; // How many LinkedListIters point to this node? -} LinkedListNode; - -typedef struct { - PyObject_HEAD - int count; - LinkedListNode* sentinal; - // sentinal object to make list operations simpler/faster and equivalent - // to the boost API. It's prev node is the last element in the list and - // it's next node is the first -} LinkedListObject; - -typedef struct { - PyObject_HEAD - LinkedListNode* node; - LinkedListObject* list; -} LinkedListIterObject; - -/* LinkedListNode */ - -void check_node_deleted(LinkedListNode* node) -{ - if(node->iter_count <= 0 && node->deleted) { - free(node); - nodes_deleted += 1; - } -} - -static int remove_node(LinkedListObject* self, LinkedListNode* node) -{ - if(node->obj == NULL) { - PyErr_SetString(PyExc_IndexError, "can't remove lastIter()"); - return 0; - } - node->next->prev = node->prev; - node->prev->next = node->next; - node->deleted = 1; - self->count -= 1; - Py_DECREF(node->obj); - check_node_deleted(node); - return 1; -} - -/* LinkedListIter */ - -void switch_node(LinkedListIterObject* self, LinkedListNode* new_node) -{ - LinkedListNode* old_node; - - old_node = self->node; - self->node = new_node; - old_node->iter_count--; - self->node->iter_count++; - check_node_deleted(old_node); -} - -// Note that we don't expose the new method to python. We create -// LinkedListIters in the factory methods firstIter() and lastIter() -static LinkedListIterObject* LinkedListIterObject_new(LinkedListObject*list, - LinkedListNode* node) -{ - LinkedListIterObject* self; - - self = (LinkedListIterObject*)PyType_GenericAlloc(&LinkedListIterType, 0); - if(self != NULL) { - self->node = node; - self->list = list; - node->iter_count++; - } - return self; -} - -static void LinkedListIterObject_dealloc(LinkedListIterObject* self) -{ - self->node->iter_count--; - check_node_deleted(self->node); -} - -static PyObject *LinkedListIter_forward(LinkedListIterObject* self, PyObject *obj) -{ - switch_node(self, self->node->next); - Py_RETURN_NONE; -} - -static PyObject *LinkedListIter_back(LinkedListIterObject* self, PyObject *obj) -{ - switch_node(self, self->node->prev); - Py_RETURN_NONE; -} - -static PyObject *LinkedListIter_value(LinkedListIterObject* self, PyObject *obj) -{ - PyObject* retval; - - if(self->node->deleted) { - PyErr_SetString(PyExc_ValueError, "Node deleted"); - return NULL; - } - retval = self->node->obj; - if(retval == NULL) { - PyErr_SetString(PyExc_IndexError, "can't get value of lastIter()"); - return NULL; - } - Py_INCREF(retval); - return retval; -} - -static PyObject *LinkedListIter_copy(LinkedListIterObject* self, PyObject *obj) -{ - return (PyObject*)LinkedListIterObject_new(self->list, self->node); -} - -static PyObject *LinkedListIter_valid(LinkedListIterObject* self, PyObject *obj) -{ - return PyBool_FromLong(self->node->deleted == 0); -} - -PyObject* LinkedListIter_richcmp(LinkedListIterObject *o1, - LinkedListIterObject *o2, int opid) -{ - if(!PyObject_TypeCheck(o1, &LinkedListIterType) || - !PyObject_TypeCheck(o2, &LinkedListIterType)) { - return Py_NotImplemented; - } - switch(opid) { - case Py_EQ: - if(o1->node == o2->node) Py_RETURN_TRUE; - else Py_RETURN_FALSE; - case Py_NE: - if(o1->node != o2->node) Py_RETURN_TRUE; - else Py_RETURN_FALSE; - default: - return Py_NotImplemented; - } -} - -static PyMethodDef LinkedListIter_methods[] = { - {"forward", (PyCFunction)LinkedListIter_forward, METH_NOARGS, - "Move to the next element", - }, - {"back", (PyCFunction)LinkedListIter_back, METH_NOARGS, - "Move to the previous element", - }, - {"value", (PyCFunction)LinkedListIter_value, METH_NOARGS, - "Return the current element", - }, - {"copy", (PyCFunction)LinkedListIter_copy, METH_NOARGS, - "Duplicate iter", - }, - {"valid", (PyCFunction)LinkedListIter_valid, METH_NOARGS, - "Test if the iter is valid", - }, - {NULL}, -}; - -static PyTypeObject LinkedListIterType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ - "fasttypes.LinkedListIter", /* tp_name */ - sizeof(LinkedListIterObject), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)LinkedListIterObject_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - 0, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_RICHCOMPARE, /* tp_flags */ - "fasttypes LinkedListIter", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - (richcmpfunc)LinkedListIter_richcmp, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - LinkedListIter_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - 0, /* tp_init */ - 0, /* tp_alloc */ - 0, /* tp_new */ -}; - -/* LinkedList */ - -LinkedListNode* make_new_node(PyObject* obj, LinkedListNode* prev, - LinkedListNode* next) -{ - LinkedListNode* retval; - retval = malloc(sizeof(LinkedListNode)); - if(!retval) { - PyErr_SetString(PyExc_MemoryError, "can't create new node"); - return NULL; - } - Py_XINCREF(obj); - retval->obj = obj; - retval->prev = prev; - retval->next = next; - retval->iter_count = retval->deleted = 0; - return retval; -} - -void set_iter_type_error(PyObject* obj) -{ - // Set an exception when we expected a LinkedListIter and got something - // else - PyObject* args; - PyObject* fmt; - PyObject* err_str; - - args = Py_BuildValue("(O)", obj); - fmt = PyString_FromString("Expected LinkedListIter, got %r"); - err_str = PyString_Format(fmt, args); - PyErr_SetObject(PyExc_TypeError, err_str); - Py_DECREF(fmt); - Py_DECREF(err_str); - Py_DECREF(args); -} - -static PyObject* insert_before(LinkedListObject* self, LinkedListNode* node, - PyObject* obj) -{ - LinkedListNode* new_node; - PyObject* retval; - - new_node = make_new_node(obj, node->prev, node); - if(!new_node) return NULL; - node->prev->next = new_node; - node->prev = new_node; - self->count += 1; - retval = (PyObject*)LinkedListIterObject_new(self, new_node); - return retval; -} - -static PyObject* LinkedList_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - LinkedListObject *self; - LinkedListNode *sentinal; - - self = (LinkedListObject *)type->tp_alloc(type, 0); - if (self == NULL) return NULL; - - sentinal = make_new_node(NULL, NULL, NULL); - if(!sentinal) { - Py_DECREF(self); - return NULL; - } - self->sentinal = sentinal->next = sentinal->prev = sentinal; - sentinal->iter_count = 1; // prevent the sentinal from being deleted - self->count = 0; - - return (PyObject *)self; -} - -static void LinkedList_dealloc(LinkedListObject* self) -{ - LinkedListNode *node, *tmp; - - node = self->sentinal->next; - while(node != self->sentinal) { - node->deleted = 1; - tmp = node->next; - check_node_deleted(node); - node = tmp; - } - - self->sentinal->iter_count -= 1; - check_node_deleted(self->sentinal); - return; -} - -static int LinkedList_init(LinkedListObject *self) -{ - self->count = 0; - return 0; -} - -static Py_ssize_t LinkedList_len(LinkedListObject *self) -{ - return self->count; -} - -static PyObject* LinkedList_get(LinkedListObject *self, - LinkedListIterObject *iter) -{ - if(!PyObject_TypeCheck(iter, &LinkedListIterType)) { - set_iter_type_error((PyObject*)iter); - return NULL; - } - return PyObject_CallMethod((PyObject*)iter, "value", "()"); -} -int LinkedList_set(LinkedListObject *self, LinkedListIterObject *iter, - PyObject *value) -{ - if(!PyObject_TypeCheck(iter, &LinkedListIterType)) { - set_iter_type_error((PyObject*)iter); - return -1; - } - if(iter->node->deleted) { - PyErr_SetString(PyExc_ValueError, "Node deleted"); - return -1; - } - if(iter->node->obj == NULL) { - PyErr_SetString(PyExc_IndexError, "can't set value of lastIter()"); - return -1; - } - if(value == NULL) { - if(!remove_node(self, iter->node)) return -1; - return 0; - } - Py_INCREF(value); - Py_DECREF(iter->node->obj); - iter->node->obj = value; - return 0; -} - -static PyObject *LinkedList_insertBefore(LinkedListObject* self, PyObject *args) -{ - LinkedListIterObject *iter; - PyObject *obj; - - if(!PyArg_ParseTuple(args, "OO", &iter, &obj)) return NULL; - if(!PyObject_TypeCheck(iter, &LinkedListIterType)) { - set_iter_type_error(obj); - return NULL; - } - - return insert_before(self, iter->node, obj); -} - -static PyObject *LinkedList_append(LinkedListObject* self, PyObject *obj) -{ - return insert_before(self, self->sentinal, obj); -} - -static PyObject *LinkedList_remove(LinkedListObject* self, - LinkedListIterObject *iter) -{ - LinkedListNode* next_node; - if(!PyObject_TypeCheck(iter, &LinkedListIterType)) { - set_iter_type_error((PyObject*)iter); - return NULL; - } - - next_node = iter->node->next; - if(!remove_node(self, iter->node)) return NULL; - return (PyObject*)LinkedListIterObject_new(self, next_node); -} - -static PyObject *LinkedList_firstIter(LinkedListObject* self, PyObject *obj) -{ - PyObject* retval; - retval = (PyObject*)LinkedListIterObject_new(self, self->sentinal->next); - return retval; -} - -static PyObject *LinkedList_lastIter(LinkedListObject* self, PyObject *obj) -{ - PyObject* retval; - retval = (PyObject*)LinkedListIterObject_new(self, self->sentinal); - return retval; -} - -static PyMappingMethods LinkedListMappingMethods = { - (lenfunc)LinkedList_len, - (binaryfunc)LinkedList_get, - (objobjargproc)LinkedList_set, -}; - -static PyMethodDef LinkedList_methods[] = { - {"insertBefore", (PyCFunction)LinkedList_insertBefore, METH_VARARGS, - "insert an element before iter", - }, - {"append", (PyCFunction)LinkedList_append, METH_O, - "append an element to the list", - }, - {"remove", (PyCFunction)LinkedList_remove, METH_O, - "remove an element to the list", - }, - {"firstIter", (PyCFunction)LinkedList_firstIter, METH_NOARGS, - "get an iter pointing to the first element in the list", - }, - {"lastIter", (PyCFunction)LinkedList_lastIter, METH_NOARGS, - "get an iter pointing to the last element in the list", - }, - {NULL}, -}; - -static PyTypeObject LinkedListType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ - "fasttypes.LinkedList", /* tp_name */ - sizeof(LinkedListObject), /* tp_basicsize */ - 0, /* tp_itemsize */ - (destructor)LinkedList_dealloc, /* tp_dealloc */ - 0, /* tp_print */ - 0, /* tp_getattr */ - 0, /* tp_setattr */ - 0, /* tp_compare */ - 0, /* tp_repr */ - 0, /* tp_as_number */ - 0, /* tp_as_sequence */ - &LinkedListMappingMethods, /* tp_as_mapping */ - 0, /* tp_hash */ - 0, /* tp_call */ - 0, /* tp_str */ - 0, /* tp_getattro */ - 0, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ - "fasttypes LinkedList", /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - LinkedList_methods, /* tp_methods */ - 0, /* tp_members */ - 0, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)LinkedList_init, /* tp_init */ - 0, /* tp_alloc */ - LinkedList_new, /* tp_new */ -}; - -/* Module-level stuff */ - -static PyObject *count_nodes_deleted(PyObject *obj) -{ - return PyInt_FromLong(nodes_deleted); -} - -static PyObject *reset_nodes_deleted(PyObject *obj) -{ - nodes_deleted = 0; - Py_RETURN_NONE; -} - - -static PyMethodDef FasttypesMethods[] = -{ - {"_count_nodes_deleted", (PyCFunction)count_nodes_deleted, METH_NOARGS, - "get a count of how many nodes have been deleted (DEBUGGING ONLY)", - }, - {"_reset_nodes_deleted", (PyCFunction)reset_nodes_deleted, METH_NOARGS, - "reset the count of how many nodes have been deleted (DEBUGGING ONLY)", - }, - { NULL, NULL, 0, NULL } -}; - -PyMODINIT_FUNC initfasttypes(void) -{ - PyObject *m; - - if (PyType_Ready(&LinkedListType) < 0) - return; - - if (PyType_Ready(&LinkedListIterType) < 0) - return; - - m = Py_InitModule("fasttypes", FasttypesMethods); - - Py_INCREF(&LinkedListType); - Py_INCREF(&LinkedListIterType); - PyModule_AddObject(m, "LinkedList", (PyObject *)&LinkedListType); -} diff --git a/mvc/widgets/osx/helpers.py b/mvc/widgets/osx/helpers.py deleted file mode 100644 index e4aa23a..0000000 --- a/mvc/widgets/osx/helpers.py +++ /dev/null @@ -1,95 +0,0 @@ -# @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. - -"""helper classes.""" - -import logging -import traceback - -from Foundation import * -from objc import nil - -class NotificationForwarder(NSObject): - """Forward notifications from a Cocoa object to a python class. - """ - - def initWithNSObject_center_(self, nsobject, center): - """Initialize the NotificationForwarder nsobject is the NSObject to - forward notifications for. It can be nil in which case notifications - from all objects will be forwarded. - - center is the NSNotificationCenter to get notifications from. It can - be None, in which cas the default notification center is used. - """ - self.nsobject = nsobject - self.callback_map = {} - if center is None: - self.center = NSNotificationCenter.defaultCenter() - else: - self.center = center - return self - - @classmethod - def create(cls, object, center=None): - """Helper method to call aloc() then initWithNSObject_center_().""" - return cls.alloc().initWithNSObject_center_(object, center) - - def connect(self, callback, name): - """Register to listen for notifications. - Only one callback for each notification name can be connected. - """ - - if name in self.callback_map: - raise ValueError("%s already connected" % name) - - self.callback_map[name] = callback - self.center.addObserver_selector_name_object_(self, 'observe:', name, - self.nsobject) - - def disconnect(self, name=None): - if name is not None: - self.center.removeObserver_name_object_(self, name, self.nsobject) - self.callback_map.pop(name) - else: - self.center.removeObserver_(self) - self.callback_map.clear() - - def observe_(self, notification): - name = notification.name() - callback = self.callback_map[name] - if callback is None: - logging.warn("Callback for %s is dead", name) - self.center.removeObverser_name_object_(self, name, self.nsobject) - return - try: - callback(notification) - except: - logging.warn("Callback for %s raised exception:%s\n", - name.encode('utf-8'), - traceback.format_exc()) diff --git a/mvc/widgets/osx/layout.py b/mvc/widgets/osx/layout.py deleted file mode 100644 index 0238975..0000000 --- a/mvc/widgets/osx/layout.py +++ /dev/null @@ -1,748 +0,0 @@ -# @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. - -""".layout -- Widgets that handle laying out other -widgets. - -We basically follow GTK's packing model. Widgets are packed into vboxes, -hboxes or other container widgets. The child widgets request a minimum size, -and the container widgets allocate space for their children. Widgets may get -more size then they requested in which case they have to deal with it. In -rare cases, widgets may get less size then they requested in which case they -should just make sure they don't throw an exception or segfault. - -Check out the GTK tutorial for more info. -""" - -import itertools - -from AppKit import * -from Foundation import * -from objc import YES, NO, nil, signature, loadBundle - -import tableview -import wrappermap -from .base import Container, Bin, FlippedView -from mvc.utils import Matrix - -# These don't seem to be in pyobjc's AppKit (yet) -NSScrollerKnobStyleDefault = 0 -NSScrollerKnobStyleDark = 1 -NSScrollerKnobStyleLight = 2 - -NSScrollerStyleLegacy = 0 -NSScrollerStyleOverlay = 1 - -def _extra_space_iter(extra_length, count): - """Utility function to allocate extra space left over in containers.""" - if count == 0: - return - extra_space, leftover = divmod(extra_length, count) - while leftover >= 1: - yield extra_space + 1 - leftover -= 1 - yield extra_space + leftover - while True: - yield extra_space - -class BoxPacking: - """Utility class to store how we are packing a single widget.""" - - def __init__(self, widget, expand, padding): - self.widget = widget - self.expand = expand - self.padding = padding - -class Box(Container): - """Base class for HBox and VBox. """ - CREATES_VIEW = False - - def __init__(self, spacing=0): - self.spacing = spacing - Container.__init__(self) - self.packing_start = [] - self.packing_end = [] - self.expand_count = 0 - - def packing_both(self): - return itertools.chain(self.packing_start, self.packing_end) - - def get_children(self): - for packing in self.packing_both(): - yield packing.widget - children = property(get_children) - - # Internally Boxes use a (length, breadth) coordinate system. length and - # breadth will be either x or y depending on which way the box is - # oriented. The subclasses must provide methods to translate between the - # 2 coordinate systems. - - def translate_size(self, size): - """Translate a (width, height) tulple to (length, breadth).""" - raise NotImplementedError() - - def untranslate_size(self, size): - """Reverse the work of translate_size.""" - raise NotImplementedError() - - def make_child_rect(self, position, length): - """Create a rect to position a child with.""" - raise NotImplementedError() - - def pack_start(self, child, expand=False, padding=0): - self.packing_start.append(BoxPacking(child, expand, padding)) - if expand: - self.expand_count += 1 - self.child_added(child) - - def pack_end(self, child, expand=False, padding=0): - self.packing_end.append(BoxPacking(child, expand, padding)) - if expand: - self.expand_count += 1 - self.child_added(child) - - def _remove_from_packing(self, child): - for i in xrange(len(self.packing_start)): - if self.packing_start[i].widget is child: - return self.packing_start.pop(i) - for i in xrange(len(self.packing_end)): - if self.packing_end[i].widget is child: - return self.packing_end.pop(i) - raise LookupError("%s not found" % child) - - def remove(self, child): - packing = self._remove_from_packing(child) - if packing.expand: - self.expand_count -= 1 - self.child_removed(child) - - def translate_widget_size(self, widget): - return self.translate_size(widget.get_size_request()) - - def calc_size_request(self): - length = breadth = 0 - for packing in self.packing_both(): - child_length, child_breadth = \ - self.translate_widget_size(packing.widget) - length += child_length - if packing.padding: - length += packing.padding * 2 # Need to pad on both sides - breadth = max(breadth, child_breadth) - spaces = max(0, len(self.packing_start) + len(self.packing_end) - 1) - length += spaces * self.spacing - return self.untranslate_size((length, breadth)) - - def place_children(self): - request_length, request_breadth = self.translate_widget_size(self) - ps = self.viewport.placement.size - total_length, dummy = self.translate_size((ps.width, ps.height)) - total_extra_space = total_length - request_length - extra_space_iter = _extra_space_iter(total_extra_space, - self.expand_count) - start_end = self._place_packing_list(self.packing_start, - extra_space_iter, 0) - if self.expand_count == 0 and total_extra_space > 0: - # account for empty space after the end of pack_start list and - # before the pack_end list. - self.draw_empty_space(start_end, total_extra_space) - start_end += total_extra_space - self._place_packing_list(reversed(self.packing_end), extra_space_iter, - start_end) - - def draw_empty_space(self, start, length): - empty_rect = self.make_child_rect(start, length) - my_view = self.viewport.view - opaque_view = my_view.opaqueAncestor() - if opaque_view is not None: - empty_rect2 = opaque_view.convertRect_fromView_(empty_rect, my_view) - opaque_view.setNeedsDisplayInRect_(empty_rect2) - - def _place_packing_list(self, packing_list, extra_space_iter, position): - for packing in packing_list: - child_length, child_breadth = \ - self.translate_widget_size(packing.widget) - if packing.expand: - child_length += extra_space_iter.next() - if packing.padding: # space before - self.draw_empty_space(position, packing.padding) - position += packing.padding - child_rect = self.make_child_rect(position, child_length) - if packing.padding: # space after - self.draw_empty_space(position, packing.padding) - position += packing.padding - packing.widget.place(child_rect, self.viewport.view) - position += child_length - if self.spacing > 0: - self.draw_empty_space(position, self.spacing) - position += self.spacing - return position - - def enable(self): - Container.enable(self) - for mem in self.children: - mem.enable() - - def disable(self): - Container.disable(self) - for mem in self.children: - mem.disable() - -class VBox(Box): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def translate_size(self, size): - return (size[1], size[0]) - - def untranslate_size(self, size): - return (size[1], size[0]) - - def make_child_rect(self, position, length): - placement = self.viewport.placement - return NSMakeRect(placement.origin.x, placement.origin.y + position, - placement.size.width, length) - -class HBox(Box): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def translate_size(self, size): - return (size[0], size[1]) - - def untranslate_size(self, size): - return (size[0], size[1]) - - def make_child_rect(self, position, length): - placement = self.viewport.placement - return NSMakeRect(placement.origin.x + position, placement.origin.y, - length, placement.size.height) - -class Alignment(Bin): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - CREATES_VIEW = False - - def __init__(self, xalign=0.0, yalign=0.0, xscale=0.0, yscale=0.0, - top_pad=0, bottom_pad=0, left_pad=0, right_pad=0): - Bin.__init__(self) - self.xalign = xalign - self.yalign = yalign - self.xscale = xscale - self.yscale = yscale - self.top_pad = top_pad - self.bottom_pad = bottom_pad - self.left_pad = left_pad - self.right_pad = right_pad - if self.child is not None: - self.place_children() - - def set(self, xalign=0.0, yalign=0.0, xscale=0.0, yscale=0.0): - self.xalign = xalign - self.yalign = yalign - self.xscale = xscale - self.yscale = yscale - if self.child is not None: - self.place_children() - - def set_padding(self, top_pad=0, bottom_pad=0, left_pad=0, right_pad=0): - self.top_pad = top_pad - self.bottom_pad = bottom_pad - self.left_pad = left_pad - self.right_pad = right_pad - if self.child is not None and self.viewport is not None: - self.place_children() - - def vertical_pad(self): - return self.top_pad + self.bottom_pad - - def horizontal_pad(self): - return self.left_pad + self.right_pad - - def calc_size_request(self): - if self.child: - child_width, child_height = self.child.get_size_request() - return (child_width + self.horizontal_pad(), - child_height + self.vertical_pad()) - else: - return (0, 0) - - def calc_size(self, requested, total, scale): - extra_width = max(0, total - requested) - return requested + int(round(extra_width * scale)) - - def calc_position(self, size, total, align): - return int(round((total - size) * align)) - - def place_children(self): - if self.child is None: - return - - total_width = self.viewport.placement.size.width - total_height = self.viewport.placement.size.height - total_width -= self.horizontal_pad() - total_height -= self.vertical_pad() - request_width, request_height = self.child.get_size_request() - - child_width = self.calc_size(request_width, total_width, self.xscale) - child_height = self.calc_size(request_height, total_height, self.yscale) - child_x = self.calc_position(child_width, total_width, self.xalign) - child_y = self.calc_position(child_height, total_height, self.yalign) - child_x += self.left_pad - child_y += self.top_pad - - my_origin = self.viewport.area().origin - child_rect = NSMakeRect(my_origin.x + child_x, my_origin.y + child_y, child_width, child_height) - self.child.place(child_rect, self.viewport.view) - # Make sure the space not taken up by our child is redrawn. - self.viewport.queue_redraw() - -class DetachedWindowHolder(Alignment): - def __init__(self): - Alignment.__init__(self, bottom_pad=16, xscale=1.0, yscale=1.0) - -class _TablePacking(object): - """Utility class to help with packing Table widgets.""" - def __init__(self, widget, column, row, column_span, row_span): - self.widget = widget - self.column = column - self.row = row - self.column_span = column_span - self.row_span = row_span - - def column_indexes(self): - return range(self.column, self.column + self.column_span) - - def row_indexes(self): - return range(self.row, self.row + self.row_span) - -class Table(Container): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - CREATES_VIEW = False - - def __init__(self, columns, rows): - Container.__init__(self) - self._cells = Matrix(columns, rows) - self._children = [] # List of _TablePacking objects - self._children_sorted = True - self.rows = rows - self.columns = columns - self.row_spacing = self.column_spacing = 0 - - def _ensure_children_sorted(self): - if not self._children_sorted: - def cell_area(table_packing): - return table_packing.column_span * table_packing.row_span - self._children.sort(key=cell_area) - self._children_sorted = True - - def get_children(self): - return [cell.widget for cell in self._children] - children = property(get_children) - - def calc_size_request(self): - self._ensure_children_sorted() - self._calc_dimensions() - return self.total_width, self.total_height - - def _calc_dimensions(self): - self.column_widths = [0] * self.columns - self.row_heights = [0] * self.rows - - for tp in self._children: - child_width, child_height = tp.widget.get_size_request() - # recalc the width of the child's columns - self._recalc_dimension(child_width, self.column_widths, - tp.column_indexes()) - # recalc the height of the child's rows - self._recalc_dimension(child_height, self.row_heights, - tp.row_indexes()) - - self.total_width = (self.column_spacing * (self.columns - 1) + - sum(self.column_widths)) - self.total_height = (self.row_spacing * (self.rows - 1) + - sum(self.row_heights)) - - def _recalc_dimension(self, child_size, size_array, positions): - current_size = sum(size_array[p] for p in positions) - child_size_needed = child_size - current_size - if child_size_needed > 0: - iter = _extra_space_iter(child_size_needed, len(positions)) - for p in positions: - size_array[p] += iter.next() - - def place_children(self): - # This method depepnds on us calling _calc_dimensions() in - # calc_size_request(). Ensure that this happens. - if self.cached_size_request is None: - self.get_size_request() - column_positions = [0] - for width in self.column_widths[:-1]: - column_positions.append(width + column_positions[-1] + self.column_spacing) - row_positions = [0] - for height in self.row_heights[:-1]: - row_positions.append(height + row_positions[-1] + self.row_spacing) - - my_x= self.viewport.placement.origin.x - my_y = self.viewport.placement.origin.y - for tp in self._children: - x = my_x + column_positions[tp.column] - y = my_y + row_positions[tp.row] - width = sum(self.column_widths[i] for i in tp.column_indexes()) - height = sum(self.row_heights[i] for i in tp.row_indexes()) - rect = NSMakeRect(x, y, width, height) - tp.widget.place(rect, self.viewport.view) - - def pack(self, widget, column, row, column_span=1, row_span=1): - tp = _TablePacking(widget, column, row, column_span, row_span) - for c in tp.column_indexes(): - for r in tp.row_indexes(): - if self._cells[c, r]: - raise ValueError("Cell %d x %d is already taken" % (c, r)) - self._cells[column, row] = widget - self._children.append(tp) - self._children_sorted = False - self.child_added(widget) - - def remove(self, child): - for i in xrange(len(self._children)): - if self._children[i].widget is child: - self._children.remove(i) - break - else: - raise ValueError("%s is not a child of this Table" % child) - self._cells.remove(child) - self.child_removed(widget) - - def set_column_spacing(self, spacing): - self.column_spacing = spacing - self.invalidate_size_request() - - def set_row_spacing(self, spacing): - self.row_spacing = spacing - self.invalidate_size_request() - - def enable(self, row=None, column=None): - Container.enable(self) - if row != None and column != None: - if self._cells[column, row]: - self._cells[column, row].enable() - elif row != None: - for mem in self._cells.row(row): - if mem: mem.enable() - elif column != None: - for mem in self._cells.column(column): - if mem: mem.enable() - else: - for mem in self._cells: - if mem: mem.enable() - - def disable(self, row=None, column=None): - Container.disable(self) - if row != None and column != None: - if self._cells[column, row]: - self._cells[column, row].disable() - elif row != None: - for mem in self._cells.row(row): - if mem: mem.disable() - elif column != None: - for mem in self._cells.column(column): - if mem: mem.disable() - else: - for mem in self._cells: - if mem: mem.disable() - -class MiroScrollView(NSScrollView): - def tile(self): - NSScrollView.tile(self) - # tile is called when we need to layout our child view and scrollers. - # This probably means that we've either hidden or shown a scrollbar so - # call invalidate_size_request to ensure that things get re-layed out - # correctly. (#see 13842) - wrapper = wrappermap.wrapper(self) - if wrapper is not None: - wrapper.invalidate_size_request() - -class Scroller(Bin): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, horizontal, vertical): - Bin.__init__(self) - self.view = MiroScrollView.alloc().init() - self.view.setAutohidesScrollers_(YES) - self.view.setHasHorizontalScroller_(horizontal) - self.view.setHasVerticalScroller_(vertical) - self.document_view = FlippedView.alloc().init() - self.view.setDocumentView_(self.document_view) - - def prepare_for_dark_content(self): - try: - self.view.setScrollerKnobStyle_(NSScrollerKnobStyleLight) - except AttributeError: - # This only works on 10.7 and abvoe - pass - - def set_has_borders(self, has_border): - self.view.setBorderType_(NSBezelBorder) - - def viewport_repositioned(self): - # If the window is resized, this translates to a - # viewport_repositioned() event. Instead of calling - # place_children() one, which is what our suporclass does, we need - # some extra logic here. place the chilren to work out if we need a - # scrollbar, then get the new size, then replace the children (which - # now takes into account of scrollbar size.) - super(Scroller, self).viewport_repositioned() - self.cached_size_request = self.calc_size_request() - self.place_children() - - def set_background_color(self, color): - self.view.setBackgroundColor_(self.make_color(color)) - - def add(self, child): - child.parent_is_scroller = True - Bin.add(self, child) - - def remove(self): - child.parent_is_scroller = False - Bin.remove(self) - - def children_changed(self): - # since our size isn't dependent on our children, don't call - # invalidate_size_request() here. Just call place_children() so that - # they get positioned correctly in the document view. - # - # XXX dodgy - why are we laying out the children twice? When the - # children change, the scroller could appear/disappear. But you have - # no idea if that's going to happen without knowing how big your - # children are. So we lay it out, get the size, then, place the - # children again. This makes sure that the right side of the children - # are redrawn. There's got to be a better way?? - self.place_children() - self.cached_size_request = self.calc_size_request() - self.place_children() - - def calc_size_request(self): - if self.child: - width = height = 0 - try: - legacy = self.view.scrollerStyle() == NSScrollerStyleLegacy - except AttributeError: - legacy = True - if not self.view.hasHorizontalScroller(): - width = self.child.get_size_request()[0] - if not self.view.hasVerticalScroller(): - height = self.child.get_size_request()[1] - # Add a little room for the scrollbars (if necessary) - if legacy and self.view.hasHorizontalScroller(): - height += NSScroller.scrollerWidth() - if legacy and self.view.hasVerticalScroller(): - width += NSScroller.scrollerWidth() - return width, height - else: - return 0, 0 - - def place_children(self): - if self.child is not None: - scroll_view_size = self.view.contentView().frame().size - child_width, child_height = self.child.get_size_request() - child_width = max(child_width, scroll_view_size.width) - child_height = max(child_height, scroll_view_size.height) - frame = NSRect(NSPoint(0,0), NSSize(child_width, child_height)) - if isinstance(self.child, tableview.TableView) and self.child.is_showing_headers(): - # Hack to allow the content of a table view to scroll, but not - # the headers - self.child.place(frame, self.document_view) - if self.view.documentView() is not self.child.tableview: - self.view.setDocumentView_(self.child.tableview) - else: - self.child.place(frame, self.document_view) - self.document_view.setFrame_(frame) - self.document_view.setNeedsDisplay_(YES) - self.view.setNeedsDisplay_(YES) - self.child.emit('place-in-scroller') - -class ExpanderView(FlippedView): - def init(self): - self = super(ExpanderView, self).init() - self.label_rect = None - self.content_view = None - self.button = NSButton.alloc().init() - self.button.setState_(NSOffState) - self.button.setTitle_("") - self.button.setBezelStyle_(NSDisclosureBezelStyle) - self.button.setButtonType_(NSPushOnPushOffButton) - self.button.sizeToFit() - self.addSubview_(self.button) - self.button.setTarget_(self) - self.button.setAction_('buttonChanged:') - self.content_view = FlippedView.alloc().init() - return self - - def buttonChanged_(self, button): - if button.state() == NSOnState: - self.addSubview_(self.content_view) - else: - self.content_view.removeFromSuperview() - if self.window(): - wrappermap.wrapper(self).invalidate_size_request() - - def mouseDown_(self, event): - pass # Just need to respond to the selector so we get mouseUp_ - - def mouseUp_(self, event): - position = event.locationInWindow() - window_label_rect = self.convertRect_toView_(self.label_rect, None) - if NSPointInRect(position, window_label_rect): - self.button.setNextState() - self.buttonChanged_(self.button) - -class Expander(Bin): - BUTTON_PAD_TOP = 2 - BUTTON_PAD_LEFT = 4 - LABEL_SPACING = 4 - - def __init__(self, child): - Bin.__init__(self) - if child: - self.add(child) - self.label = None - self.spacing = 0 - self.view = ExpanderView.alloc().init() - self.button = self.view.button - self.button.setFrameOrigin_(NSPoint(self.BUTTON_PAD_LEFT, - self.BUTTON_PAD_TOP)) - self.content_view = self.view.content_view - - def remove_viewport(self): - Bin.remove_viewport(self) - if self.label is not None: - self.label.remove_viewport() - - def set_spacing(self, spacing): - self.spacing = spacing - - def set_label(self, widget): - if self.label is not None: - self.label.remove_viewport() - self.label = widget - self.children_changed() - - def set_expanded(self, expanded): - if expanded: - self.button.setState_(NSOnState) - else: - self.button.setState_(NSOffState) - self.view.buttonChanged_(self.button) - - def calc_top_size(self): - width = self.button.bounds().size.width - height = self.button.bounds().size.height - if self.label is not None: - label_width, label_height = self.label.get_size_request() - width += self.LABEL_SPACING + label_width - height = max(height, label_height) - width += self.BUTTON_PAD_LEFT - height += self.BUTTON_PAD_TOP - return width, height - - def calc_size_request(self): - width, height = self.calc_top_size() - if self.child is not None and self.button.state() == NSOnState: - child_width, child_height = self.child.get_size_request() - width = max(width, child_width) - height += self.spacing + child_height - return width, height - - def place_children(self): - top_width, top_height = self.calc_top_size() - if self.label: - label_width, label_height = self.label.get_size_request() - button_width = self.button.bounds().size.width - label_x = self.BUTTON_PAD_LEFT + button_width + self.LABEL_SPACING - label_rect = NSMakeRect(label_x, self.BUTTON_PAD_TOP, - label_width, label_height) - self.label.place(label_rect, self.viewport.view) - self.view.label_rect = label_rect - if self.child: - size = self.viewport.area().size - child_rect = NSMakeRect(0, 0, size.width, size.height - - top_height) - self.content_view.setFrame_(NSMakeRect(0, top_height, size.width, - size.height - top_height)) - self.child.place(child_rect, self.content_view) - - -class TabViewDelegate(NSObject): - def tabView_willSelectTabViewItem_(self, tab_view, tab_view_item): - try: - wrapper = wrappermap.wrapper(tab_view) - except KeyError: - pass # The NSTabView hasn't been placed yet, don't worry about it. - else: - wrapper.place_child_with_item(tab_view_item) - -class TabContainer(Container): - def __init__(self): - Container.__init__(self) - self.children = [] - self.item_to_child = {} - self.view = NSTabView.alloc().init() - self.view.setAllowsTruncatedLabels_(NO) - self.delegate = TabViewDelegate.alloc().init() - self.view.setDelegate_(self.delegate) - - def append_tab(self, child_widget, label, image): - item = NSTabViewItem.alloc().init() - item.setLabel_(label) - item.setView_(FlippedView.alloc().init()) - self.view.addTabViewItem_(item) - self.children.append(child_widget) - self.child_added(child_widget) - self.item_to_child[item] = child_widget - - def select_tab(self, index): - self.view.selectTabViewItemAtIndex_(index) - - def place_children(self): - self.place_child_with_item(self.view.selectedTabViewItem()) - - def place_child_with_item(self, tab_view_item): - child = self.item_to_child[tab_view_item] - child_view = tab_view_item.view() - content_rect =self.view.contentRect() - child_view.setFrame_(content_rect) - child.place(child_view.bounds(), child_view) - - def calc_size_request(self): - tab_size = self.view.minimumSize() - # make sure there's enough room for the tabs, plus a little extra - # space to make things look good - max_width = tab_size.width + 60 - max_height = 0 - for child in self.children: - width, height = child.get_size_request() - max_width = max(width, max_width) - max_height = max(height, max_height) - max_height += tab_size.height - - return max_width, max_height diff --git a/mvc/widgets/osx/layoutmanager.py b/mvc/widgets/osx/layoutmanager.py deleted file mode 100644 index de4301b..0000000 --- a/mvc/widgets/osx/layoutmanager.py +++ /dev/null @@ -1,445 +0,0 @@ -# @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. - -"""textlayout.py -- Contains the LayoutManager class. It handles laying text, -buttons, getting font metrics and other tasks that are required to size -things. -""" -import logging -import math - -from AppKit import * -from Foundation import * -from objc import YES, NO, nil - -import drawing - -INFINITE = 1000000 # size of an "infinite" dimension - -class MiroLayoutManager(NSLayoutManager): - """Overide NSLayoutManager to draw better underlines.""" - - def drawUnderlineForGlyphRange_underlineType_baselineOffset_lineFragmentRect_lineFragmentGlyphRange_containerOrigin_(self, glyph_range, typ, offset, line_rect, line_glyph_range, container_origin): - container, _ = self.textContainerForGlyphAtIndex_effectiveRange_(glyph_range.location, None) - rect = self.boundingRectForGlyphRange_inTextContainer_(glyph_range, container) - x = container_origin.x + rect.origin.x - y = (container_origin.y + rect.origin.y + rect.size.height - offset) - underline_height, offset = self.calc_underline_extents(glyph_range) - y = math.ceil(y + offset) + underline_height / 2.0 - path = NSBezierPath.bezierPath() - path.setLineWidth_(underline_height) - path.moveToPoint_(NSPoint(x, y)) - path.relativeLineToPoint_(NSPoint(rect.size.width, 0)) - path.stroke() - - def calc_underline_extents(self, line_glyph_range): - index = self.characterIndexForGlyphAtIndex_(line_glyph_range.location) - font, _ = self.textStorage().attribute_atIndex_effectiveRange_(NSFontAttributeName, index, None) - # we use a couple of magic numbers that seems to work okay. I (BDK) - # got it from some old mozilla code. - height = font.ascender() - font.descender() - height = max(1.0, round(0.05 * height)) - offset = max(1.0, round(0.1 * height)) - return height, offset - -class TextBoxPool(object): - """Handles a pool of TextBox objects. We monitor the TextBox objects and - when those objects die, we reclaim them for the pool. - - Creating TextBoxes is fairly expensive and NSLayoutManager do a lot of - caching, so it's useful to keep them around rather than destroying them. - """ - - def __init__(self): - self.used_text_boxes = [] - self.available_text_boxes = [] - - def get(self): - """Get a NSLayoutManager, either from the pool or by creating a new - one. - """ - try: - rv = self.available_text_boxes.pop() - except IndexError: - rv = TextBox() - self.used_text_boxes.append(rv) - return rv - - def reclaim_textboxes(self): - """Move used TextBoxes back to the available pool. This should be - called after the code using text boxes is done using all of them. - """ - self.available_text_boxes.extend(self.used_text_boxes) - self.used_text_boxes[:] = [] - -text_box_pool = TextBoxPool() - -class Font(object): - line_height_sizer = NSLayoutManager.alloc().init() - - def __init__(self, nsfont): - self.nsfont = nsfont - - def ascent(self): - return self.nsfont.ascender() - - def descent(self): - return -self.nsfont.descender() - - def line_height(self): - return Font.line_height_sizer.defaultLineHeightForFont_(self.nsfont) - -class FontPool(object): - def __init__(self): - self._cached_fonts = {} - - def get(self, scale_factor, bold, italic, family): - cache_key = (scale_factor, bold, italic, family) - try: - return self._cached_fonts[cache_key] - except KeyError: - font = self._create(scale_factor, bold, italic, family) - self._cached_fonts[cache_key] = font - return font - - def _create(self, scale_factor, bold, italic, family): - size = round(scale_factor * NSFont.systemFontSize()) - nsfont = None - if family is not None: - if bold: - nsfont = NSFont.fontWithName_size_(family + " Bold", size) - else: - nsfont = NSFont.fontWithName_size_(family, size) - if nsfont is None: - logging.error('FontPool: family %s scale %s bold %s ' - 'italic %s not found', - family, scale_factor, bold, italic) - # Att his point either we have requested a custom font that failed - # to load or the system font was requested. - if nsfont is None: - if bold: - nsfont = NSFont.boldSystemFontOfSize_(size) - else: - nsfont = NSFont.systemFontOfSize_(size) - return Font(nsfont) - -class LayoutManager(object): - font_pool = FontPool() - default_font = font_pool.get(1.0, False, False, None) - - def __init__(self): - self.current_font = self.default_font - self.set_text_color((0, 0, 0)) - self.set_text_shadow(None) - - def font(self, scale_factor, bold=False, italic=False, family=None): - return self.font_pool.get(scale_factor, bold, italic, family) - - def set_font(self, scale_factor, bold=False, italic=False, family=None): - self.current_font = self.font(scale_factor, bold, italic, family) - - def set_text_color(self, color): - self.text_color = color - - def set_text_shadow(self, shadow): - self.shadow = shadow - - def textbox(self, text, underline=False): - text_box = text_box_pool.get() - color = NSColor.colorWithDeviceRed_green_blue_alpha_(self.text_color[0], self.text_color[1], self.text_color[2], 1.0) - text_box.reset(text, self.current_font, color, self.shadow, underline) - return text_box - - def button(self, text, pressed=False, disabled=False, style='normal'): - if style == 'webby': - return StyledButton(text, self.current_font, pressed, disabled) - else: - return NativeButton(text, self.current_font, pressed, disabled) - - def reset(self): - text_box_pool.reclaim_textboxes() - self.current_font = self.default_font - self.text_color = (0, 0, 0) - self.shadow = None - -class TextBox(object): - def __init__(self): - self.layout_manager = MiroLayoutManager.alloc().init() - container = NSTextContainer.alloc().init() - container.setLineFragmentPadding_(0) - self.layout_manager.addTextContainer_(container) - self.layout_manager.setUsesFontLeading_(NO) - self.text_storage = NSTextStorage.alloc().init() - self.text_storage.addLayoutManager_(self.layout_manager) - self.text_container = self.layout_manager.textContainers()[0] - - def reset(self, text, font, color, shadow, underline): - """Reset the text box so it's ready to be used by a new owner.""" - self.text_storage.deleteCharactersInRange_(NSRange(0, - self.text_storage.length())) - self.text_container.setContainerSize_(NSSize(INFINITE, INFINITE)) - self.paragraph_style = NSMutableParagraphStyle.alloc().init() - self.font = font - self.color = color - self.shadow = shadow - self.width = None - self.set_text(text, underline=underline) - - def make_attr_string(self, text, color, font, underline): - attributes = NSMutableDictionary.alloc().init() - if color is not None: - nscolor = NSColor.colorWithDeviceRed_green_blue_alpha_(color[0], color[1], color[2], 1.0) - attributes.setObject_forKey_(nscolor, NSForegroundColorAttributeName) - else: - attributes.setObject_forKey_(self.color, NSForegroundColorAttributeName) - if font is not None: - attributes.setObject_forKey_(font.nsfont, NSFontAttributeName) - else: - attributes.setObject_forKey_(self.font.nsfont, NSFontAttributeName) - if underline: - attributes.setObject_forKey_(NSUnderlineStyleSingle, NSUnderlineStyleAttributeName) - attributes.setObject_forKey_(self.paragraph_style.copy(), NSParagraphStyleAttributeName) - if text is None: - text = "" - return NSAttributedString.alloc().initWithString_attributes_(text, attributes) - - def set_text(self, text, color=None, font=None, underline=False): - string = self.make_attr_string(text, color, font, underline) - self.text_storage.setAttributedString_(string) - - def append_text(self, text, color=None, font=None, underline=False): - string = self.make_attr_string(text, color, font, underline) - self.text_storage.appendAttributedString_(string) - - def set_width(self, width): - if width is not None: - self.text_container.setContainerSize_(NSSize(width, INFINITE)) - else: - self.text_container.setContainerSize_(NSSize(INFINITE, INFINITE)) - self.width = width - - def update_paragraph_style(self): - attr = NSParagraphStyleAttributeName - value = self.paragraph_style.copy() - rnge = NSMakeRange(0, self.text_storage.length()) - self.text_storage.addAttribute_value_range_(attr, value, rnge) - - def set_wrap_style(self, wrap): - if wrap == 'word': - self.paragraph_style.setLineBreakMode_(NSLineBreakByWordWrapping) - elif wrap == 'char': - self.paragraph_style.setLineBreakMode_(NSLineBreakByCharWrapping) - elif wrap == 'truncated-char': - self.paragraph_style.setLineBreakMode_(NSLineBreakByTruncatingTail) - else: - raise ValueError("Unknown wrap value: %s" % wrap) - self.update_paragraph_style() - - def set_alignment(self, align): - if align == 'left': - self.paragraph_style.setAlignment_(NSLeftTextAlignment) - elif align == 'right': - self.paragraph_style.setAlignment_(NSRightTextAlignment) - elif align == 'center': - self.paragraph_style.setAlignment_(NSCenterTextAlignment) - else: - raise ValueError("Unknown align value: %s" % align) - self.update_paragraph_style() - - def get_size(self): - # The next line is there just to force cocoa to layout the text - self.layout_manager.glyphRangeForTextContainer_(self.text_container) - rect = self.layout_manager.usedRectForTextContainer_(self.text_container) - return rect.size.width, rect.size.height - - def char_at(self, x, y): - width, height = self.get_size() - if 0 <= x < width and 0 <= y < height: - index, _ = self.layout_manager.glyphIndexForPoint_inTextContainer_fractionOfDistanceThroughGlyph_(NSPoint(x, y), self.text_container, None) - return index - else: - return None - - def draw(self, context, x, y, width, height): - if self.shadow is not None: - context.save() - context.set_shadow(self.shadow.color, self.shadow.opacity, self.shadow.offset, self.shadow.blur_radius) - self.width = width - self.text_container.setContainerSize_(NSSize(width, height)) - glyph_range = self.layout_manager.glyphRangeForTextContainer_(self.text_container) - self.layout_manager.drawGlyphsForGlyphRange_atPoint_(glyph_range, NSPoint(x, y)) - if self.shadow is not None: - context.restore() - context.path.removeAllPoints() - -class NativeButton(object): - - def __init__(self, text, font, pressed, disabled=False): - self.min_width = 0 - self.cell = NSButtonCell.alloc().init() - self.cell.setBezelStyle_(NSRoundRectBezelStyle) - self.cell.setButtonType_(NSMomentaryPushInButton) - self.cell.setFont_(font.nsfont) - self.cell.setEnabled_(not disabled) - self.cell.setTitle_(text) - if pressed: - self.cell.setState_(NSOnState) - else: - self.cell.setState_(NSOffState) - self.cell.setImagePosition_(NSImageLeft) - - def set_icon(self, icon): - image = icon.image.copy() - image.setFlipped_(NO) - self.cell.setImage_(image) - - def get_size(self): - size = self.cell.cellSize() - return size.width, size.height - - def draw(self, context, x, y, width, height): - rect = NSMakeRect(x, y, width, height) - NSGraphicsContext.currentContext().saveGraphicsState() - self.cell.drawWithFrame_inView_(rect, context.view) - NSGraphicsContext.currentContext().restoreGraphicsState() - context.path.removeAllPoints() - -class StyledButton(object): - PAD_HORIZONTAL = 11 - BIG_PAD_VERTICAL = 4 - SMALL_PAD_VERTICAL = 2 - TOP_COLOR = (1, 1, 1) - BOTTOM_COLOR = (0.86, 0.86, 0.86) - LINE_COLOR_TOP = (0.71, 0.71, 0.71) - LINE_COLOR_BOTTOM = (0.45, 0.45, 0.45) - TEXT_COLOR = (0.19, 0.19, 0.19) - DISABLED_COLOR = (0.86, 0.86, 0.86) - DISABLED_TEXT_COLOR = (0.43, 0.43, 0.43) - ICON_PAD = 8 - - def __init__(self, text, font, pressed, disabled=False): - self.pressed = pressed - self.disabled = disabled - attributes = NSMutableDictionary.alloc().init() - attributes.setObject_forKey_(font.nsfont, NSFontAttributeName) - if self.disabled: - color = self.DISABLED_TEXT_COLOR - else: - color = self.TEXT_COLOR - nscolor = NSColor.colorWithDeviceRed_green_blue_alpha_(color[0], color[1], color[2], 1.0) - attributes.setObject_forKey_(nscolor, NSForegroundColorAttributeName) - self.title = NSAttributedString.alloc().initWithString_attributes_(text, attributes) - self.image = None - - def set_icon(self, icon): - self.image = icon.image.copy() - self.image.setFlipped_(YES) - - def get_size(self): - width, height = self.get_text_size() - if self.image is not None: - width += self.image.size().width + self.ICON_PAD - height = max(height, self.image.size().height) - height += self.BIG_PAD_VERTICAL * 2 - else: - height += self.SMALL_PAD_VERTICAL * 2 - if height % 2 == 1: - # make height even so that the radius of our circle is whole - height += 1 - width += self.PAD_HORIZONTAL * 2 - return width, height - - def get_text_size(self): - size = self.title.size() - return size.width, size.height - - def draw(self, context, x, y, width, height): - self._draw_button(context, x, y, width, height) - self._draw_title(context, x, y) - context.path.removeAllPoints() - - def _draw_button(self, context, x, y, width, height): - radius = height / 2 - self._draw_path(context, x, y, width, height, radius) - if self.disabled: - end_color = self.DISABLED_COLOR - start_color = self.DISABLED_COLOR - elif self.pressed: - end_color = self.TOP_COLOR - start_color = self.BOTTOM_COLOR - else: - context.set_line_width(1) - start_color = self.TOP_COLOR - end_color = self.BOTTOM_COLOR - gradient = drawing.Gradient(x, y, x, y+height) - gradient.set_start_color(start_color) - gradient.set_end_color(end_color) - context.gradient_fill(gradient) - self._draw_border(context, x, y, width, height, radius) - - def _draw_path(self, context, x, y, width, height, radius): - inner_width = width - radius * 2 - context.move_to(x + radius, y) - context.rel_line_to(inner_width, 0) - context.arc(x + width - radius, y+radius, radius, -math.pi/2, math.pi/2) - context.rel_line_to(-inner_width, 0) - context.arc(x + radius, y+radius, radius, math.pi/2, -math.pi/2) - - def _draw_path_reverse(self, context, x, y, width, height, radius): - inner_width = width - radius * 2 - context.move_to(x + radius, y) - context.arc_negative(x + radius, y+radius, radius, -math.pi/2, math.pi/2) - context.rel_line_to(inner_width, 0) - context.arc_negative(x + width - radius, y+radius, radius, math.pi/2, -math.pi/2) - context.rel_line_to(-inner_width, 0) - - def _draw_border(self, context, x, y, width, height, radius): - self._draw_path(context, x, y, width, height, radius) - self._draw_path_reverse(context, x+1, y+1, width-2, height-2, radius-1) - gradient = drawing.Gradient(x, y, x, y+height) - gradient.set_start_color(self.LINE_COLOR_TOP) - gradient.set_end_color(self.LINE_COLOR_BOTTOM) - context.save() - context.clip() - context.rectangle(x, y, width, height) - context.gradient_fill(gradient) - context.restore() - - def _draw_title(self, context, x, y): - c_width, c_height = self.get_size() - t_width, t_height = self.get_text_size() - x = x + self.PAD_HORIZONTAL - y = y + (c_height - t_height) / 2 - if self.image is not None: - self.image.drawAtPoint_fromRect_operation_fraction_( - NSPoint(x, y+3), NSZeroRect, NSCompositeSourceOver, 1.0) - x += self.image.size().width + self.ICON_PAD - else: - y += 0.5 - self.title.drawAtPoint_(NSPoint(x, y)) diff --git a/mvc/widgets/osx/osxmenus.py b/mvc/widgets/osx/osxmenus.py deleted file mode 100644 index 32ca469..0000000 --- a/mvc/widgets/osx/osxmenus.py +++ /dev/null @@ -1,571 +0,0 @@ -# @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. - -"""menus.py -- Menu handling code.""" - -import logging -import struct - -from objc import nil, NO, YES -import AppKit -from AppKit import * -from Foundation import * - -from mvc import signals -from mvc.widgets import keyboard -# import these names directly into our namespace for easy access -from mvc.widgets.keyboard import Shortcut, MOD - -# XXX hacks -def _(text, *params): - if params: - return text % params[0] - return text - -MODIFIERS_MAP = { - keyboard.MOD: NSCommandKeyMask, - keyboard.CMD: NSCommandKeyMask, - keyboard.SHIFT: NSShiftKeyMask, - keyboard.CTRL: NSControlKeyMask, - keyboard.ALT: NSAlternateKeyMask -} - -if isinstance(NSBackspaceCharacter, int): - backspace = NSBackspaceCharacter -else: - backspace = ord(NSBackspaceCharacter) - -KEYS_MAP = { - keyboard.SPACE: " ", - keyboard.ENTER: "\r", - keyboard.BKSPACE: struct.pack("H", backspace), - keyboard.DELETE: NSDeleteFunctionKey, - keyboard.RIGHT_ARROW: NSRightArrowFunctionKey, - keyboard.LEFT_ARROW: NSLeftArrowFunctionKey, - keyboard.UP_ARROW: NSUpArrowFunctionKey, - keyboard.DOWN_ARROW: NSDownArrowFunctionKey, - '.': '.', - ',': ',' -} -# add function keys -for i in range(1, 13): - portable_key = getattr(keyboard, "F%s" % i) - osx_key = getattr(AppKit, "NSF%sFunctionKey" % i) - KEYS_MAP[portable_key] = osx_key - -REVERSE_MODIFIERS_MAP = dict((i[1], i[0]) for i in MODIFIERS_MAP.items()) -REVERSE_KEYS_MAP = dict((i[1], i[0]) for i in KEYS_MAP.items() - if i[0] != keyboard.BKSPACE) -REVERSE_KEYS_MAP[u'\x7f'] = keyboard.BKSPACE -REVERSE_KEYS_MAP[u'\x1b'] = keyboard.ESCAPE - -def make_modifier_mask(shortcut): - mask = 0 - for modifier in shortcut.modifiers: - mask |= MODIFIERS_MAP[modifier] - return mask - -VIEW_ITEM_MAP = {} - -def _remove_mnemonic(label): - """Remove the underscore used by GTK for mnemonics. - - We totally ignore them on OSX, since they are now deprecated. - """ - return label.replace("_", "") - -def handle_menu_activate(ns_menu_item): - """Handle a menu item being activated. - - This gets called by our application delegate. - """ - - menu_item = ns_menu_item.representedObject() - menu_item.emit("activate") - menubar = menu_item._find_menubar() - if menubar is not None: - menubar.emit("activate", menu_item.name) - -class MenuItemBase(signals.SignalEmitter): - """Base class for MenuItem and Separator""" - def __init__(self): - signals.SignalEmitter.__init__(self) - self.name = None - self.parent = None - - def show(self): - self._menu_item.setHidden_(False) - - def hide(self): - self._menu_item.setHidden_(True) - - def enable(self): - self._menu_item.setEnabled_(True) - - def disable(self): - self._menu_item.setEnabled_(False) - - def remove_from_parent(self): - """Remove this menu item from it's parent Menu.""" - if self.parent is not None: - self.parent.remove(self) - -class MenuItem(MenuItemBase): - """See the GTK version of this method for the current docstring.""" - - # map Miro action names to standard OSX actions. - _STD_ACTION_MAP = { - "HideMiro": (NSApp(), 'hide:'), - "HideOthers": (NSApp(), 'hideOtherApplications:'), - "ShowAll": (NSApp(), 'unhideAllApplications:'), - "Cut": (nil, 'cut:'), - "Copy": (nil, 'copy:'), - "Paste": (nil, 'paste:'), - "Delete": (nil, 'delete:'), - "SelectAll": (nil, 'selectAll:'), - "Zoom": (nil, 'performZoom:'), - "Minimize": (nil, 'performMiniaturize:'), - "BringAllToFront": (nil, 'arrangeInFront:'), - "CloseWindow": (nil, 'performClose:'), - } - - def __init__(self, label, name, shortcut=None): - MenuItemBase.__init__(self) - self.name = name - self._menu_item = self._make_menu_item(label) - self.create_signal('activate') - self._setup_shortcut(shortcut) - - def _make_menu_item(self, label): - menu_item = NSMenuItem.alloc().init() - menu_item.setTitle_(_remove_mnemonic(label)) - # we set ourselves as the represented object for the menu item so we - # can easily translate one to the other - menu_item.setRepresentedObject_(self) - if self.name in self._STD_ACTION_MAP: - menu_item.setTarget_(self._STD_ACTION_MAP[self.name][0]) - menu_item.setAction_(self._STD_ACTION_MAP[self.name][1]) - else: - menu_item.setTarget_(NSApp().delegate()) - menu_item.setAction_('handleMenuActivate:') - return menu_item - - def _setup_shortcut(self, shortcut): - if shortcut is None: - key = '' - modifier_mask = 0 - elif isinstance(shortcut.shortcut, str): - key = shortcut.shortcut - modifier_mask = make_modifier_mask(shortcut) - elif shortcut.shortcut in KEYS_MAP: - key = KEYS_MAP[shortcut.shortcut] - modifier_mask = make_modifier_mask(shortcut) - else: - logging.warn("Don't know how to handle shortcut: %s", shortcut) - return - self._menu_item.setKeyEquivalent_(key) - self._menu_item.setKeyEquivalentModifierMask_(modifier_mask) - - def _change_shortcut(self, shortcut): - self._setup_shortcut(shortcut) - - def set_label(self, new_label): - self._menu_item.setTitle_(new_label) - - def get_label(self): - self._menu_item.title() - - def _find_menubar(self): - """Remove this menu item from it's parent Menu.""" - menu_item = self - while menu_item.parent is not None: - menu_item = menu_item.parent - if isinstance(menu_item, MenuBar): - return menu_item - else: - return None - -class CheckMenuItem(MenuItem): - """See the GTK version of this method for the current docstring.""" - def set_state(self, active): - if active is None: - state = NSMixedState - elif active: - state = NSOnState - else: - state = NSOffState - self._menu_item.setState_(state) - - def get_state(self): - return self._menu_item.state() == NSOnState - - def do_activate(self): - if self._menu_item.state() == NSOffState: - self._menu_item.setState_(NSOnState) - else: - self._menu_item.setState_(NSOffState) - -class RadioMenuItem(CheckMenuItem): - """See the GTK version of this method for the current docstring.""" - def __init__(self, label, name, shortcut=None): - CheckMenuItem.__init__(self, label, name, shortcut) - # The leader of a radio group stores the list of all items in the - # group - self.group_leader = None - self.others_in_group = set() - - def set_group(self, group_item): - if self.group_leader is not None: - raise ValueError("%s is already in a group" % self) - if group_item.group_leader is None: - group_leader = group_item - else: - group_leader = group_item.group_leader - if group_leader.group_leader is not None: - raise AssertionError("group_leader structure is wrong") - self.group_leader = group_leader - group_leader.others_in_group.add(self) - - def remove_from_group(self): - """Remove this RadioMenuItem from its current group.""" - if self.group_leader is not None: - # we have a group leader, remove ourself from their list. - # Note that this code will work even if we're the last item in - # others_in_group. - self.group_leader.others_in_group.remove(self) - self.group_leader = None - elif len(self.others_in_group) > 1: - # we're the group leader, hand off the leader to a different item - first_item = iter(self.others_in_group).next() - for other in self.others_in_group: - if other is first_item: - other.others_in_group = self.others_in_group - other.others_in_group.remove(first_item) - other.group_leader = None - else: - other.group_leader = first_item - self.others_in_group = set() - elif len(self.others_in_group) == 1: - # we're the group leader, but there's only 1 other item. unset - # everything. - for other in self.others_in_group: - other.group_leader = None - self.others_in_group = set() - - def _items_in_group(self): - if self.group_leader is not None: # we have a group leader - yield self.group_leader - for other in self.group_leader.others_in_group: - yield other - elif self.others_in_group: # we're the group leader - yield self - for other in self.others_in_group: - yield other - else: # we don't have a group set - yield self - - def do_activate(self): - for item in self._items_in_group(): - if item is not self: - item.set_state(False) - CheckMenuItem.do_activate(self) - -class Separator(MenuItemBase): - """See the GTK version of this method for the current docstring.""" - def __init__(self): - MenuItemBase.__init__(self) - self._menu_item = NSMenuItem.separatorItem() - -class MenuShell(signals.SignalEmitter): - def __init__(self, nsmenu): - signals.SignalEmitter.__init__(self) - self._menu = nsmenu - self.children = [] - self.parent = None - - def append(self, menu_item): - """Add a menu item to the end of this menu.""" - self.children.append(menu_item) - self._menu.addItem_(menu_item._menu_item) - menu_item.parent = self - - def insert(self, index, menu_item): - """Insert a menu item in the middle of this menu.""" - self.children.insert(index, menu_item) - self._menu.insertItem_atIndex_(menu_item._menu_item, index) - menu_item.parent = self - - def index(self, name): - """Find the position of a child menu item.""" - for i, menu_item in enumerate(self.children): - if menu_item.name == name: - return i - return -1 - - def remove(self, menu_item): - """Remove a child menu item. - - :raises ValueError: menu_item is not a child of this menu - """ - self.children.remove(menu_item) - self._menu.removeItem_(menu_item._menu_item) - menu_item.parent = None - - def get_children(self): - """Get the child menu items in order.""" - return list(self.children) - - def find(self, name): - """Search for a menu or menu item - - This method recursively searches the entire menu structure for a Menu - or MenuItem object with a given name. - - :raises KeyError: name not found - """ - found = self._find(name) - if found is None: - raise KeyError(name) - else: - return found - - def _find(self, name): - """Low-level helper-method for find(). - - :returns: found menu item or None. - """ - for menu_item in self.get_children(): - if menu_item.name == name: - return menu_item - if isinstance(menu_item, Menu): - submenu_find = menu_item._find(name) - if submenu_find is not None: - return submenu_find - return None - -class Menu(MenuShell): - """See the GTK version of this method for the current docstring.""" - def __init__(self, label, name, child_items=None): - MenuShell.__init__(self, NSMenu.alloc().init()) - self._menu.setTitle_(_remove_mnemonic(label)) - # we will enable/disable menu items manually - self._menu.setAutoenablesItems_(False) - self.name = name - if child_items is not None: - for item in child_items: - self.append(item) - self._menu_item = NSMenuItem.alloc().init() - self._menu_item.setTitle_(_remove_mnemonic(label)) - self._menu_item.setSubmenu_(self._menu) - # Hack to set the services menu - if name == "ServicesMenu": - NSApp().setServicesMenu_(self._menu_item) - - def show(self): - self._menu_item.setHidden_(False) - - def hide(self): - self._menu_item.setHidden_(True) - - def enable(self): - self._menu_item.setEnabled_(True) - - def disable(self): - self._menu_item.setEnabled_(False) - -class AppMenu(MenuShell): - """Wrapper for the application menu (AKA the Miro menu) - - We need to special case this because OSX automatically creates the menu - item. - """ - def __init__(self): - MenuShell.__init__(self, NSApp().mainMenu().itemAtIndex_(0).submenu()) - self.name = "Libre Video Converter" - -class MenuBar(MenuShell): - """See the GTK version of this method for the current docstring.""" - def __init__(self): - MenuShell.__init__(self, NSApp().mainMenu()) - self.create_signal('activate') - self._add_app_menu() - - def _add_app_menu(self): - """Add the app menu to this menu bar. - - We need to special case this because OSX automatically adds the - NSMenuItem for the app menu, we just need to set up our wrappers. - """ - self._app_menu = AppMenu() - self.children.append(self._app_menu) - self._app_menu.parent = self - - def add_initial_menus(self, menus): - for menu in menus: - self.append(menu) - self._modify_initial_menus() - - def _extract_menu_item(self, name): - """Helper method for changing the portable menu structure.""" - menu_item = self.find(name) - menu_item.remove_from_parent() - return menu_item - - def _modify_initial_menus(self): - short_appname = "Libre Video Converter" # XXX - - # Application menu - miroMenuItems = [ - self._extract_menu_item("About"), - Separator(), - self._extract_menu_item("Quit") - ] - - for item in miroMenuItems: - self._app_menu.append(item) - - self._app_menu.find("Quit").set_label(_("Quit %(appname)s", - {"appname": short_appname})) - - # Help Menu - #helpItem = self.find("Help") - #helpItem.set_label(_("%(appname)s Help", {"appname": short_appname})) - #helpItem._change_shortcut(Shortcut("?", MOD)) - - self._update_present_menu() - self._connect_to_signals() - - def do_activate(self, name): - # We handle a couple OSX-specific actions here - if name == "PresentActualSize": - NSApp().delegate().present_movie('natural-size') - elif name == "PresentDoubleSize": - NSApp().delegate().present_movie('double-size') - elif name == "PresentHalfSize": - NSApp().delegate().present_movie('half-size') - elif name == "ShowMain": - app.widgetapp.window.nswindow.makeKeyAndOrderFront_(self) - - def _connect_to_signals(self): - return - app.playback_manager.connect("will-play", self._on_playback_change) - app.playback_manager.connect("will-stop", self._on_playback_change) - - def _on_playback_change(self, playback_manager, *args): - self._update_present_menu() - - def _update_present_menu(self): - return - if self._should_enable_present_menu(): - for menu_item in self.present_menu.get_children(): - menu_item.enable() - else: - for menu_item in self.present_menu.get_children(): - menu_item.disable() - - def _should_enable_present_menu(self): - return False - if (app.playback_manager.is_playing and - not app.playback_manager.is_playing_audio): - # we're currently playing video, allow the user to fullscreen - return True - selection_info = app.item_list_controller_manager.get_selection_info() - if (selection_info.has_download and - selection_info.has_file_type('video')): - # A downloaded video is selected, allow the user to start playback - # in fullscreen - return True - return False - -#class ContextMenuHandler(NSObject): -# def initWithCallback_(self, callback): -# self = super(ContextMenuHandler, self).init() -# self.callback = callback -# return self -# -# def handleMenuItem_(self, sender): -# self.callback() -# -#class MiroContextMenu(NSMenu): -# # Works exactly like NSMenu, except it keeps a reference to the menu -# # handler objects. -# def init(self): -# self = super(MiroContextMenu, self).init() -# self.handlers = set() -# return self -# -# def addItem_(self, item): -# if isinstance(item.target(), ContextMenuHandler): -# self.handlers.add(item.target()) -# return NSMenu.addItem_(self, item) -# -def make_context_menu(menu_items): - nsmenu = MiroContextMenu.alloc().init() - for item in menu_items: - if item is None: - nsitem = NSMenuItem.separatorItem() - else: - label, callback = item - nsitem = NSMenuItem.alloc().init() - if isinstance(label, tuple) and len(label) == 2: - label, icon_path = label - image = NSImage.alloc().initWithContentsOfFile_(icon_path) - nsitem.setImage_(image) - if callback is None: - font_size = NSFont.systemFontSize() - font = NSFont.fontWithName_size_("Lucida Sans Italic", font_size) - if font is None: - font = NSFont.systemFontOfSize_(font_size) - attributes = {NSFontAttributeName: font} - attributed_label = NSAttributedString.alloc().initWithString_attributes_(label, attributes) - nsitem.setAttributedTitle_(attributed_label) - else: - nsitem.setTitle_(label) - if isinstance(callback, list): - submenu = make_context_menu(callback) - nsmenu.setSubmenu_forItem_(submenu, nsitem) - else: - handler = ContextMenuHandler.alloc().initWithCallback_(callback) - nsitem.setTarget_(handler) - nsitem.setAction_('handleMenuItem:') - nsmenu.addItem_(nsitem) - return nsmenu - -def translate_event_modifiers(event): - mods = set() - flags = event.modifierFlags() - if flags & NSCommandKeyMask: - mods.add(keyboard.CMD) - if flags & NSControlKeyMask: - mods.add(keyboard.CTRL) - if flags & NSAlternateKeyMask: - mods.add(keyboard.ALT) - if flags & NSShiftKeyMask: - mods.add(keyboard.SHIFT) - return mods diff --git a/mvc/widgets/osx/rect.py b/mvc/widgets/osx/rect.py deleted file mode 100644 index 3c8d448..0000000 --- a/mvc/widgets/osx/rect.py +++ /dev/null @@ -1,78 +0,0 @@ -# @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. - -""".rect -- Simple Rectangle class.""" - -from Foundation import NSMakeRect, NSRectFromString - -class Rect(object): - @classmethod - def from_string(cls, rect_string): - if rect_string.startswith('{{'): - return NSRectWrapper(NSRectFromString(rect_string)) - else: - try: - items = [int(i) for i in rect_string.split(',')] - return Rect(*items) - except: - return None - - def __init__(self, x, y, width, height): - self.nsrect = NSMakeRect(x, y, width, height) - - def get_x(self): - return self.nsrect.origin.x - def set_x(self, x): - self.nsrect.origin.x = x - x = property(get_x, set_x) - - def get_y(self): - return self.nsrect.origin.y - def set_y(self, y): - self.nsrect.origin.x = y - y = property(get_y, set_y) - - def get_width(self): - return self.nsrect.size.width - def set_width(self, width): - self.nsrect.size.width = width - width = property(get_width, set_width) - - def get_height(self): - return self.nsrect.size.height - def set_height(self, height): - self.nsrect.size.height = height - height = property(get_height, set_height) - - def __str__(self): - return "%d,%d,%d,%d" % (self.nsrect.origin.x, self.nsrect.origin.y, self.nsrect.size.width, self.nsrect.size.height) - -class NSRectWrapper(Rect): - def __init__(self, nsrect): - self.nsrect = nsrect diff --git a/mvc/widgets/osx/simple.py b/mvc/widgets/osx/simple.py deleted file mode 100644 index 1c12b06..0000000 --- a/mvc/widgets/osx/simple.py +++ /dev/null @@ -1,376 +0,0 @@ -# @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. - -from __future__ import division -import logging -import math - -from AppKit import * -from Foundation import * -from objc import YES, NO, nil - -from mvc.widgets import widgetconst -from .base import Widget, SimpleBin, FlippedView -from .utils import filename_to_unicode -import drawing -import wrappermap - -"""A collection of various simple widgets.""" - -class Image(object): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, path): - self._set_image(NSImage.alloc().initByReferencingFile_( - filename_to_unicode(path))) - - def _set_image(self, nsimage): - self.nsimage = nsimage - self.width = self.nsimage.size().width - self.height = self.nsimage.size().height - if self.width * self.height == 0: - raise ValueError('Image has invalid size: (%d, %d)' % ( - self.width, self.height)) - - def resize(self, width, height): - return ResizedImage(self, width, height) - - def crop_and_scale(self, src_x, src_y, src_width, src_height, dest_width, - dest_height): - if dest_width <= 0 or dest_height <= 0: - logging.stacktrace("invalid dest sizes: %s %s" % (dest_width, - dest_height)) - return TransformedImage(self.nsimage) - - source_rect = NSMakeRect(src_x, src_y, src_width, src_height) - dest_rect = NSMakeRect(0, 0, dest_width, dest_height) - - dest = NSImage.alloc().initWithSize_(NSSize(dest_width, dest_height)) - dest.lockFocus() - try: - NSGraphicsContext.currentContext().setImageInterpolation_( - NSImageInterpolationHigh) - self.nsimage.drawInRect_fromRect_operation_fraction_(dest_rect, - source_rect, NSCompositeCopy, 1.0) - finally: - dest.unlockFocus() - return TransformedImage(dest) - - def resize_for_space(self, width, height): - """Returns an image scaled to fit into the specified space at the - correct height/width ratio. - """ - # this prevents division by 0. - if self.width == 0 and self.height == 0: - return self - elif self.width == 0: - ratio = height / self.height - return self.resize(self.width, ratio * self.height) - elif self.height == 0: - ratio = width / self.width - return self.resize(ratio * self.width, self.height) - - ratio = min(width / self.width, height / self.height) - return self.resize(ratio * self.width, ratio * self.height) - -class ResizedImage(Image): - def __init__(self, image, width, height): - nsimage = image.nsimage.copy() - nsimage.setCacheMode_(NSImageCacheNever) - nsimage.setScalesWhenResized_(YES) - nsimage.setSize_(NSSize(width, height)) - self._set_image(nsimage) - -class TransformedImage(Image): - def __init__(self, nsimage): - self._set_image(nsimage) - -class NSImageDisplay(NSView): - def init(self): - self = super(NSImageDisplay, self).init() - self.border = False - self.image = None - return self - - def isFlipped(self): - return YES - - def set_border(self, border): - self.border = border - - def set_image(self, image): - self.image = image - - def drawRect_(self, dest_rect): - if self.image is not None: - source_rect = self.calculateSourceRectFromDestRect_(dest_rect) - context = NSGraphicsContext.currentContext() - context.setShouldAntialias_(YES) - context.setImageInterpolation_(NSImageInterpolationHigh) - context.saveGraphicsState() - drawing.flip_context(self.bounds().size.height) - self.image.nsimage.drawInRect_fromRect_operation_fraction_( - dest_rect, source_rect, NSCompositeSourceOver, 1.0) - context.restoreGraphicsState() - if self.border: - context = drawing.DrawingContext(self, self.bounds(), dest_rect) - context.style = drawing.DrawingStyle() - context.set_line_width(1) - context.set_color((0, 0, 0)) # black - context.rectangle(0, 0, context.width, context.height) - context.stroke() - - def calculateSourceRectFromDestRect_(self, dest_rect): - """Calulate where dest_rect maps to on our image. - - This is tricky because our image might be scaled up, in which case - the rect from our image will be smaller than dest_rect. - """ - view_size = self.frame().size - x_scale = float(self.image.width) / view_size.width - y_scale = float(self.image.height) / view_size.height - - return NSMakeRect(dest_rect.origin.x * x_scale, - dest_rect.origin.y * y_scale, - dest_rect.size.width * x_scale, - dest_rect.size.height * y_scale) - - # XXX FIXME: should track mouse movement - mouseDown is not the correct - # event. - def mouseDown_(self, event): - wrappermap.wrapper(self).emit('clicked') - -class ImageDisplay(Widget): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, image=None): - Widget.__init__(self) - self.create_signal('clicked') - self.view = NSImageDisplay.alloc().init() - self.set_image(image) - - def set_image(self, image): - self.image = image - if image: - image.nsimage.setCacheMode_(NSImageCacheNever) - self.view.set_image(image) - self.invalidate_size_request() - - def set_border(self, border): - self.view.set_border(border) - - def calc_size_request(self): - if self.image is not None: - return self.image.width, self.image.height - else: - return 0, 0 - -class ClickableImageButton(ImageDisplay): - def __init__(self, image_path, max_width=None, max_height=None): - ImageDisplay.__init__(self) - self.set_border(True) - self.max_width = max_width - self.max_height = max_height - self.image = None - self._width, self._height = None, None - if image_path: - self.set_path(image_path) - - def set_path(self, path): - image = Image(path) - if self.max_width: - image = image.resize_for_space(self.max_width, self.max_height) - super(ClickableImageButton, self).set_image(image) - - def calc_size_request(self): - if self.max_width: - return self.max_width, self.max_height - else: - return ImageDisplay.calc_size_request(self) - -class MiroImageView(NSImageView): - def viewWillMoveToWindow_(self, aWindow): - self.setAnimates_(not aWindow == nil) - -class AnimatedImageDisplay(Widget): - def __init__(self, path): - Widget.__init__(self) - self.nsimage = NSImage.alloc().initByReferencingFile_( - filename_to_unicode(path)) - self.view = MiroImageView.alloc().init() - self.view.setImage_(self.nsimage) - # enabled when viewWillMoveToWindow:aWindow invoked - self.view.setAnimates_(NO) - - def calc_size_request(self): - return self.nsimage.size().width, self.nsimage.size().height - -class Label(Widget): - """See https://develop.participatoryculture.org/index.php/WidgetAPI for a description of the API for this class.""" - def __init__(self, text="", wrap=False, color=None): - Widget.__init__(self) - self.view = NSTextField.alloc().init() - self.view.setEditable_(NO) - self.view.setBezeled_(NO) - self.view.setBordered_(NO) - self.view.setDrawsBackground_(NO) - self.wrap = wrap - self.bold = False - self.size = NSFont.systemFontSize() - self.sizer_cell = self.view.cell().copy() - self.set_font() - self.set_text(text) - self.__color = self.view.textColor() - if color is not None: - self.set_color(color) - - def get_width(self): - return self.calc_size_request()[0] - - def set_bold(self, bold): - self.bold = bold - self.set_font() - - def set_size(self, size): - if size > 0: - self.size = NSFont.systemFontSize() * size - elif size == widgetconst.SIZE_SMALL: - self.size = NSFont.smallSystemFontSize() - elif size == widgetconst.SIZE_NORMAL: - self.size = NSFont.systemFontSize() - else: - raise ValueError("Unknown size constant: %s" % size) - - self.set_font() - - def set_color(self, color): - self.__color = self.make_color(color) - - if self.view.isEnabled(): - self.view.setTextColor_(self.__color) - else: - self.view.setTextColor_(self.__color.colorWithAlphaComponent_(0.5)) - - def set_background_color(self, color): - self.view.setBackgroundColor_(self.make_color(color)) - self.view.setDrawsBackground_(YES) - - def set_font(self): - if self.bold: - font = NSFont.boldSystemFontOfSize_(self.size) - else: - font= NSFont.systemFontOfSize_(self.size) - self.view.setFont_(font) - self.sizer_cell.setFont_(font) - self.invalidate_size_request() - - def calc_size_request(self): - if (self.wrap and self.manual_size_request is not None and - self.manual_size_request[0] > 0): - wrap_width = self.manual_size_request[0] - size = self.sizer_cell.cellSizeForBounds_(NSMakeRect(0, 0, - wrap_width, 10000)) - else: - size = self.sizer_cell.cellSize() - return math.ceil(size.width), math.ceil(size.height) - - def baseline(self): - return -self.view.font().descender() - - def set_text(self, text): - self.view.setStringValue_(text) - self.sizer_cell.setStringValue_(text) - self.invalidate_size_request() - - def get_text(self): - val = self.view.stringValue() - if not val: - val = u'' - return val - - def set_selectable(self, val): - self.view.setSelectable_(val) - - def set_alignment(self, alignment): - self.view.setAlignment_(alignment) - - def get_alignment(self, alignment): - return self.view.alignment() - - def set_wrap(self, wrap): - self.wrap = True - self.invalidate_size_request() - - def enable(self): - Widget.enable(self) - self.view.setTextColor_(self.__color) - self.view.setEnabled_(True) - - def disable(self): - Widget.disable(self) - self.view.setTextColor_(self.__color.colorWithAlphaComponent_(0.5)) - self.view.setEnabled_(False) - -class SolidBackground(SimpleBin): - def __init__(self, color=None): - SimpleBin.__init__(self) - self.view = FlippedView.alloc().init() - if color is not None: - self.set_background_color(color) - - def set_background_color(self, color): - self.view.setBackgroundColor_(self.make_color(color)) - -class ProgressBar(Widget): - def __init__(self): - Widget.__init__(self) - self.view = NSProgressIndicator.alloc().init() - self.view.setMaxValue_(1.0) - self.view.setIndeterminate_(False) - - def calc_size_request(self): - return 20, 20 - - def set_progress(self, fraction): - self.view.setIndeterminate_(False) - self.view.setDoubleValue_(fraction) - - def start_pulsing(self): - self.view.setIndeterminate_(True) - self.view.startAnimation_(nil) - - def stop_pulsing(self): - self.view.stopAnimation_(nil) - -class HLine(Widget): - def __init__(self): - Widget.__init__(self) - self.view = NSBox.alloc().init() - self.view.setBoxType_(NSBoxSeparator) - - def calc_size_request(self): - return self.view.frame().size.width, self.view.frame().size.height diff --git a/mvc/widgets/osx/tablemodel.py b/mvc/widgets/osx/tablemodel.py deleted file mode 100644 index 980b60b..0000000 --- a/mvc/widgets/osx/tablemodel.py +++ /dev/null @@ -1,532 +0,0 @@ -# @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. - -"""tablemodel.py -- Model classes for TableView. """ - -import logging - -from AppKit import (NSDragOperationNone, NSDragOperationAll, NSTableViewDropOn, - NSOutlineViewDropOnItemIndex, protocols) -from Foundation import NSObject, NSNotFound, NSMutableIndexSet -from objc import YES, NO, nil - -from mvc import signals -from mvc.errors import WidgetActionError -import fasttypes -import wrappermap - -MIRO_DND_ITEM_LOCAL = 'miro-local-item' - -# XXX need unsigned but value comes out as signed. -NSDragOperationEvery = NSDragOperationAll - -def list_from_nsindexset(index_set): - rows = list() - index = index_set.firstIndex() - while (index != NSNotFound): - rows.append(index) - index = index_set.indexGreaterThanIndex_(index) - return rows - -class RowList(object): - """RowList is a Linked list that has some optimizations for looking up - rows by index number. - """ - def __init__(self): - self.list = fasttypes.LinkedList() - self.iter_cache = [] - - def firstIter(self): - return self.list.firstIter() - - def lastIter(self): - return self.list.lastIter() - - def insertBefore(self, iter, value): - self.iter_cache = [] - if iter is None: - return self.list.append(value) - else: - return self.list.insertBefore(iter, value) - - def append(self, value): - return self.list.append(value) - - def __len__(self): - return len(self.list) - - def __getitem__(self, iter): - return self.list[iter] - - def __iter__(self): - iter = self.firstIter() - while iter != self.lastIter(): - yield iter.value() - iter.forward() - - def remove(self, iter): - self.iter_cache = [] - return self.list.remove(iter) - - def nth_iter(self, index): - if index < 0: - raise IndexError(index) - elif index >= len(self): - raise LookupError() - if len(self.iter_cache) == 0: - self.iter_cache.append(self.firstIter()) - try: - return self.iter_cache[index].copy() - except IndexError: - pass - iter = self.iter_cache[-1].copy() - index -= len(self.iter_cache) - 1 - for x in xrange(index): - iter.forward() - self.iter_cache.append(iter.copy()) - return iter - -class TableModelBase(signals.SignalEmitter): - """Base class for TableModel and TreeTableModel.""" - def __init__(self, *column_types): - signals.SignalEmitter.__init__(self) - self.row_list = RowList() - self.column_types = column_types - self.create_signal('row-changed') - self.create_signal('structure-will-change') - - def check_column_values(self, column_values): - if len(self.column_types) != len(column_values): - raise ValueError("Wrong number of columns") - # We might want to do more typechecking here - - def get_column_data(self, row, column): - attr_map = column.attrs() - return dict((name, row[index]) for name, index in attr_map.items()) - - def update_value(self, iter, index, value): - iter.value().values[index] = value - self.emit('row-changed', iter) - - def update(self, iter, *column_values): - iter.value().update_values(column_values) - self.emit('row-changed', iter) - - def remove(self, iter): - self.emit('structure-will-change') - row_list = self.containing_list(iter) - rv = row_list.remove(iter) - if rv == row_list.lastIter(): - rv = None - return rv - - def nth_iter(self, index): - return self.row_list.nth_iter(index) - - def next_iter(self, iter): - row_list = self.containing_list(iter) - retval = iter.copy() - retval.forward() - if retval == row_list.lastIter(): - return None - else: - return retval - - def first_iter(self): - if len(self.row_list) > 0: - return self.row_list.firstIter() - else: - return None - - def __len__(self): - return len(self.row_list) - - def __getitem__(self, iter): - return iter.value() - - def __iter__(self): - return iter(self.row_list) - -class TableRow(object): - """See https://develop.participatoryculture.org/index.php/WidgetAPITableView for a description of the API for this class.""" - def __init__(self, column_values): - self.update_values(column_values) - - def update_values(self, column_values): - self.values = list(column_values) - - def __getitem__(self, index): - return self.values[index] - - def __len__(self): - return len(self.values) - - def __iter__(self): - return iter(self.values) - -class TableModel(TableModelBase): - """See https://develop.participatoryculture.org/index.php/WidgetAPITableView for a description of the API for this class.""" - def __init__(self, *column_types): - TableModelBase.__init__(self, column_types) - self.row_indexes = {} - - def remember_row_at_index(self, row, index): - if row not in self.row_indexes: - self.row_indexes[row] = index - - def row_of_iter(self, tableview, iter): - row = iter.value() - try: - return self.row_indexes[row] - except KeyError: - iter = self.row_list.firstIter() - index = 0 - while iter != self.row_list.lastIter(): - current_row = iter.value() - self.row_indexes[current_row] = index - if current_row is row: - return index - index += 1 - iter.forward() - raise LookupError("%s is not in this table" % row) - - def containing_list(self, iter): - return self.row_list - - def append(self, *column_values): - self.emit('structure-will-change') - self.row_indexes = {} - retval = self.row_list.append(TableRow(column_values)) - return retval - - def remove(self, iter): - self.row_indexes = {} - return TableModelBase.remove(self, iter) - - def insert_before(self, iter, *column_values): - self.emit('structure-will-change') - self.row_indexes = {} - row = TableRow(column_values) - retval = self.row_list.insertBefore(iter, row) - return retval - - def iter_for_row(self, tableview, row): - return self.row_list.nth_iter(row) - - -class TreeNode(NSObject, TableRow): - """A row in a TreeTableModel""" - - # Implementation note: these need to be NSObjects because we return them - # to the NSOutlineView. - - def initWithValues_parent_(self, column_values, parent): - self.children = RowList() - self.update_values(column_values) - self.parent = parent - return self - - @staticmethod - def create_(values, parent): - return TreeNode.alloc().initWithValues_parent_(values, parent) - - def iterchildren(self): - return iter(self.children) - -class TreeTableModel(TableModelBase): - """https://develop.participatoryculture.org/index.php/WidgetAPITableView""" - def __init__(self, *column_values): - TableModelBase.__init__(self, *column_values) - self.iter_for_item = {} - - def containing_list(self, iter): - return self.row_list_for_iter(iter.value().parent) - - def row_list_for_iter(self, iter): - """Return the rows of all direct children of iter.""" - if iter is None: - return self.row_list - else: - return iter.value().children - - def remember_iter(self, iter): - self.iter_for_item[iter.value()] = iter - return iter - - def append(self, *column_values): - self.emit('structure-will-change') - retval = self.row_list.append(TreeNode.create_(column_values, None)) - return self.remember_iter(retval) - - def forget_iter_for_item(self, item): - del self.iter_for_item[item] - for child in item.children: - self.forget_iter_for_item(child) - - def remove(self, iter): - item = iter.value() - rv = TableModelBase.remove(self, iter) - self.forget_iter_for_item(item) - return rv - - def insert_before(self, iter, *column_values): - self.emit('structure-will-change') - row = TreeNode.create_(column_values, self.parent_iter(iter)) - retval = self.containing_list(iter).insertBefore(iter, row) - return self.remember_iter(retval) - - def append_child(self, iter, *column_values): - self.emit('structure-will-change') - row_list = self.row_list_for_iter(iter) - retval = row_list.append(TreeNode.create_(column_values, iter)) - return self.remember_iter(retval) - - def child_iter(self, iter): - row_list = iter.value().children - if len(row_list) == 0: - return None - else: - return row_list.firstIter() - - def nth_child_iter(self, iter, index): - row_list = self.row_list_for_iter(iter) - return row_list.nth_iter(index) - - def has_child(self, iter): - return len(iter.value().children) > 0 - - def children_count(self, iter): - if iter is not None: - return len(iter.value().children) - else: - return len(self.row_list) - - def children_iters(self, iter): - return self.iters_in_rowlist(self.row_list_for_iter(iter)) - - def parent_iter(self, iter): - return iter.value().parent - - def iter_for_row(self, tableview, row): - item = tableview.itemAtRow_(row) - if item in self.iter_for_item: - return self.iter_for_item[item] - elif item == -1: - raise WidgetActionError("no item at row %s" % row) - else: - raise WidgetActionError("no iter for item %s at row %s" % - (repr(item), row)) - - def row_of_iter(self, tableview, iter): - item = iter.value() - row = tableview.rowForItem_(item) - if row == -1: - raise LookupError("%s is not in this table" % repr(item)) - return row - - def get_path(self, iter_): - """Not implemented (yet?) for Cocoa. Currently the only place this is - needed is tablistmanager, where the situation that uses paths results - from GTK peculiarities. - """ - return NotImplemented - -class DataSourceBase(NSObject): - def initWithModel_(self, model): - self.model = model - self.drag_source = None - self.drag_dest = None - return self - - def setDragSource_(self, drag_source): - self.drag_source = drag_source - - def setDragDest_(self, drag_dest): - self.drag_dest = drag_dest - - def view_writeColumnData_ToPasteboard_(self, view, data, pasteboard): - if not self.drag_source: - return NO - wrapper = wrappermap.wrapper(view) - drag_data = self.drag_source.begin_drag(wrapper, data) - if not drag_data: - return NO - pasteboard.declareTypes_owner_((MIRO_DND_ITEM_LOCAL,), self) - for typ, value in drag_data.items(): - stringval = repr((repr(value), typ)) - pasteboard.setString_forType_(stringval, MIRO_DND_ITEM_LOCAL) - return YES - - def calcType_(self, drag_info): - source_actions = drag_info.draggingSourceOperationMask() - if not (self.drag_dest and - (self.drag_dest.allowed_actions() | source_actions)): - return None - types = self.drag_dest.allowed_types() - available = drag_info.draggingPasteboard().availableTypeFromArray_( - (MIRO_DND_ITEM_LOCAL,)) - if available: - # XXX using eval() sucks. - data = eval(drag_info.draggingPasteboard().stringForType_( - MIRO_DND_ITEM_LOCAL)) - if data: - _, typ = data - return typ - return None - - def validateDrop_dragInfo_parentIter_position_(self, view, drag_info, - parent, position): - typ = self.calcType_(drag_info) - if typ: - wrapper = wrappermap.wrapper(view) - drop_action = self.drag_dest.validate_drop( - wrapper, self.model, typ, - drag_info.draggingSourceOperationMask(), parent, - position) - if not drop_action: - return NSDragOperationNone - if isinstance(drop_action, (tuple, list)): - drop_action, iter = drop_action - view.setDropRow_dropOperation_( - self.model.row_of_iter(view, iter), - NSTableViewDropOn) - return drop_action - else: - return NSDragOperationNone - - def acceptDrop_dragInfo_parentIter_position_(self, view, drag_info, - parent, position): - typ = self.calcType_(drag_info) - if typ: - # XXX using eval sucks. - data = eval(drag_info.draggingPasteboard().stringForType_(MIRO_DND_ITEM_LOCAL)) - ids, _ = data - ids = eval(ids) - wrapper = wrappermap.wrapper(view) - self.drag_dest.accept_drop(wrapper, self.model, typ, - drag_info.draggingSourceOperationMask(), parent, - position, ids) - return YES - else: - return NO - -class MiroTableViewDataSource(DataSourceBase, protocols.NSTableDataSource): - def numberOfRowsInTableView_(self, table_view): - return len(self.model) - - def tableView_objectValueForTableColumn_row_(self, table_view, column, row): - node = self.model.nth_iter(row).value() - self.model.remember_row_at_index(node, row) - return self.model.get_column_data(node.values, column) - - def tableView_writeRowsWithIndexes_toPasteboard_(self, tableview, rowIndexes, - pasteboard): - indexes = list_from_nsindexset(rowIndexes) - data = [self.model[self.model.nth_iter(i)] for i in indexes] - return self.view_writeColumnData_ToPasteboard_(tableview, data, - pasteboard) - - def translateRow_operation_(self, row, operation): - if operation == NSTableViewDropOn: - return self.model.nth_iter(row), -1 - else: - return None, row - - def tableView_validateDrop_proposedRow_proposedDropOperation_(self, - tableview, drag_info, row, operation): - parent, position = self.translateRow_operation_(row, operation) - drop_action = self.validateDrop_dragInfo_parentIter_position_(tableview, - drag_info, parent, position) - if isinstance(drop_action, (list, tuple)): - # XXX nothing uses this yet - drop_action, iter = drop_action - tableview.setDropRow_dropOperation_( - self.model.row_of_iter(tableview, iter), - NSTableViewDropOn) - return drop_action - - def tableView_acceptDrop_row_dropOperation_(self, - tableview, drag_info, row, operation): - parent, position = self.translateRow_operation_(row, operation) - return self.acceptDrop_dragInfo_parentIter_position_(tableview, - drag_info, parent, position) - - -class MiroOutlineViewDataSource(DataSourceBase, protocols.NSOutlineViewDataSource): - def outlineView_child_ofItem_(self, view, child, item): - if item is nil: - row_list = self.model.row_list - else: - row_list = item.children - return row_list.nth_iter(child).value() - - def outlineView_isItemExpandable_(self, view, item): - if item is not nil and hasattr(item, 'children'): - return len(item.children) > 0 - else: - return len(self.model) > 0 - - def outlineView_numberOfChildrenOfItem_(self, view, item): - if item is not nil and hasattr(item, 'children'): - return len(item.children) - else: - return len(self.model) - - def outlineView_objectValueForTableColumn_byItem_(self, view, column, - item): - return self.model.get_column_data(item.values, column) - - def outlineView_writeItems_toPasteboard_(self, outline_view, items, - pasteboard): - data = [i.values for i in items] - return self.view_writeColumnData_ToPasteboard_(outline_view, data, - pasteboard) - - def outlineView_validateDrop_proposedItem_proposedChildIndex_(self, - outlineview, drag_info, item, child_index): - if item is None: - iter = None - else: - iter = self.model.iter_for_item[item] - drop_action = self.validateDrop_dragInfo_parentIter_position_( - outlineview, drag_info, iter, child_index) - if isinstance(drop_action, (tuple, list)): - drop_action, iter = drop_action - outlineview.setDropItem_dropChildIndex_( - iter.value(), NSOutlineViewDropOnItemIndex) - return drop_action - - def outlineView_acceptDrop_item_childIndex_(self, outlineview, drag_info, - item, child_index): - if item is None: - iter = None - else: - iter = self.model.iter_for_item[item] - return self.acceptDrop_dragInfo_parentIter_position_(outlineview, - drag_info, iter, child_index) diff --git a/mvc/widgets/osx/tableview.py b/mvc/widgets/osx/tableview.py deleted file mode 100644 index 2d2256f..0000000 --- a/mvc/widgets/osx/tableview.py +++ /dev/null @@ -1,1629 +0,0 @@ -# @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. - -""".tableview -- TableView widget and it's -associated classes. -""" - -import math -import logging -from contextlib import contextmanager -from collections import namedtuple - -from AppKit import * -from Foundation import * -from objc import YES, NO, nil - -from mvc import signals -from mvc import errors -from mvc.widgets import widgetconst -from mvc.widgets.tableselection import SelectionOwnerMixin -from mvc.widgets.tablescroll import ScrollbarOwnerMixin -from .utils import filename_to_unicode -import wrappermap -import tablemodel -import osxmenus -from .base import Widget -from .simple import Image -from .drawing import DrawingContext, DrawingStyle, Gradient, ImageSurface -from .helpers import NotificationForwarder -from .layoutmanager import LayoutManager - -EXPANDER_PADDING = 6 -HEADER_HEIGHT = 17 -CUSTOM_HEADER_HEIGHT = 25 - -def iter_range(ns_range): - """Iterate over an NSRange object""" - return xrange(ns_range.location, ns_range.location + ns_range.length) - -Rect = namedtuple('Rect', 'x y width height') -def NSRectToRect(nsrect): - origin, size = nsrect.origin, nsrect.size - return Rect(origin.x, origin.y, size.width, size.height) - -Point = namedtuple('Point', 'x y') -def NSPointToPoint(nspoint): - return Point(int(nspoint.x), int(nspoint.y)) - -class HotspotTracker(object): - """Contains the info on the currently tracked hotspot. See: - https://develop.participatoryculture.org/index.php/WidgetAPITableView - """ - def __init__(self, tableview, point): - self.tableview = tableview - self.row = tableview.rowAtPoint_(point) - self.column = tableview.columnAtPoint_(point) - if self.row == -1 or self.column == -1: - self.hit = False - return - model = tableview.dataSource().model - self.iter = model.iter_for_row(tableview, self.row) - self.table_column = tableview.tableColumns()[self.column] - self.cell = self.table_column.dataCell() - self.update_position(point) - if isinstance(self.cell, CustomTableCell): - self.name = self.calc_hotspot() - else: - self.name = None - self.hit = (self.name is not None) - - def is_for_context_menu(self): - return self.name == '#show-context-menu' - - def calc_cell_hotspot(self, column, row): - if (self.hit and self.column == column and self.row == row): - return self.name - else: - return None - - def update_position(self, point): - cell_frame = self.tableview.frameOfCellAtColumn_row_(self.column, - self.row) - self.pos = NSPoint(point.x - cell_frame.origin.x, - point.y - cell_frame.origin.y) - - def update_hit(self): - old_hit = self.hit - self.hit = (self.calc_hotspot() == self.name) - if old_hit != self.hit: - self.redraw_cell() - - def set_cell_data(self): - model = self.tableview.dataSource().model - row = model[self.iter] - value_dict = model.get_column_data(row, self.table_column) - self.cell.setObjectValue_(value_dict) - self.cell.set_wrapper_data() - - def calc_hotspot(self): - self.set_cell_data() - cell_frame = self.tableview.frameOfCellAtColumn_row_(self.column, - self.row) - style = self.cell.make_drawing_style(cell_frame, self.tableview) - layout_manager = self.cell.layout_manager - layout_manager.reset() - return self.cell.wrapper.hotspot_test(style, layout_manager, - self.pos.x, self.pos.y, cell_frame.size.width, - cell_frame.size.height) - - def redraw_cell(self): - # Check to see if we removed the table in response to a hotspot click. - if self.tableview.superview() is not nil: - cell_frame = self.tableview.frameOfCellAtColumn_row_(self.column, - self.row) - self.tableview.setNeedsDisplayInRect_(cell_frame) - -def _calc_interior_frame(total_frame, tableview): - """Calculate the inner cell area for a table cell. - - We tell cocoa that the intercell spacing is (0, 0) and instead handle the - spacing ourselves. This method calculates the area that a cell should - render to, given the total spacing. - """ - return NSMakeRect(total_frame.origin.x + tableview.column_spacing // 2, - total_frame.origin.y + tableview.row_spacing // 2, - total_frame.size.width - tableview.column_spacing, - total_frame.size.height - tableview.row_spacing) - -class MiroTableCell(NSTextFieldCell): - def init(self): - return super(MiroTableCell, self).initTextCell_('') - - def calcHeight_(self, view): - font = self.font() - return math.ceil(font.ascender() + abs(font.descender()) + - font.leading()) - - def highlightColorWithFrame_inView_(self, frame, view): - return nil - - def setObjectValue_(self, value_dict): - if isinstance(value_dict, dict): - NSCell.setObjectValue_(self, value_dict['value']) - else: - # OS X calls setObjectValue_('') on intialization - NSCell.setObjectValue_(self, value_dict) - - def drawInteriorWithFrame_inView_(self, frame, view): - return NSTextFieldCell.drawInteriorWithFrame_inView_(self, - _calc_interior_frame(frame, view), view) - -class MiroTableInfoListTextCell(MiroTableCell): - def initWithAttrGetter_(self, attr_getter): - self = self.init() - self.setWraps_(NO) - self.attr_getter = attr_getter - self._textColor = self.textColor() - return self - - def drawWithFrame_inView_(self, frame, view): - # adjust frame based on the cell spacing - frame = _calc_interior_frame(frame, view) - if (self.isHighlighted() and frame is not None and - (view.isDescendantOf_(view.window().firstResponder()) or - view.gradientHighlight) and view.window().isMainWindow()): - self.setTextColor_(NSColor.whiteColor()) - else: - self.setTextColor_(self._textColor) - return MiroTableCell.drawWithFrame_inView_(self, frame, view) - - def titleRectForBounds_(self, rect): - frame = MiroTableCell.titleRectForBounds_(self, rect) - text_size = self.attributedStringValue().size() - frame.origin.y = rect.origin.y + (rect.size.height - text_size.height) / 2.0 - return frame - - def drawInteriorWithFrame_inView_(self, frame, view): - rect = self.titleRectForBounds_(frame) - self.attributedStringValue().drawInRect_(rect) - - def setObjectValue_(self, value): - if isinstance(value, tuple): - info, attrs, group_info = value - cell_text = self.attr_getter(info) - NSCell.setObjectValue_(self, cell_text) - else: - # Getting set to a something other than a model row, usually this - # happens in initialization - NSCell.setObjectValue_(self, '') - -class MiroTableImageCell(NSImageCell): - def calcHeight_(self, view): - return self.value_dict['image'].size().height - - def highlightColorWithFrame_inView_(self, frame, view): - return nil - - def setObjectValue_(self, value_dict): - NSImageCell.setObjectValue_(self, value_dict['image']) - - def drawInteriorWithFrame_inView_(self, frame, view): - return NSImageCell.drawInteriorWithFrame_inView_(self, - _calc_interior_frame(frame, view), view) - -class MiroCheckboxCell(NSButtonCell): - def init(self): - self = super(MiroCheckboxCell, self).init() - self.setButtonType_(NSSwitchButton) - self.setTitle_('') - return self - - def calcHeight_(self, view): - return self.cellSize().height - - def highlightColorWithFrame_inView_(self, frame, view): - return nil - - def setObjectValue_(self, value_dict): - if isinstance(value_dict, dict): - NSButtonCell.setObjectValue_(self, value_dict['value']) - else: - # OS X calls setObjectValue_('') on intialization - NSCell.setObjectValue_(self, value_dict) - - def startTrackingAt_inView_(self, point, view): - return YES - - def continueTracking_at_inView_(self, lastPoint, at, view): - return YES - - def stopTracking_at_inView_mouseIsUp_(self, lastPoint, at, tableview, mouseIsUp): - if mouseIsUp: - column = tableview.columnAtPoint_(at) - row = tableview.rowAtPoint_(at) - if column != -1 and row != -1: - wrapper = wrappermap.wrapper(tableview) - column = wrapper.columns[column] - itr = wrapper.model.iter_for_row(tableview, row) - column.renderer.emit('clicked', itr) - return NSButtonCell.stopTracking_at_inView_mouseIsUp_(self, lastPoint, - at, tableview, mouseIsUp) - - def drawInteriorWithFrame_inView_(self, frame, view): - return NSButtonCell.drawInteriorWithFrame_inView_(self, - _calc_interior_frame(frame, view), view) - -class CellRendererBase(object): - DRAW_BACKGROUND = True - - def set_index(self, index): - self.index = index - - def get_index(self): - return self.index - -class CellRenderer(CellRendererBase): - def __init__(self): - self.cell = self.build_cell() - self._font_scale_factor = 1.0 - self._font_bold = False - self.set_align('left') - - def build_cell(self): - return MiroTableCell.alloc().init() - - def setDataCell_(self, column): - column.setDataCell_(self.cell) - - def set_text_size(self, size): - if size == widgetconst.SIZE_NORMAL: - self._font_scale_factor = 1.0 - elif size == widgetconst.SIZE_SMALL: - # make the scale factor such so that the font size is 11.0 - self._font_scale_factor = 11.0 / NSFont.systemFontSize() - else: - raise ValueError("Unknown size: %s" % size) - self._set_font() - - def set_font_scale(self, scale_factor): - self._font_scale_factor = scale_factor - self._set_font() - - def set_bold(self, bold): - self._font_bold = bold - self._set_font() - - def _set_font(self): - size = NSFont.systemFontSize() * self._font_scale_factor - if self._font_bold: - font = NSFont.boldSystemFontOfSize_(size) - else: - font = NSFont.systemFontOfSize_(size) - self.cell.setFont_(font) - - def set_color(self, color): - color = NSColor.colorWithDeviceRed_green_blue_alpha_(color[0], - color[1], color[2], 1.0) - self.cell._textColor = color - self.cell.setTextColor_(color) - - def set_align(self, align): - if align == 'left': - ns_alignment = NSLeftTextAlignment - elif align == 'center': - ns_alignment = NSCenterTextAlignment - elif align == 'right': - ns_alignment = NSRightTextAlignment - else: - raise ValueError("unknown alignment: %s", align) - self.cell.setAlignment_(ns_alignment) - -class ImageCellRenderer(CellRendererBase): - def setDataCell_(self, column): - column.setDataCell_(MiroTableImageCell.alloc().init()) - -class CheckboxCellRenderer(CellRendererBase, signals.SignalEmitter): - def __init__(self): - signals.SignalEmitter.__init__(self, 'clicked') - self.size = widgetconst.SIZE_NORMAL - - def set_control_size(self, size): - self.size = size - - def setDataCell_(self, column): - cell = MiroCheckboxCell.alloc().init() - if self.size == widgetconst.SIZE_SMALL: - cell.setControlSize_(NSSmallControlSize) - column.setDataCell_(cell) - -class CustomTableCell(NSCell): - def init(self): - self = super(CustomTableCell, self).init() - self.layout_manager = LayoutManager() - self.hotspot = None - self.default_drawing_style = DrawingStyle() - return self - - def highlightColorWithFrame_inView_(self, frame, view): - return nil - - def calcHeight_(self, view): - self.layout_manager.reset() - self.set_wrapper_data() - cell_size = self.wrapper.get_size(self.default_drawing_style, - self.layout_manager) - return cell_size[1] - - def make_drawing_style(self, frame, view): - text_color = None - if (self.isHighlighted() and frame is not None and - (view.isDescendantOf_(view.window().firstResponder()) or - view.gradientHighlight) and view.window().isMainWindow()): - text_color = NSColor.whiteColor() - return DrawingStyle(text_color=text_color) - - def drawInteriorWithFrame_inView_(self, frame, view): - NSGraphicsContext.currentContext().saveGraphicsState() - if not self.wrapper.IGNORE_PADDING: - # adjust frame based on the cell spacing. We also have to adjust - # the hover position to account for the new frame - original_frame = frame - frame = _calc_interior_frame(frame, view) - hover_adjustment = (frame.origin.x - original_frame.origin.x, - frame.origin.y - original_frame.origin.y) - else: - hover_adjustment = (0, 0) - if self.wrapper.outline_column: - pad_left = EXPANDER_PADDING - else: - pad_left = 0 - drawing_rect = NSMakeRect(frame.origin.x + pad_left, frame.origin.y, - frame.size.width - pad_left, frame.size.height) - context = DrawingContext(view, drawing_rect, drawing_rect) - context.style = self.make_drawing_style(frame, view) - self.layout_manager.reset() - self.set_wrapper_data() - column = self.wrapper.get_index() - hover_pos = view.get_hover(self.row, column) - if hover_pos is not None: - hover_pos = [hover_pos[0] - hover_adjustment[0], - hover_pos[1] - hover_adjustment[1]] - self.wrapper.render(context, self.layout_manager, self.isHighlighted(), - self.hotspot, hover_pos) - NSGraphicsContext.currentContext().restoreGraphicsState() - - def setObjectValue_(self, value): - self.object_value = value - - def set_wrapper_data(self): - self.wrapper.__dict__.update(self.object_value) - -class CustomCellRenderer(CellRendererBase): - CellClass = CustomTableCell - - IGNORE_PADDING = False - - def __init__(self): - self.outline_column = False - self.index = None - - def setDataCell_(self, column): - # Note that the ownership is the opposite of what happens in widgets. - # The NSObject owns it's wrapper widget. This happens for a couple - # reasons: - # 1) The data cell gets copied a bunch of times, so wrappermap won't - # work with it. - # 2) The Wrapper should only needs to stay around as long as the - # NSCell that it's wrapping is around. Once the column gets removed - # from the table, the wrapper can be deleted. - nscell = self.CellClass.alloc().init() - nscell.wrapper = self - column.setDataCell_(nscell) - - def hotspot_test(self, style, layout, x, y, width, height): - return None - -class InfoListTableCell(CustomTableCell): - def set_wrapper_data(self): - self.wrapper.info, self.wrapper.attrs, self.wrapper.group_info = \ - self.object_value - -class InfoListRenderer(CustomCellRenderer): - CellClass = InfoListTableCell - - def hotspot_test(self, style, layout, x, y, width, height): - return None - -class InfoListRendererText(CellRenderer): - def build_cell(self): - cell = MiroTableInfoListTextCell.alloc() - return cell.initWithAttrGetter_(self.get_value) - -def calc_row_height(view, model_row): - max_height = 0 - model = view.dataSource().model - for column in view.tableColumns(): - cell = column.dataCell() - data = model.get_column_data(model_row, column) - cell.setObjectValue_(data) - cell_height = cell.calcHeight_(view) - max_height = max(max_height, cell_height) - if max_height == 0: - max_height = 12 - return max_height + view.row_spacing - -class TableViewDelegate(NSObject): - def tableView_willDisplayCell_forTableColumn_row_(self, view, cell, - column, row): - column = view.column_index_map[column] - cell.column = column - cell.row = row - if view.hotspot_tracker: - cell.hotspot = view.hotspot_tracker.calc_cell_hotspot(column, row) - else: - cell.hotspot = None - - def tableView_didClickTableColumn_(self, tableview, column): - wrapper = wrappermap.wrapper(tableview) - for column_wrapper in wrapper.columns: - if column_wrapper._column is column: - column_wrapper.emit('clicked') - - def tableView_toolTipForCell_rect_tableColumn_row_mouseLocation_(self, tableview, cell, rect, column, row, location): - wrapper = wrappermap.wrapper(tableview) - iter = tableview.dataSource().model.iter_for_row(tableview, row) - for wrapper_column in wrapper.columns: - if wrapper_column._column is column: - break - return (wrapper.get_tooltip(iter, wrapper_column), rect) - -class VariableHeightTableViewDelegate(TableViewDelegate): - def tableView_heightOfRow_(self, table_view, row): - model = table_view.dataSource().model - iter = model.iter_for_row(table_view, row) - if iter is None: - return 12 - return calc_row_height(table_view, model[iter]) - - -# TableViewCommon is a hack to do a Mixin class. We want the same behaviour -# for our table views and our outline views. Normally we would use a Mixin, -# but that doesn't work with pyobjc. Instead we define the common code in -# TableViewCommon, then copy it into MiroTableView and MiroOutlineView - -class TableViewCommon(object): - def init(self): - self = super(self.__class__, self).init() - self.hotspot_tracker = None - self._tracking_rects = [] - self.hover_info = None - self.column_index_map = {} - self.setFocusRingType_(NSFocusRingTypeNone) - self.handled_last_mouse_down = False - self.gradientHighlight = False - self.tracking_area = None - self.group_lines_enabled = False - self.group_line_width = 1 - self.group_line_color = (0, 0, 0, 1.0) - # we handle cell spacing manually - self.setIntercellSpacing_(NSSize(0, 0)) - self.column_spacing = 3 - self.row_spacing = 2 - return self - - def updateTrackingAreas(self): - # remove existing tracking area if needed - if self.tracking_area: - self.removeTrackingArea_(self.tracking_area) - - # create a new tracking area for the entire view. This allows us to - # get mouseMoved events whenever the mouse is inside our view. - self.tracking_area = NSTrackingArea.alloc() - self.tracking_area.initWithRect_options_owner_userInfo_( - self.visibleRect(), - NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | - NSTrackingActiveInKeyWindow, - self, - nil) - self.addTrackingArea_(self.tracking_area) - - def addTableColumn_(self, column): - index = len(self.tableColumns()) - column.set_index(index) - self.column_index_map[column._column] = index - self.SuperClass.addTableColumn_(self, column._column) - - def removeTableColumn_(self, column): - self.SuperClass.removeTableColumn_(self, column) - removed = self.column_index_map.pop(column) - for key, index in self.column_index_map.items(): - if index > removed: - self.column_index_map[key] -= 1 - - def moveColumn_toColumn_(self, src, dest): - # Need to switch the TableColumn objects too - columns = wrappermap.wrapper(self).columns - columns[src], columns[dest] = columns[dest], columns[src] - for index, column in enumerate(columns): - column.set_index(index) - self.SuperClass.moveColumn_toColumn_(self, src, dest) - - def highlightSelectionInClipRect_(self, rect): - if wrappermap.wrapper(self).draws_selection: - if not self.gradientHighlight: - return self.SuperClass.highlightSelectionInClipRect_(self, - rect) - context = NSGraphicsContext.currentContext() - focused = self.isDescendantOf_(self.window().firstResponder()) - for row in tablemodel.list_from_nsindexset(self.selectedRowIndexes()): - self.drawBackgroundGradient(context, focused, row) - - def setFrameSize_(self, size): - if size.height == 0: - size.height = 4 - self.SuperClass.setFrameSize_(self, size) - - def drawBackgroundGradient(self, context, focused, row): - widget = wrappermap.wrapper(self) - window = widget.get_window() - if window and window.is_active(): - if focused: - start_color = (0.588, 0.717, 0.843) - end_color = (0.416, 0.568, 0.713) - top_line_color = (0.416, 0.569, 0.714, 1.0) - bottom_line_color = (0.416, 0.569, 0.714, 1.0) - else: - start_color = (168 / 255.0, 188 / 255.0, 208 / 255.0) - end_color = (129 / 255.0, 152 / 255.0, 176 / 255.0) - top_line_color = (129 / 255.0, 152 / 255.0, 175 / 255.0, 1.0) - bottom_line_color = (0.416, 0.569, 0.714, 1.0) - else: - start_color = (0.675, 0.722, 0.765) - end_color = (0.592, 0.659, 0.710) - top_line_color = (0.596, 0.635, 0.671, 1.0) - bottom_line_color = (0.522, 0.576, 0.620, 1.0) - - rect = self.rectOfRow_(row) - top = NSMakeRect(rect.origin.x, rect.origin.y, rect.size.width, 1) - context.saveGraphicsState() - # draw the top line - NSColor.colorWithDeviceRed_green_blue_alpha_(*top_line_color).set() - NSRectFill(top) - bottom = NSMakeRect(rect.origin.x, rect.origin.y + rect.size.height - 2, - rect.size.width, 1) - NSColor.colorWithDeviceRed_green_blue_alpha_(*bottom_line_color).set() - NSRectFill(bottom) - highlight = NSMakeRect(rect.origin.x, rect.origin.y + rect.size.height - 1, - rect.size.width, 1) - NSColor.colorWithDeviceRed_green_blue_alpha_(0.918, 0.925, 0.941, 1.0).set() - NSRectFill(highlight) - - # draw the gradient - rect.origin.y += 1 - rect.size.height -= 3 - NSRectClip(rect) - gradient = Gradient(rect.origin.x, rect.origin.y, - rect.origin.x, rect.origin.y + rect.size.height) - gradient.set_start_color(start_color) - gradient.set_end_color(end_color) - gradient.draw() - context.restoreGraphicsState() - - def drawBackgroundInClipRect_(self, clip_rect): - # save our graphics state, since we are about to modify the clip path - graphics_context = NSGraphicsContext.currentContext() - graphics_context.saveGraphicsState() - # create a NSBezierPath that contains the rects of the columns with - # DRAW_BACKGROUND True. - clip_path = NSBezierPath.bezierPath() - number_of_columns = len(self.tableColumns()) - for col_index in iter_range(self.columnsInRect_(clip_rect)): - column = wrappermap.wrapper(self.tableColumns()[col_index]) - column_rect = None - if column.renderer.DRAW_BACKGROUND: - # We should draw the background for this column, add it's rect - # to our clip rect. - column_rect = self.rectOfColumn_(col_index) - clip_path.appendBezierPathWithRect_(column_rect) - else: - # We shouldn't draw the background for this column. Don't add - # anything to the clip rect, but do draw the area before the - # first row and after the last row. - self.drawBackgroundOutsideContent_clipRect_(col_index, - clip_rect) - if col_index == number_of_columns - 1: # last column - if not column_rect: - column_rect = self.rectOfColumn_(col_index) - column_right = column_rect.origin.x + column_rect.size.width - clip_right = clip_rect.origin.x + clip_rect.size.width - if column_right < clip_right: - # there's space to the right, so add that to the clip_rect - remaining = clip_right - column_right - left_rect = NSMakeRect(column_right, clip_rect.origin.y, - remaining, clip_rect.size.height) - clip_path.appendBezierPathWithRect_(left_rect) - # clip to that path - clip_path.addClip() - # do the default drawing - self.SuperClass.drawBackgroundInClipRect_(self, clip_rect) - # restore graphics state - graphics_context.restoreGraphicsState() - - def drawBackgroundOutsideContent_clipRect_(self, index, clip_rect): - """Draw our background outside the rows with content - - We call this for cells with DRAW_BACKGROUND set to False. For those, - we let the cell draw their own background, but we still need to draw - the background before the first cell and after the last cell. - """ - - self.backgroundColor().set() - - total_rect = NSIntersectionRect(self.rectOfColumn_(index), clip_rect) - - if self.numberOfRows() == 0: - # if no rows are selected, draw the background over everything - NSRectFill(total_rect) - return - - # fill the area above the first row - first_row_rect = self.rectOfRow_(0) - if first_row_rect.origin.y > total_rect.origin.y: - height = first_row_rect.origin.y - total_rect.origin.y - NSRectFill(NSMakeRect(total_rect.origin.x, total_rect.origin.y, - total_rect.size.width, height)) - - # fill the area below the last row - last_row_rect = self.rectOfRow_(self.numberOfRows()-1) - if NSMaxY(last_row_rect) < NSMaxY(total_rect): - y = NSMaxY(last_row_rect) + 1 - height = NSMaxY(total_rect) - NSMaxY(last_row_rect) - NSRectFill(NSMakeRect(total_rect.origin.x, y, - total_rect.size.width, height)) - - def drawRow_clipRect_(self, row, clip_rect): - self.SuperClass.drawRow_clipRect_(self, row, clip_rect) - if self.group_lines_enabled: - self.drawGroupLine_(row) - - def drawGroupLine_(self, row): - infolist = wrappermap.wrapper(self).model - if (not isinstance(infolist, tablemodel.InfoListModel) or - infolist.get_grouping() is None): - return - - info, attrs, group_info = infolist[row] - if group_info[0] == group_info[1] - 1: - rect = self.rectOfRow_(row) - rect.origin.y = NSMaxY(rect) - self.group_line_width - rect.size.height = self.group_line_width - NSColor.colorWithDeviceRed_green_blue_alpha_( - *self.group_line_color).set() - NSRectFill(rect) - - def canDragRowsWithIndexes_atPoint_(self, indexes, point): - return YES - - def draggingSourceOperationMaskForLocal_(self, local): - drag_source = wrappermap.wrapper(self).drag_source - if drag_source and local: - return drag_source.allowed_actions() - return NSDragOperationNone - - def mouseMoved_(self, event): - location = self.convertPoint_fromView_(event.locationInWindow(), nil) - row = self.rowAtPoint_(location) - column = self.columnAtPoint_(location) - if (self.hover_info is not None and self.hover_info != (row, column)): - # left a cell, redraw it the old one - rect = self.frameOfCellAtColumn_row_(self.hover_info[1], - self.hover_info[0]) - self.setNeedsDisplayInRect_(rect) - if row == -1 or column == -1: - # corner case: we got a mouseMoved_ event, but the pointer is - # outside the view - self.hover_pos = self.hover_info = None - return - # queue a redraw on the cell currently hovered over - rect = self.frameOfCellAtColumn_row_(column, row) - self.setNeedsDisplayInRect_(rect) - # recalculate hover_pos and hover_info - self.hover_pos = (location[0] - rect[0][0], - location[0] - rect[0][1]) - self.hover_info = (row, column) - - def mouseExited_(self, event): - if self.hover_info: - # mouse left our window, unset hover and redraw the cell that the - # mouse was in - rect = self.frameOfCellAtColumn_row_(self.hover_info[1], - self.hover_info[0]) - self.setNeedsDisplayInRect_(rect) - self.hover_pos = self.hover_info = None - - def get_hover(self, row, column): - if self.hover_info == (row, column): - return self.hover_pos - else: - return None - - def mouseDown_(self, event): - if event.modifierFlags() & NSControlKeyMask: - self.handleContextMenu_(event) - self.handled_last_mouse_down = True - return - - point = self.convertPoint_fromView_(event.locationInWindow(), nil) - - if event.clickCount() == 2: - if self.handled_last_mouse_down: - return - wrapper = wrappermap.wrapper(self) - row = self.rowAtPoint_(point) - if (row != -1 and self.point_should_click(point, row)): - iter = wrapper.model.iter_for_row(self, row) - wrapper.emit('row-activated', iter) - return - - # Like clickCount() == 2 but keep running so we can get to run the - # hotspot tracker et al. - if event.clickCount() == 1: - wrapper = wrappermap.wrapper(self) - row = self.rowAtPoint_(point) - if (row != -1 and self.point_should_click(point, row)): - - iter = wrapper.model.iter_for_row(self, row) - wrapper.emit('row-clicked', iter) - - hotspot_tracker = HotspotTracker(self, point) - if hotspot_tracker.hit: - self.hotspot_tracker = hotspot_tracker - self.hotspot_tracker.redraw_cell() - self.handled_last_mouse_down = True - if hotspot_tracker.is_for_context_menu(): - self.popup_context_menu(self.hotspot_tracker.row, event) - # once we're out of that call, we know the context menu is - # gone - hotspot_tracker.redraw_cell() - self.hotspot_tracker = None - else: - self.handled_last_mouse_down = False - self.SuperClass.mouseDown_(self, event) - - def point_should_click(self, point, row): - """Should a click on a point result in a row-clicked signal? - - Subclasses can override if not every point should result in a click. - """ - return True - - def rightMouseDown_(self, event): - self.handleContextMenu_(event) - - def handleContextMenu_(self, event): - self.window().makeFirstResponder_(self) - point = self.convertPoint_fromView_(event.locationInWindow(), nil) - column = self.columnAtPoint_(point) - row = self.rowAtPoint_(point) - if self.group_lines_enabled and column == 0: - self.selectAllItemsInGroupForRow_(row) - self.popup_context_menu(row, event) - - def selectAllItemsInGroupForRow_(self, row): - wrapper = wrappermap.wrapper(self) - infolist = wrapper.model - if (not isinstance(infolist, tablemodel.InfoListModel) or - infolist.get_grouping() is None): - return - - info, attrs, group_info = infolist[row] - select_range = NSMakeRange(row - group_info[0], group_info[1]) - index_set = NSIndexSet.indexSetWithIndexesInRange_(select_range) - self.selectRowIndexes_byExtendingSelection_(index_set, NO) - - def popup_context_menu(self, row, event): - selection = self.selectedRowIndexes() - if row != -1 and not selection.containsIndex_(row): - index_set = NSIndexSet.alloc().initWithIndex_(row) - self.selectRowIndexes_byExtendingSelection_(index_set, NO) - wrapper = wrappermap.wrapper(self) - if wrapper.context_menu_callback is not None: - menu_items = wrapper.context_menu_callback(wrapper) - menu = osxmenus.make_context_menu(menu_items) - NSMenu.popUpContextMenu_withEvent_forView_(menu, event, self) - - def mouseDragged_(self, event): - if self.hotspot_tracker is not None: - point = self.convertPoint_fromView_(event.locationInWindow(), nil) - self.hotspot_tracker.update_position(point) - self.hotspot_tracker.update_hit() - else: - self.SuperClass.mouseDragged_(self, event) - - def mouseUp_(self, event): - if self.hotspot_tracker is not None: - point = self.convertPoint_fromView_(event.locationInWindow(), nil) - self.hotspot_tracker.update_position(point) - self.hotspot_tracker.update_hit() - if self.hotspot_tracker.hit: - wrappermap.wrapper(self).send_hotspot_clicked() - if self.hotspot_tracker: - self.hotspot_tracker.redraw_cell() - self.hotspot_tracker = None - else: - self.SuperClass.mouseUp_(self, event) - - def keyDown_(self, event): - mods = osxmenus.translate_event_modifiers(event) - if event.charactersIgnoringModifiers() == ' ' and len(mods) == 0: - # handle spacebar with no modifiers by sending the row-activated - # signal - wrapper = wrappermap.wrapper(self) - row = self.selectedRow() - if row >= 0: - iter = wrapper.model.iter_for_row(self, row) - wrapper.emit('row-activated', iter) - else: - self.SuperClass.keyDown_(self, event) - -class TableColumn(signals.SignalEmitter): - def __init__(self, title, renderer, header=None, **attrs): - signals.SignalEmitter.__init__(self) - self.create_signal('clicked') - self._column = MiroTableColumn.alloc().initWithIdentifier_(title) - self._column.set_attrs(attrs) - wrappermap.add(self._column, self) - header_cell = MiroTableHeaderCell.alloc().init() - self.custom_header = False - if header: - header_cell.set_widget(header) - self.custom_header = True - self._column.setHeaderCell_(header_cell) - self._column.headerCell().setStringValue_(title) - self._column.setEditable_(NO) - self._column.setResizingMask_(NSTableColumnNoResizing) - self.renderer = renderer - self.sort_order_ascending = True - self.sort_indicator_visible = False - self.do_horizontal_padding = True - self.min_width = self.max_width = None - renderer.setDataCell_(self._column) - - def set_do_horizontal_padding(self, horizontal_padding): - self.do_horizontal_padding = horizontal_padding - - def set_right_aligned(self, right_aligned): - if right_aligned: - self._column.headerCell().setAlignment_(NSRightTextAlignment) - else: - self._column.headerCell().setAlignment_(NSLeftTextAlignment) - - def set_min_width(self, width): - self.min_width = width - - def set_max_width(self, width): - self.max_width = width - - def set_width(self, width): - self._column.setWidth_(width) - - def get_width(self): - return self._column.width() - - def set_resizable(self, resizable): - mask = 0 - if resizable: - mask |= NSTableColumnUserResizingMask - self._column.setResizingMask_(mask) - - def set_sort_indicator_visible(self, visible): - self.sort_indicator_visible = visible - self._column.tableView().headerView().setNeedsDisplay_(True) - - def get_sort_indicator_visible(self): - return self.sort_indicator_visible - - def set_sort_order(self, ascending): - self.sort_order_ascending = ascending - self._column.tableView().headerView().setNeedsDisplay_(True) - - def get_sort_order_ascending(self): - return self.sort_order_ascending - - def set_index(self, index): - self.index = index - self.renderer.set_index(index) - -class MiroTableColumn(NSTableColumn): - def set_attrs(self, attrs): - self._attrs = attrs - - def attrs(self): - return self._attrs - -class MiroTableView(NSTableView): - SuperClass = NSTableView - for name, value in TableViewCommon.__dict__.items(): - locals()[name] = value - -class MiroTableHeaderView(NSTableHeaderView): - def initWithFrame_(self, frame): - # frame is not used - self = super(MiroTableHeaderView, self).initWithFrame_(frame) - self.selected = None - self.custom_header = False - return self - - def drawRect_(self, rect): - wrapper = wrappermap.wrapper(self.tableView()) - if self.selected: - self.selected.set_selected(False) - for column in wrapper.columns: - if column.sort_indicator_visible: - self.selected = column._column.headerCell() - self.selected.set_selected(True) - self.selected.set_ascending(column.sort_order_ascending) - break - NSTableHeaderView.drawRect_(self, rect) - if self.custom_header: - NSGraphicsContext.currentContext().saveGraphicsState() - # Draw the separator between the header and the contents. - context = DrawingContext(self, rect, rect) - context.set_line_width(1) - context.set_color((2 / 255.0, 2 / 255.0, 2 / 255.0)) - context.move_to(0, context.height - 0.5) - context.rel_line_to(context.width, 0) - context.stroke() - NSGraphicsContext.currentContext().restoreGraphicsState() - -class MiroTableHeaderCell(NSTableHeaderCell): - def init(self): - self = super(MiroTableHeaderCell, self).init() - self.layout_manager = LayoutManager() - self.button = None - return self - - def set_selected(self, selected): - self.button._enabled = selected - - def set_ascending(self, ascending): - self.button._ascending = ascending - - def set_widget(self, widget): - self.button = widget - - def drawWithFrame_inView_(self, frame, view): - if self.button is None: - # use the default behavior when set_widget hasn't been called - return NSTableHeaderCell.drawWithFrame_inView_(self, frame, view) - - NSGraphicsContext.currentContext().saveGraphicsState() - drawing_rect = NSMakeRect(frame.origin.x, frame.origin.y, - frame.size.width, frame.size.height) - context = DrawingContext(view, drawing_rect, drawing_rect) - context.style = self.make_drawing_style(frame, view) - self.layout_manager.reset() - columns = wrappermap.wrapper(view.tableView()).columns - header_cells = [c._column.headerCell() for c in columns] - background_only = not self in header_cells - self.button.draw(context, self.layout_manager, background_only) - NSGraphicsContext.currentContext().restoreGraphicsState() - - def make_drawing_style(self, frame, view): - text_color = None - if (self.isHighlighted() and frame is not None and - (view.isDescendantOf_(view.window().firstResponder()) or - view.gradientHighlight)): - text_color = NSColor.whiteColor() - return DrawingStyle(text_color=text_color) - -class CocoaSelectionOwnerMixin(SelectionOwnerMixin): - """Cocoa-specific methods for selection management. - - This subclass should not define any behavior. Methods that cannot be - completed in this widget state should raise WidgetActionError. - """ - - def _set_allow_multiple_select(self, allow): - self.tableview.setAllowsMultipleSelection_(allow) - - def _get_allow_multiple_select(self): - return self.tableview.allowsMultipleSelection() - - def _get_selected_iters(self): - selection = self.tableview.selectedRowIndexes() - selrows = tablemodel.list_from_nsindexset(selection) - return [self.model.iter_for_row(self.tableview, row) for row in selrows] - - def _get_selected_iter(self): - row = self.tableview.selectedRow() - if row == -1: - return None - return self.model.iter_for_row(self.tableview, row) - - def _get_selected_rows(self): - return [self.model[i] for i in self._get_selected_iters()] - - @property - def num_rows_selected(self): - return self.tableview.numberOfSelectedRows() - - def _is_selected(self, iter_): - row = self.row_of_iter(iter_) - return self.tableview.isRowSelected_(row) - - def _select(self, iter_): - row = self.row_of_iter(iter_) - index_set = NSIndexSet.alloc().initWithIndex_(row) - self.tableview.selectRowIndexes_byExtendingSelection_(index_set, YES) - - def _unselect(self, iter_): - self.tableview.deselectRow_(self.row_of_iter(iter_)) - - def _unselect_all(self): - self.tableview.deselectAll_(nil) - - def _iter_to_string(self, iter_): - return unicode(self.model.row_of_iter(self.tableview, iter_)) - - def _iter_from_string(self, row): - return self.model.iter_for_row(self.tableview, int(row)) - -class CocoaScrollbarOwnerMixin(ScrollbarOwnerMixin): - """Manages a TableView's scroll position.""" - def __init__(self): - ScrollbarOwnerMixin.__init__(self, _work_around_17153=True) - self.connect('place-in-scroller', self.on_place_in_scroller) - self.scroll_position = (0, 0) - self.clipview_notifications = None - self._position_set = False - - def _set_scroll_position(self, scroll_to): - """Restore a saved scroll position.""" - self.scroll_position = scroll_to - try: - scroller = self._scroller - except errors.WidgetNotReadyError: - return - self._position_set = True - clipview = scroller.contentView() - if not self.clipview_notifications: - self.clipview_notifications = NotificationForwarder.create(clipview) - # NOTE: intentional changes are BoundsChanged; bad changes are - # FrameChanged - clipview.setPostsFrameChangedNotifications_(YES) - self.clipview_notifications.connect(self.on_scroll_changed, - 'NSViewFrameDidChangeNotification') - # NOTE: scrollPoint_ just scrolls the point into view; we want to - # scroll the view so that the point becomes the origin - size = self.tableview.visibleRect().size - size = (size.width, size.height) - rect = NSMakeRect(scroll_to[0], scroll_to[1], size[0], size[1]) - self.tableview.scrollRectToVisible_(rect) - - def on_place_in_scroller(self, scroller): - # workaround for 17153.1 - if not self._position_set: - self._set_scroll_position(self.scroll_position) - - @property - def _manually_scrolled(self): - """Return whether the view has been scrolled explicitly by the user - since the last time it was set automatically. Ignores X coords. - """ - auto_y = self.scroll_position[1] - real_y = self.get_scroll_position()[1] - return abs(auto_y - real_y) > 5 - - def _get_item_area(self, iter_): - rect = self.tableview.rectOfRow_(self.row_of_iter(iter_)) - return NSRectToRect(rect) - - def _get_visible_area(self): - return NSRectToRect(self._scroller.contentView().documentVisibleRect()) - - def _get_scroll_position(self): - point = self._scroller.contentView().documentVisibleRect().origin - return NSPointToPoint(point) - - def on_scroll_changed(self, notification): - # we get this notification when the scroll position has been reset (when - # it should not have been); put it back - self.set_scroll_position(self.scroll_position) - # this notification also serves as the Cocoa equivalent to - # on_scroll_range_changed, which tells super when we may be ready to - # scroll to an iter - self.emit('scroll-range-changed') - - def set_scroller(self, scroller): - """For GTK; Cocoa tableview knows its enclosingScrollView""" - - @property - def _scroller(self): - """Return an NSScrollView or raise WidgetNotReadyError""" - scroller = self.tableview.enclosingScrollView() - if not scroller: - raise errors.WidgetNotReadyError('enclosingScrollView') - return scroller - -class SorterPadding(NSView): - # Why is this a Mac only widget? Because the wrappermap mechanism requires - # us to layout the widgets (so that we may call back to the portable API - # hooks of the widget. Since we only set the view component, this fake - # widget is never placed so the wrappermap mechanism fails to work. - # - # So far, this is okay because only the Mac uses custom headers. - def init(self): - self = super(SorterPadding, self).init() - image = Image(resources.path('images/headertoolbar.png')) - self.image = ImageSurface(image) - return self - - def isFlipped(self): - return YES - - def drawRect_(self, rect): - context = DrawingContext(self, self.bounds(), rect) - context.style = DrawingStyle() - self.image.draw(context, 0, 0, context.width, context.height) - # XXX this color doesn't take into account enable/disabled state - # of the sorting widgets. - edge = 72.0 / 255 - context.set_color((edge, edge, edge)) - context.set_line_width(1) - context.move_to(0.5, 0) - context.rel_line_to(0, context.height) - context.stroke() - -class TableView(CocoaSelectionOwnerMixin, CocoaScrollbarOwnerMixin, Widget): - """Displays data as a tabular list. TableView follows the GTK TreeView - widget fairly closely. - """ - - CREATES_VIEW = False - # Bit of a hack. We create several views. By setting CREATES_VIEW to - # False, we get to position the views manually. - - draws_selection = True - - def __init__(self, model, custom_headers=False): - Widget.__init__(self) - CocoaSelectionOwnerMixin.__init__(self) - CocoaScrollbarOwnerMixin.__init__(self) - self.create_signal('hotspot-clicked') - self.create_signal('row-clicked') - self.create_signal('row-activated') - self.create_signal('reallocate-columns') - self.model = model - self.columns = [] - self.drag_source = self.drag_dest = None - self.context_menu_callback = None - self.tableview = MiroTableView.alloc().init() - self.data_source = tablemodel.MiroTableViewDataSource.alloc() - types = (tablemodel.MIRO_DND_ITEM_LOCAL,) - self.tableview.registerForDraggedTypes_(types) - self.view = self.tableview - self.data_source.initWithModel_(self.model) - self.tableview.setDataSource_(self.data_source) - self.tableview.setVerticalMotionCanBeginDrag_(YES) - self.set_columns_draggable(False) - self.set_auto_resizes(False) - self.row_height_set = False - self.set_fixed_height(False) - self.auto_resizing = False - self.header_view = MiroTableHeaderView.alloc().initWithFrame_( - NSMakeRect(0, 0, 0, 0)) - self.tableview.setCornerView_(None) - self.custom_header = False - self.header_height = HEADER_HEIGHT - self.set_show_headers(True) - self.notifications = NotificationForwarder.create(self.tableview) - self.model_signal_ids = [ - self.model.connect_weak('row-changed', self.on_row_change), - self.model.connect_weak('structure-will-change', - self.on_model_structure_change), - ] - self.iters_to_update = [] - self.height_changed = self.reload_needed = False - self.old_selection = None - self._resizing = False - if custom_headers: - self._enable_custom_headers() - - def unset_model(self): - for signal_id in self.model_signal_ids: - self.model.disconnect(signal_id) - self.model = None - self.tableview.setDataSource_(None) - self.data_source = None - - def _enable_custom_headers(self): - self.custom_header = True - self.header_height = CUSTOM_HEADER_HEIGHT - self.header_view.custom_header = True - self.tableview.setCornerView_(SorterPadding.alloc().init()) - - def enable_album_view_focus_hack(self): - # this only matters on GTK - pass - - def focus(self): - if self.tableview.window() is not None: - self.tableview.window().makeFirstResponder_(self.tableview) - - def send_hotspot_clicked(self): - tracker = self.tableview.hotspot_tracker - self.emit('hotspot-clicked', tracker.name, tracker.iter) - - def on_row_change(self, model, iter): - self.iters_to_update.append(iter) - if not self.fixed_height: - self.height_changed = True - if self.tableview.hotspot_tracker is not None: - self.tableview.hotspot_tracker.update_hit() - - def on_model_structure_change(self, model): - self.will_need_reload() - self.cancel_hotspot_track() - - def will_need_reload(self): - if not self.reload_needed: - self.reload_needed = True - self.old_selection = self._get_selected_rows() - - def cancel_hotspot_track(self): - if self.tableview.hotspot_tracker is not None: - self.tableview.hotspot_tracker.redraw_cell() - self.tableview.hotspot_tracker = None - - def on_expanded(self, notification): - self.invalidate_size_request() - item = notification.userInfo()['NSObject'] - iter_ = self.model.iter_for_item[item] - self.emit('row-expanded', iter_, self.model.get_path(iter_)) - - def on_collapsed(self, notification): - self.invalidate_size_request() - item = notification.userInfo()['NSObject'] - iter_ = self.model.iter_for_item[item] - self.emit('row-collapsed', iter_, self.model.get_path(iter_)) - - def on_column_resize(self, notification): - if self.auto_resizing or self._resizing: - return - self._resizing = True - try: - column = notification.userInfo()['NSTableColumn'] - label = column.headerCell().stringValue() - self.emit('reallocate-columns', {label: column.width()}) - finally: - self._resizing = False - - def is_tree(self): - return isinstance(self.model, tablemodel.TreeTableModel) - - def set_row_expanded(self, iter, expanded): - """Expand or collapse the specified row. Succeeds or raises - WidgetActionError. - """ - item = iter.value() - if expanded: - self.tableview.expandItem_(item) - else: - self.tableview.collapseItem_(item) - if self.tableview.isItemExpanded_(item) != expanded: - raise errors.WidgetActionError( - "cannot expand iter. expandable: %r" % ( - self.tableview.isExpandable_(item),)) - self.invalidate_size_request() - - def is_row_expanded(self, iter): - return self.tableview.isItemExpanded_(iter.value()) - - def calc_size_request(self): - self.tableview.tile() - height = self.tableview.frame().size.height - if self._show_headers: - height += self.header_height - return self.calc_width(), height - - def viewport_repositioned(self): - self._do_layout() - - def viewport_created(self): - wrappermap.add(self.tableview, self) - self._do_layout() - self._add_views() - self.notifications.connect(self.on_selection_changed, - 'NSTableViewSelectionDidChangeNotification') - self.notifications.connect(self.on_column_resize, - 'NSTableViewColumnDidResizeNotification') - # scroll has been unset - self._position_set = False - - def remove_viewport(self): - if self.viewport is not None: - self._remove_views() - wrappermap.remove(self.tableview) - self.notifications.disconnect() - self.viewport = None - if self.clipview_notifications: - self.clipview_notifications.disconnect() - self.clipview_notifications = None - - def _should_place_header_view(self): - return self._show_headers and not self.parent_is_scroller - - def _add_views(self): - self.viewport.view.addSubview_(self.tableview) - if self._should_place_header_view(): - self.viewport.view.addSubview_(self.header_view) - - def _remove_views(self): - self.tableview.removeFromSuperview() - self.header_view.removeFromSuperview() - - def _do_layout(self): - x = self.viewport.placement.origin.x - y = self.viewport.placement.origin.y - width = self.viewport.get_width() - height = self.viewport.get_height() - if self._should_place_header_view(): - self.header_view.setFrame_(NSMakeRect(x, y, - width, self.header_height)) - self.tableview.setFrame_(NSMakeRect(x, y + self.header_height, - width, height - self.header_height)) - else: - self.header_view.setFrame_(NSMakeRect(x, y, - width, self.header_height)) - self.tableview.setFrame_(NSMakeRect(x, y, width, height)) - - if self.auto_resize: - self.auto_resizing = True - # ListView sizes itself in do_size_allocated; - # this is necessary for tablist and StandardView - columns = self.tableview.tableColumns() - if len(columns) == 1: - columns[0].setWidth_(self.viewport.area().size.width) - self.auto_resizing = False - self.queue_redraw() - - def calc_width(self): - if self.column_count() == 0: - return 0 - width = 0 - columns = self.tableview.tableColumns() - if self.auto_resize: - # Table auto-resizes, we can shrink to min-width for each column - width = sum(column.minWidth() for column in columns) - width += self.tableview.column_spacing * self.column_count() - else: - # Table doesn't auto-resize, the columns can't get smaller than - # their current width - width = sum(column.width() for column in columns) - return width - - def start_bulk_change(self): - # stop our model from emitting signals, which is slow if we're - # adding/removing/changing a bunch of rows. Instead, just reload the - # model afterwards. - self.will_need_reload() - self.cancel_hotspot_track() - self.model.freeze_signals() - - def model_changed(self): - if not self.row_height_set and self.fixed_height: - self.try_to_set_row_height() - self.model.thaw_signals() - size_changed = False - if self.reload_needed: - self.tableview.reloadData() - new_selection = self._get_selected_rows() - if new_selection != self.old_selection: - self.on_selection_changed(self.tableview) - self.old_selection = None - size_changed = True - elif self.iters_to_update: - if self.fixed_height or not self.height_changed: - # our rows don't change height, just update cell areas - if self.is_tree(): - for iter in self.iters_to_update: - self.tableview.reloadItem_(iter.value()) - else: - for iter in self.iters_to_update: - row = self.row_of_iter(iter) - rect = self.tableview.rectOfRow_(row) - self.tableview.setNeedsDisplayInRect_(rect) - else: - # our rows can change height inform Cocoa that their heights - # might have changed (this will redraw them) - index_set = NSMutableIndexSet.alloc().init() - for iter in self.iters_to_update: - try: - index_set.addIndex_(self.row_of_iter(iter)) - except LookupError: - # This happens when the iter's parent is unexpanded, - # just ignore. - pass - self.tableview.noteHeightOfRowsWithIndexesChanged_(index_set) - size_changed = True - else: - return - if size_changed: - self.invalidate_size_request() - self.height_changed = self.reload_needed = False - self.iters_to_update = [] - - def width_for_columns(self, width): - """If the table is width pixels big, how much width is available for - the table's columns. - """ - # XXX this used to do some calculation with the spacing of each column, - # but it doesn't appear like we need it to be that complicated anymore - # (see #18273) - return width - 2 - - def set_column_spacing(self, column_spacing): - self.tableview.column_spacing = column_spacing - - def set_row_spacing(self, row_spacing): - self.tableview.row_spacing = row_spacing - - def set_alternate_row_backgrounds(self, setting): - self.tableview.setUsesAlternatingRowBackgroundColors_(setting) - - def set_grid_lines(self, horizontal, vertical): - mask = 0 - if horizontal: - mask |= NSTableViewSolidHorizontalGridLineMask - if vertical: - mask |= NSTableViewSolidVerticalGridLineMask - self.tableview.setGridStyleMask_(mask) - - def set_gradient_highlight(self, setting): - self.tableview.gradientHighlight = setting - - def set_group_lines_enabled(self, enabled): - self.tableview.group_lines_enabled = enabled - self.queue_redraw() - - def set_group_line_style(self, color, width): - self.tableview.group_line_color = color + (1.0,) - self.tableview.group_line_width = width - self.queue_redraw() - - def get_tooltip(self, iter, column): - return None - - def add_column(self, column): - if not self.custom_header == column.custom_header: - raise ValueError('Column header does not match type ' - 'required by TableView') - self.columns.append(column) - self.tableview.addTableColumn_(column) - self._set_min_max_column_widths(column) - # Adding a column means that each row could have a different height. - # call noteNumberOfRowsChanged() to have OS X recalculate the heights - self.tableview.noteNumberOfRowsChanged() - self.invalidate_size_request() - self.try_to_set_row_height() - - def _set_min_max_column_widths(self, column): - if column.do_horizontal_padding: - spacing = self.tableview.column_spacing - else: - spacing = 0 - if column.min_width > 0: - column._column.setMinWidth_(column.min_width + spacing) - if column.max_width > 0: - column._column.setMaxWidth_(column.max_width + spacing) - - def column_count(self): - return len(self.tableview.tableColumns()) - - def remove_column(self, index): - column = self.columns.pop(index) - self.tableview.removeTableColumn_(column._column) - self.invalidate_size_request() - - def get_columns(self): - titles = [] - columns = self.tableview.tableColumns() - for column in columns: - titles.append(column.headerCell().stringValue()) - return titles - - def set_background_color(self, (red, green, blue)): - color = NSColor.colorWithDeviceRed_green_blue_alpha_(red, green, blue, - 1.0) - self.tableview.setBackgroundColor_(color) - - def set_show_headers(self, show): - self._show_headers = show - if show: - self.tableview.setHeaderView_(self.header_view) - else: - self.tableview.setHeaderView_(None) - if self.viewport is not None: - self._remove_views() - self._do_layout() - self._add_views() - self.invalidate_size_request() - self.queue_redraw() - - def is_showing_headers(self): - return self._show_headers - - def set_search_column(self, model_index): - pass - - def try_to_set_row_height(self): - if len(self.model) > 0: - first_iter = self.model.first_iter() - height = calc_row_height(self.tableview, self.model[first_iter]) - self.tableview.setRowHeight_(height) - self.row_height_set = True - - def set_auto_resizes(self, setting): - self.auto_resize = setting - - def set_columns_draggable(self, dragable): - self.tableview.setAllowsColumnReordering_(dragable) - - def set_fixed_height(self, fixed): - if fixed: - self.fixed_height = True - delegate_class = TableViewDelegate - self.row_height_set = False - self.try_to_set_row_height() - else: - self.fixed_height = False - delegate_class = VariableHeightTableViewDelegate - self.delegate = delegate_class.alloc().init() - self.tableview.setDelegate_(self.delegate) - self.tableview.reloadData() - - def row_of_iter(self, iter): - return self.model.row_of_iter(self.tableview, iter) - - def set_context_menu_callback(self, callback): - self.context_menu_callback = callback - - # disable the drag when the cells are constantly updating. Mac OS X - # deals badly with this.. - def set_volatile(self, volatile): - if volatile: - self.data_source.setDragSource_(None) - self.data_source.setDragDest_(None) - else: - self.data_source.setDragSource_(self.drag_source) - self.data_source.setDragDest_(self.drag_dest) - - def set_drag_source(self, drag_source): - self.drag_source = drag_source - self.data_source.setDragSource_(drag_source) - - def set_drag_dest(self, drag_dest): - self.drag_dest = drag_dest - if drag_dest is None: - self.data_source.setDragDest_(None) - else: - types = drag_dest.allowed_types() - self.data_source.setDragDest_(drag_dest) diff --git a/mvc/widgets/osx/utils.py b/mvc/widgets/osx/utils.py deleted file mode 100644 index c0c2d85..0000000 --- a/mvc/widgets/osx/utils.py +++ /dev/null @@ -1,2 +0,0 @@ -def filename_to_unicode(filename): - return filename.decode('utf8') diff --git a/mvc/widgets/osx/viewport.py b/mvc/widgets/osx/viewport.py deleted file mode 100644 index e6564d4..0000000 --- a/mvc/widgets/osx/viewport.py +++ /dev/null @@ -1,101 +0,0 @@ -# @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. - -""".viewport.py -- Viewport classes - -A Viewport represents the area where a Widget is located. -""" - -from objc import YES, NO, nil -from Foundation import * - -class Viewport(object): - """Used when a widget creates it's own NSView.""" - def __init__(self, view, initial_frame): - self.view = view - self.view.setFrame_(initial_frame) - self.placement = initial_frame - - def at_position(self, rect): - """Check if a viewport is currently positioned at rect.""" - return self.placement == rect - - def reposition(self, rect): - """Move the viewport to a different position.""" - self.view.setFrame_(rect) - self.placement = rect - - def remove(self): - self.view.removeFromSuperview() - - def area(self): - """Area of our view that is occupied by the viewport.""" - return NSRect(self.view.bounds().origin, self.placement.size) - - def get_width(self): - return self.view.frame().size.width - - def get_height(self): - return self.view.frame().size.height - - def queue_redraw(self): - opaque_view = self.view.opaqueAncestor() - if opaque_view is not None: - rect = opaque_view.convertRect_fromView_(self.area(), self.view) - opaque_view.setNeedsDisplayInRect_(rect) - - def redraw_now(self): - self.view.displayRect_(self.area()) - -class BorrowedViewport(Viewport): - """Used when a widget uses the NSView of one of it's ancestors. We store - the view that we borrow as well as an NSRect specifying where on that view - we are placed. - """ - def __init__(self, view, placement): - self.view = view - self.placement = placement - - def at_position(self, rect): - return self.placement == rect - - def reposition(self, rect): - self.placement = rect - - def remove(self): - pass - - def area(self): - return self.placement - - def get_width(self): - return self.placement.size.width - - def get_height(self): - return self.placement.size.height diff --git a/mvc/widgets/osx/widgetset.py b/mvc/widgets/osx/widgetset.py deleted file mode 100644 index 1203566..0000000 --- a/mvc/widgets/osx/widgetset.py +++ /dev/null @@ -1,58 +0,0 @@ -# @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. - -""".widgetset -- Contains all the -platform-specific widgets. This module doesn't have any actual code in it, it -just imports the widgets from their actual locations. -""" - -from .const import * -from .control import (TextEntry, NumberEntry, - SecureTextEntry, MultilineTextEntry) -from .control import Checkbox, Button, OptionMenu, RadioButtonGroup, RadioButton -from .customcontrol import (CustomButton, - ContinuousCustomButton, CustomSlider, DragableCustomButton) -from .contextmenu import ContextMenu -from .drawing import DrawingContext, ImageSurface, Gradient -from .drawingwidgets import DrawingArea, Background -from .rect import Rect -from .layout import VBox, HBox, Alignment, Table, Scroller, Expander, TabContainer, DetachedWindowHolder -from .window import Window, MainWindow, Dialog, FileSaveDialog, FileOpenDialog -from .window import DirectorySelectDialog, AboutDialog, AlertDialog, PreferencesWindow, DonateWindow, DialogWindow, get_first_time_dialog_coordinates -from .simple import (Image, ImageDisplay, Label, - SolidBackground, ClickableImageButton, AnimatedImageDisplay, - ProgressBar, HLine) -from .tableview import (TableView, TableColumn, - CellRenderer, CustomCellRenderer, ImageCellRenderer, - CheckboxCellRenderer, - CUSTOM_HEADER_HEIGHT) -from .tablemodel import (TableModel, - TreeTableModel) -from .osxmenus import (MenuBar, Menu, Separator, MenuItem, RadioMenuItem, CheckMenuItem) -from .base import Widget diff --git a/mvc/widgets/osx/widgetupdates.py b/mvc/widgets/osx/widgetupdates.py deleted file mode 100644 index 30677c2..0000000 --- a/mvc/widgets/osx/widgetupdates.py +++ /dev/null @@ -1,72 +0,0 @@ -# @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. - -"""widgetupdates.py -- Handle updates to our widgets -""" - -from PyObjCTools import AppHelper - -class SizeRequestManager(object): - """Helper object to manage size requests - - If something changes in a widget that makes us want to request a new size, - we avoid calculating it immediately. The reason is that the - new-size-request will cascade all the way up the widget tree, and then - result in our widget being placed. We don't necessary want all of this - action to happen while we are in the middle of handling an event - (especially with TableView). It's also inefficient to calculate things - immediately, since we might do something else to invalidate the size - request in the current event. - - SizeRequestManager stores which widgets need to have their size - recalculated, then calls do_invalidate_size_request() using callAfter - """ - - def __init__(self): - self.widgets_to_request = set() - #app.widgetapp.connect("event-processed", self._on_event_processed) - - def add_widget(self, widget): - if len(self.widgets_to_request) == 0: - AppHelper.callAfter(self._run_requests) - self.widgets_to_request.add(widget) - - def _run_requests(self): - this_run = self.widgets_to_request - self.widgets_to_request = set() - for widget in this_run: - widget.do_invalidate_size_request() - - def _on_event_processed(self, app): - # once we finishing handling an event, process our size requests ASAP - # to avoid any potential weirdness. Note: that we also schedule a - # call using callAfter(), often that will do nothing, but it's - # possible size requests get scheduled outside of an event - while self.widgets_to_request: - self._run_requests() diff --git a/mvc/widgets/osx/window.py b/mvc/widgets/osx/window.py deleted file mode 100644 index b959333..0000000 --- a/mvc/widgets/osx/window.py +++ /dev/null @@ -1,896 +0,0 @@ -# @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 diff --git a/mvc/widgets/osx/wrappermap.py b/mvc/widgets/osx/wrappermap.py deleted file mode 100644 index 624a496..0000000 --- a/mvc/widgets/osx/wrappermap.py +++ /dev/null @@ -1,48 +0,0 @@ -# @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. - -""".wrappermap -- Map NSViews and NSWindows to the -Widget that wraps them. -""" - -# Maps NSViews/NSWinows -> wrapper objects. -wrapper_mapping = dict() - -def wrapper(wrapped): - """Find the wrapper object for an NSView/NSWindow.""" - try: - return wrapper_mapping[wrapped] - except KeyError: - return None - -def add(wrapped, wrapper): - wrapper_mapping[wrapped] = wrapper - -def remove(wrapped): - del wrapper_mapping[wrapped] |