# -*- coding: utf-8 -*- # # info.py - commander # # Copyright (C) 2010 - Jesse van den Kieboom # # 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 Street, Fifth Floor, # Boston, MA 02110-1301, USA. from gi.repository import Pango, Gdk, Gtk import math class ScrolledWindow(Gtk.ScrolledWindow): __gtype_name__ = "CommanderScrolledWindow" def __init__(self): Gtk.ScrolledWindow.__init__(self) self._max_height = 0 self._max_lines = 10 self.view = Gtk.TextView() self.view.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) self.view.set_editable(False) self.view.set_can_focus(False) self.view.connect('style-updated', self._on_style_updated) self._update_max_height() self.view.show() self.add(self.view) self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) def _update_max_height(self): layout = self.view.create_pango_layout('Some text to measure') extents = layout.get_pixel_extents() maxh = extents[1].height * self._max_lines if maxh != self._max_height: self._max_height = maxh self.queue_resize() def _on_style_updated(self, widget): self._update_max_height() def do_get_preferred_height(self): hp, vp = self.get_policy() ret = self.view.get_preferred_height() if vp == Gtk.PolicyType.NEVER and ret[0] > self._max_height: self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.ALWAYS) self.set_min_content_height(self._max_height) elif vp == Gtk.PolicyType.ALWAYS and ret[0] < self._max_height: self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) self.set_min_content_height(0) return Gtk.ScrolledWindow.do_get_preferred_height(self) class Info(Gtk.Box): __gtype_name__ = "CommanderInfo" def __init__(self): super(Info, self).__init__() self._button_bar = None self._status_label = None self._build_ui() def _build_ui(self): self.set_orientation(Gtk.Orientation.VERTICAL) self.set_spacing(3) self.set_can_focus(False) self._sw = ScrolledWindow() self._sw.set_border_width(6) css = Gtk.CssProvider() css.load_from_data(bytes(""" .trough { background: transparent; } """, 'utf-8')) self._sw.get_vscrollbar().get_style_context().add_provider(css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) self._sw.show() self.add(self._sw) self._attr_map = { Pango.AttrType.STYLE: ('style', Pango.AttrInt), Pango.AttrType.WEIGHT: ('weight', Pango.AttrInt), Pango.AttrType.VARIANT: ('variant', Pango.AttrInt), Pango.AttrType.STRETCH: ('stretch', Pango.AttrInt), Pango.AttrType.SIZE: ('size', Pango.AttrInt), Pango.AttrType.FOREGROUND: ('foreground', Pango.AttrColor), Pango.AttrType.BACKGROUND: ('background', Pango.AttrColor), Pango.AttrType.UNDERLINE: ('underline', Pango.AttrInt), Pango.AttrType.STRIKETHROUGH: ('strikethrough', Pango.AttrInt), Pango.AttrType.RISE: ('rise', Pango.AttrInt), Pango.AttrType.SCALE: ('scale', Pango.AttrFloat) } @property def text_view(self): return self._sw.view @property def is_empty(self): buf = self.text_view.get_buffer() return buf.get_start_iter().equal(buf.get_end_iter()) def status(self, text=None): if self._status_label == None and text != None: self._status_label = Gtk.Label() context = self.text_view.get_style_context() state = context.get_state() font_desc = context.get_font(state) self._status_label.override_font(font_desc) self._status_label.override_color(Gtk.StateFlags.NORMAL, context.get_color(Gtk.StateFlags.NORMAL)) self._status_label.show() self._status_label.set_alignment(0, 0.5) self._status_label.set_padding(10, 0) self._status_label.set_use_markup(True) self._status_label.set_halign(Gtk.Align.FILL) self._ensure_button_bar() self._button_bar.pack_start(self._status_label, True, True, 0) if text != None: self._status_label.set_markup(text) elif self._status_label: self._status_label.destroy() if not self._button_bar and self.is_empty: self.destroy() def _attr_to_tag(self, attr): buf = self.text_view.get_buffer() table = buf.get_tag_table() ret = [] tp = attr.klass.type if not tp in self._attr_map: return None propname, klass = self._attr_map[tp] # The hack! Set __class__ so we can access .value/.color, # then set __class__ back. This is expected to break at any time cls = attr.__class__ attr.__class__ = klass if tp == Pango.AttrType.FOREGROUND or tp == Pango.AttrType.BACKGROUND: value = attr.color else: value = attr.value attr.__class__ = cls tagname = str(tp) + ':' + str(value) tag = table.lookup(tagname) if not tag: tag = buf.create_tag(tagname) tag.set_property(propname, value) return tag def add_lines(self, line, use_markup=False): buf = self.text_view.get_buffer() if not buf.get_start_iter().equal(buf.get_end_iter()): line = "\n" + line if not use_markup: buf.insert(buf.get_end_iter(), line) return try: ret = Pango.parse_markup(line, -1, '\x00') except Exception as e: print('Could not parse markup:', e) buf.insert(buf.get_end_iter(), line) return text = ret[2] mark = buf.create_mark(None, buf.get_end_iter(), True) buf.insert(buf.get_end_iter(), text) attrs = [] ret[1].filter(lambda x: attrs.append(x)) for attr in attrs: # Try/catch everything since the _attr_to_tag stuff is a big # hack try: tag = self._attr_to_tag(attr) except: continue if not tag is None: start = buf.get_iter_at_mark(mark) end = start.copy() start.forward_chars(attr.start_index) end.forward_chars(attr.end_index) buf.apply_tag(tag, start, end) def add_action(self, name, callback, data=None): image = Gtk.Image.new_from_icon_name(name, Gtk.IconSize.MENU) image.show() self._ensure_button_bar() ev = Gtk.EventBox() ev.set_visible_window(True) ev.add(image) ev.show() ev.set_halign(Gtk.Align.END) self._button_bar.pack_end(ev, False, False, 0) ev.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.HAND2)) ev.connect('button-press-event', self._on_action_activate, callback, data) ev.connect('enter-notify-event', self._on_action_enter_notify) ev.connect('leave-notify-event', self._on_action_leave_notify) ev.connect_after('destroy', self._on_action_destroy) return ev def clear(self): self.text_view.get_buffer().set_text('') def _ensure_button_bar(self): if not self._button_bar: self._button_bar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=3) self._button_bar.show() self.pack_start(self._button_bar, False, False, 0) def _on_action_destroy(self, widget): if self._button_bar and len(self._button_bar.get_children()) == 0: self._button_bar.destroy() self._button_bar = None def _on_action_enter_notify(self, widget, evnt): img = widget.get_child() img.set_state(Gtk.StateType.PRELIGHT) def _on_action_leave_notify(self, widget, evnt): img = widget.get_child() img.set_state(Gtk.StateType.NORMAL) def _on_action_activate(self, widget, evnt, callback, data): if data: callback(data) else: callback() # ex:ts=4:et