aboutsummaryrefslogtreecommitdiffstats
path: root/mvc/widgets/osx/layoutmanager.py
diff options
context:
space:
mode:
authorJesús Eduardo <heckyel@hyperbola.info>2017-09-11 17:47:17 -0500
committerJesús Eduardo <heckyel@hyperbola.info>2017-09-11 17:47:17 -0500
commit14738704ede6dfa6ac79f362a9c1f7f40f470cdc (patch)
tree31c83bdd188ae7b64d7169974d6f066ccfe95367 /mvc/widgets/osx/layoutmanager.py
parenteb1896583afbbb622cadcde1a24e17173f61904f (diff)
downloadlibrevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.lz
librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.xz
librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.zip
rename mvc at lvc
Diffstat (limited to 'mvc/widgets/osx/layoutmanager.py')
-rw-r--r--mvc/widgets/osx/layoutmanager.py445
1 files changed, 0 insertions, 445 deletions
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))