aboutsummaryrefslogtreecommitdiffstats
path: root/mvc/widgets/gtk/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/gtk/layoutmanager.py
parenteb1896583afbbb622cadcde1a24e17173f61904f (diff)
downloadlibrevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.lz
librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.tar.xz
librevideoconverter-14738704ede6dfa6ac79f362a9c1f7f40f470cdc.zip
rename mvc at lvc
Diffstat (limited to 'mvc/widgets/gtk/layoutmanager.py')
-rw-r--r--mvc/widgets/gtk/layoutmanager.py550
1 files changed, 0 insertions, 550 deletions
diff --git a/mvc/widgets/gtk/layoutmanager.py b/mvc/widgets/gtk/layoutmanager.py
deleted file mode 100644
index fb60049..0000000
--- a/mvc/widgets/gtk/layoutmanager.py
+++ /dev/null
@@ -1,550 +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.
-
-"""drawing.py -- Contains the LayoutManager class. LayoutManager is
-handles laying out complex objects for the custom drawing code like
-text blocks and buttons.
-"""
-
-import math
-
-import cairo
-import gtk
-import pango
-
-from mvc import utils
-
-use_native_buttons = False # not implemented in MVC
-
-class FontCache(utils.Cache):
- def get(self, context, description, scale_factor, bold, italic):
- key = (context, description, scale_factor, bold, italic)
- return utils.Cache.get(self, key)
-
- def create_new_value(self, key, invalidator=None):
- (context, description, scale_factor, bold, italic) = key
- return Font(context, description, scale_factor, bold, italic)
-
-_font_cache = FontCache(512)
-
-class LayoutManager(object):
- def __init__(self, widget):
- self.pango_context = widget.get_pango_context()
- self.update_style(widget.style)
- self.update_direction(widget.get_direction())
- widget.connect('style-set', self.on_style_set)
- widget.connect('direction-changed', self.on_direction_changed)
- self.widget = widget
- self.reset()
-
- def reset(self):
- self.current_font = self.font(1.0)
- self.text_color = (0, 0, 0)
- self.text_shadow = None
-
- def on_style_set(self, widget, previous_style):
- old_font_desc = self.style_font_desc
- self.update_style(widget.style)
- if self.style_font_desc != old_font_desc:
- # bug #17423 font changed, so the widget's width might have changed
- widget.queue_resize()
-
- def on_direction_changed(self, widget, previous_direction):
- self.update_direction(widget.get_direction())
-
- def update_style(self, style):
- self.style_font_desc = style.font_desc
- self.style = style
-
- def update_direction(self, direction):
- if direction == gtk.TEXT_DIR_RTL:
- self.pango_context.set_base_dir(pango.DIRECTION_RTL)
- else:
- self.pango_context.set_base_dir(pango.DIRECTION_LTR)
-
- def font(self, scale_factor, bold=False, italic=False, family=None):
- return _font_cache.get(self.pango_context, self.style_font_desc,
- scale_factor, bold, italic)
-
- def set_font(self, scale_factor, bold=False, italic=False, family=None):
- self.current_font = self.font(scale_factor, bold, italic)
-
- def set_text_color(self, color):
- self.text_color = color
-
- def set_text_shadow(self, shadow):
- self.text_shadow = shadow
-
- def textbox(self, text, underline=False):
- textbox = TextBox(self.pango_context, self.current_font,
- self.text_color, self.text_shadow)
- textbox.set_text(text, underline=underline)
- return textbox
-
- def button(self, text, pressed=False, disabled=False, style='normal'):
- if style == 'webby':
- return StyledButton(text, self.pango_context, self.current_font,
- pressed, disabled)
- elif use_native_buttons:
- return NativeButton(text, self.pango_context, self.current_font,
- pressed, self.style, self.widget)
- else:
- return StyledButton(text, self.pango_context, self.current_font,
- pressed)
-
- def update_cairo_context(self, cairo_context):
- cairo_context.update_context(self.pango_context)
-
-class Font(object):
- def __init__(self, context, style_font_desc, scale, bold, italic):
- self.context = context
- self.description = style_font_desc.copy()
- self.description.set_size(int(scale * style_font_desc.get_size()))
- if bold:
- self.description.set_weight(pango.WEIGHT_BOLD)
- if italic:
- self.description.set_style(pango.STYLE_ITALIC)
- self.font_metrics = None
-
- def get_font_metrics(self):
- if self.font_metrics is None:
- self.font_metrics = self.context.get_metrics(self.description)
- return self.font_metrics
-
- def ascent(self):
- return pango.PIXELS(self.get_font_metrics().get_ascent())
-
- def descent(self):
- return pango.PIXELS(self.get_font_metrics().get_descent())
-
- def line_height(self):
- metrics = self.get_font_metrics()
- # the +1: some glyphs can be slightly taller than ascent+descent
- # (#17329)
- return (pango.PIXELS(metrics.get_ascent()) +
- pango.PIXELS(metrics.get_descent()) + 1)
-
-class TextBox(object):
- def __init__(self, context, font, color, shadow):
- self.layout = pango.Layout(context)
- self.layout.set_wrap(pango.WRAP_WORD_CHAR)
- self.font = font
- self.color = color
- self.layout.set_font_description(font.description.copy())
- self.width = self.height = None
- self.shadow = shadow
-
- def set_text(self, text, font=None, color=None, underline=False):
- self.text_chunks = []
- self.attributes = []
- self.text_length = 0
- self.underlines = []
- self.append_text(text, font, color, underline)
-
- def append_text(self, text, font=None, color=None, underline=False):
- if text == None:
- text = u""
- startpos = self.text_length
- self.text_chunks.append(text)
- endpos = self.text_length = self.text_length + len(text)
- if font is not None:
- attr = pango.AttrFontDesc(font.description, startpos, endpos)
- self.attributes.append(attr)
- if underline:
- self.underlines.append((startpos, endpos))
- if color:
- def convert(value):
- return int(round(value * 65535))
- attr = pango.AttrForeground(convert(color[0]), convert(color[1]),
- convert(color[2]), startpos, endpos)
- self.attributes.append(attr)
- self.text_set = False
-
- def set_width(self, width):
- if width is not None:
- self.layout.set_width(int(width * pango.SCALE))
- else:
- self.layout.set_width(-1)
- self.width = width
-
- def set_height(self, height):
- # if height is not None:
- # # not sure why set_height isn't in the python bindings, but it
- # # isn't
- # pygtkhacks.set_pango_layout_height(self.layout,
- # int(height * pango.SCALE))
- self.height = height
-
- def set_wrap_style(self, wrap):
- if wrap == 'word':
- self.layout.set_wrap(pango.WRAP_WORD_CHAR)
- elif wrap == 'char' or wrap == 'truncated-char':
- self.layout.set_wrap(pango.WRAP_CHAR)
- else:
- raise ValueError("Unknown wrap value: %s" % wrap)
- if wrap == 'truncated-char':
- self.layout.set_ellipsize(pango.ELLIPSIZE_END)
- else:
- self.layout.set_ellipsize(pango.ELLIPSIZE_NONE)
-
- def set_alignment(self, align):
- if align == 'left':
- self.layout.set_alignment(pango.ALIGN_LEFT)
- elif align == 'right':
- self.layout.set_alignment(pango.ALIGN_RIGHT)
- elif align == 'center':
- self.layout.set_alignment(pango.ALIGN_CENTER)
- else:
- raise ValueError("Unknown align value: %s" % align)
-
- def ensure_layout(self):
- if not self.text_set:
- text = ''.join(self.text_chunks)
- if len(text) > 100:
- text = text[:self._calc_text_cutoff()]
- self.layout.set_text(text)
- attr_list = pango.AttrList()
- for attr in self.attributes:
- attr_list.insert(attr)
- self.layout.set_attributes(attr_list)
- self.text_set = True
-
- def _calc_text_cutoff(self):
- """This method is a bit of a hack... GTK slows down if we pass too
- much text to the layout. Even text that falls below our height has a
- performance penalty. Try not to have too much more than is necessary.
- """
- if None in (self.width, self.height):
- return -1
-
- chars_per_line = (self.width * pango.SCALE //
- self.font.get_font_metrics().get_approximate_char_width())
- lines_available = self.height // self.font.line_height()
- # overestimate these because it's better to have too many characters
- # than too little.
- return int(chars_per_line * lines_available * 1.2)
-
- def line_count(self):
- self.ensure_layout()
- return self.layout.get_line_count()
-
- def get_size(self):
- self.ensure_layout()
- return self.layout.get_pixel_size()
-
- def char_at(self, x, y):
- self.ensure_layout()
- x *= pango.SCALE
- y *= pango.SCALE
- width, height = self.layout.get_size()
- if 0 <= x < width and 0 <= y < height:
- index, leading = self.layout.xy_to_index(x, y)
- # xy_to_index returns the nearest character, but that
- # doesn't mean the user actually clicked on it. Double
- # check that (x, y) is actually inside that char's
- # bounding box
- char_x, char_y, char_w, char_h = self.layout.index_to_pos(index)
- if char_w > 0: # the glyph is LTR
- left = char_x
- right = char_x + char_w
- else: # the glyph is RTL
- left = char_x + char_w
- right = char_x
- if left <= x < right:
- return index
- return None
-
-
- def draw(self, context, x, y, width, height):
- self.set_width(width)
- self.set_height(height)
- self.ensure_layout()
- cairo_context = context.context
- cairo_context.save()
- underline_drawer = UnderlineDrawer(self.underlines)
- if self.shadow:
- # draw shadow first so that it's underneath the regular text
- # FIXME: we don't use the blur_radius setting
- cairo_context.set_source_rgba(self.shadow.color[0],
- self.shadow.color[1], self.shadow.color[2],
- self.shadow.opacity)
- self._draw_layout(context, x + self.shadow.offset[0],
- y + self.shadow.offset[1], width, height,
- underline_drawer)
- cairo_context.set_source_rgb(*self.color)
- self._draw_layout(context, x, y, width, height, underline_drawer)
- cairo_context.restore()
- cairo_context.new_path()
-
- def _draw_layout(self, context, x, y, width, height, underline_drawer):
- line_height = 0
- alignment = self.layout.get_alignment()
- for i in xrange(self.layout.get_line_count()):
- line = self.layout.get_line_readonly(i)
- extents = line.get_pixel_extents()[1]
- next_line_height = line_height + extents[3]
- if next_line_height > height:
- break
- if alignment == pango.ALIGN_CENTER:
- line_x = max(x, x + (width - extents[2]) / 2.0)
- elif alignment == pango.ALIGN_RIGHT:
- line_x = max(x, x + width - extents[2])
- else:
- line_x = x
- baseline = y + line_height + pango.ASCENT(extents)
- context.move_to(line_x, baseline)
- context.context.show_layout_line(line)
- underline_drawer.draw(context, line_x, baseline, line)
- line_height = next_line_height
-
-class UnderlineDrawer(object):
- """Class to draw our own underlines because cairo's don't look
- that great at small fonts. We make sure that the underline is
- always drawn at a pixel boundary and that there always is space
- between the text and the baseline.
-
- This class makes a couple assumptions that might not be that
- great. It assumes that the correct underline size is 1 pixel and
- that the text color doesn't change in the middle of an underline.
- """
- def __init__(self, underlines):
- self.underline_iter = iter(underlines)
- self.finished = False
- self.next_underline()
-
- def next_underline(self):
- try:
- self.startpos, self.endpos = self.underline_iter.next()
- except StopIteration:
- self.finished = True
- else:
- # endpos is the char to stop underlining at
- self.endpos -= 1
-
- def draw(self, context, x, baseline, line):
- baseline = round(baseline) + 0.5
- context.set_line_width(1)
- while not self.finished and line.start_index <= self.startpos:
- startpos = max(line.start_index, self.startpos)
- endpos = min(self.endpos, line.start_index + line.length)
- x1 = x + pango.PIXELS(line.index_to_x(startpos, 0))
- x2 = x + pango.PIXELS(line.index_to_x(endpos, 1))
- context.move_to(x1, baseline + 1)
- context.line_to(x2, baseline + 1)
- context.stroke()
- if endpos < self.endpos:
- break
- else:
- self.next_underline()
-
-class NativeButton(object):
- ICON_PAD = 4
-
- def __init__(self, text, context, font, pressed, style, widget):
- self.layout = pango.Layout(context)
- self.font = font
- self.pressed = pressed
- self.layout.set_font_description(font.description.copy())
- self.layout.set_text(text)
- self.pad_x = style.xthickness + 11
- self.pad_y = style.ythickness + 1
- self.style = style
- self.widget = widget
- # The above code assumes an "inner-border" style property of
- # 1. PyGTK doesn't seem to support Border objects very well,
- # so can't get it from the widget style.
- self.min_width = 0
- self.icon = None
-
- def set_min_width(self, width):
- self.min_width = width
-
- def set_icon(self, icon):
- self.icon = icon
-
- def get_size(self):
- width, height = self.layout.get_pixel_size()
- if self.icon:
- width += self.icon.width + self.ICON_PAD
- height = max(height, self.icon.height)
- width += self.pad_x * 2
- height += self.pad_y * 2
- return max(self.min_width, width), height
-
- def draw(self, context, x, y, width, height):
- text_width, text_height = self.layout.get_pixel_size()
- if self.icon:
- inner_width = text_width + self.icon.width + self.ICON_PAD
- # calculate the icon position x and y are still in cairo
- # coordinates
- icon_x = x + (width - inner_width) / 2.0
- icon_y = y + (height - self.icon.height) / 2.0
- text_x = icon_x + self.icon.width + self.ICON_PAD
- else:
- text_x = x + (width - text_width) / 2.0
- text_y = y + (height - text_height) / 2.0
-
- x, y = context.context.user_to_device(x, y)
- text_x, text_y = context.context.user_to_device(text_x, text_y)
- # Hmm, maybe we should somehow support floating point numbers
- # here, but I don't know how to.
- x, y, width, height = (int(f) for f in (x, y, width, height))
- context.context.get_target().flush()
- self.draw_box(context.window, x, y, width, height)
- self.draw_text(context.window, text_x, text_y)
- if self.icon:
- self.icon.draw(context, icon_x, icon_y, self.icon.width,
- self.icon.height)
-
- def draw_box(self, window, x, y, width, height):
- if self.pressed:
- shadow = gtk.SHADOW_IN
- state = gtk.STATE_ACTIVE
- else:
- shadow = gtk.SHADOW_OUT
- state = gtk.STATE_NORMAL
- if 'QtCurveStyle' in str(self.style):
- # This is a horrible hack for the libqtcurve library. See
- # http://bugzilla.pculture.org/show_bug.cgi?id=10380 for
- # details
- widget = window.get_user_data()
- else:
- widget = self.widget
-
- self.style.paint_box(window, state, shadow, None, widget, "button",
- int(x), int(y), int(width), int(height))
-
- def draw_text(self, window, x, y):
- if self.pressed:
- state = gtk.STATE_ACTIVE
- else:
- state = gtk.STATE_NORMAL
- self.style.paint_layout(window, state, True, None, None, None,
- int(x), int(y), self.layout)
-
-class StyledButton(object):
- PAD_HORIZONTAL = 4
- PAD_VERTICAL = 3
- 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.184, 0.184, 0.184)
- DISABLED_COLOR = (0.86, 0.86, 0.86)
- DISABLED_TEXT_COLOR = (0.5, 0.5, 0.5)
- ICON_PAD = 8
-
- def __init__(self, text, context, font, pressed, disabled=False):
- self.layout = pango.Layout(context)
- self.font = font
- self.layout.set_font_description(font.description.copy())
- self.layout.set_text(text)
- self.min_width = 0
- self.pressed = pressed
- self.disabled = disabled
- self.icon = None
-
- def set_icon(self, icon):
- self.icon = icon
-
- def set_min_width(self, width):
- self.min_width = width
-
- def get_size(self):
- width, height = self.layout.get_pixel_size()
- if self.icon:
- width += self.icon.width + self.ICON_PAD
- height = max(height, self.icon.height)
- height += self.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 + height
- return max(self.min_width, width), height
-
- 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_button(self, context, x, y, width, height, radius):
- context.context.save()
- 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 = cairo.LinearGradient(x, y, x, y + height)
- gradient.add_color_stop_rgb(0, *start_color)
- gradient.add_color_stop_rgb(1, *end_color)
- context.context.set_source(gradient)
- context.fill()
- context.set_line_width(1)
- self.draw_path(context, x+0.5, y+0.5, width, height, radius)
- gradient = cairo.LinearGradient(x, y, x, y + height)
- gradient.add_color_stop_rgb(0, *self.LINE_COLOR_TOP)
- gradient.add_color_stop_rgb(1, *self.LINE_COLOR_BOTTOM)
- context.context.set_source(gradient)
- context.stroke()
- context.context.restore()
-
- def draw(self, context, x, y, width, height):
- radius = height / 2
- self.draw_button(context, x, y, width, height, radius)
-
- text_width, text_height = self.layout.get_pixel_size()
- # draw the text in the center of the button
- text_x = x + (width - text_width) / 2
- text_y = y + (height - text_height) / 2
- if self.icon:
- icon_x = text_x - (self.icon.width + self.ICON_PAD) / 2
- text_x += (self.icon.width + self.ICON_PAD) / 2
- icon_y = y + (height - self.icon.height) / 2
- self.icon.draw(context, icon_x, icon_y, self.icon.width,
- self.icon.height)
- self.draw_text(context, text_x, text_y, width, height, radius)
-
- def draw_text(self, context, x, y, width, height, radius):
- if self.disabled:
- context.set_color(self.DISABLED_TEXT_COLOR)
- else:
- context.set_color(self.TEXT_COLOR)
- context.move_to(x, y)
- context.context.show_layout(self.layout)