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 /mvc/widgets/gtk/layoutmanager.py | |
parent | eb1896583afbbb622cadcde1a24e17173f61904f (diff) | |
download | librevideoconverter-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.py | 550 |
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) |