Blob Blame History Raw
# -*- 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