diff options
author | Jesús Eduardo <heckyel@hyperbola.info> | 2017-09-11 17:47:17 -0500 |
---|---|---|
committer | Jesús Eduardo <heckyel@hyperbola.info> | 2017-09-11 17:47:17 -0500 |
commit | 14738704ede6dfa6ac79f362a9c1f7f40f470cdc (patch) | |
tree | 31c83bdd188ae7b64d7169974d6f066ccfe95367 /lvc/widgets/osx/base.py | |
parent | eb1896583afbbb622cadcde1a24e17173f61904f (diff) | |
download | librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.lz librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.xz librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.zip |
rename mvc at lvc
Diffstat (limited to 'lvc/widgets/osx/base.py')
-rw-r--r-- | lvc/widgets/osx/base.py | 367 |
1 files changed, 367 insertions, 0 deletions
diff --git a/lvc/widgets/osx/base.py b/lvc/widgets/osx/base.py new file mode 100644 index 0000000..30536aa --- /dev/null +++ b/lvc/widgets/osx/base.py @@ -0,0 +1,367 @@ +# @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 lvc 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 lvc.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 lvc.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) |