Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  performance-log-viewer.py   Sprache: Python

 

#!/usr/bin/env python3

"""
performance-log-viewer.py -- GIMP performance log viewer
Copyright (C) 2018  Ell

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 3 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, see <https://www.gnu.org/licenses/>.


Usage: performance-log-viewer.py < infile
"""

import builtins, sys, os, math, statistics, bisect, functools, enum, re, \
       subprocess

from collections import namedtuple
from xml.etree   import ElementTree

import gi
gi.require_version ("Gdk", "3.0")
gi.require_version ("Gtk", "3.0")
from gi.repository import GLib, GObject, Gio, Gdk, Gtk, Pango

def compose (head = None, *tail):
    return (
        lambda *args, **kwargs: head (compose (*tail) (*args, **kwargs))
    ) if tail else head or (lambda x: x)

def div (x, y):
    return x / y     if y     else \
           +math.inf if x > 0 else \
           -math.inf if x < 0 else \
           None

def format_float (x):
    return "%g" % (round (100 * x) / 100)

def format_percentage (x, digits = 0):
    return "%%.%df%%%%" % digits % (100 * x)

def format_size (size):
    return GLib.format_size_full (size, GLib.FormatSizeFlags.IEC_UNITS)

def format_duration (t):
    return "%02d:%02d:%02d.%02d" % (int (t / 3600),
                                    int (t / 60) % 60,
                                    int (t % 60),
                                    round (100 * t) % 100)

def format_color (color):
    return "#%02x%02x%02x" % tuple (
        map (lambda x: min (max (round (255 * x), 0), 255), color)
    )

def is_bright_color (color):
    return max (tuple (color)[0:3]) > 0.5

def blend_colors (color1, color2, amount):
    color1 = tuple (color1)
    color2 = tuple (color2)

    a1 = color1[-1]
    a2 = color2[-1]

    a = (1 - amount) * a1 + amount * a2

    return tuple (a and ((1 - amount) * a1 * c1 + amount * a2 * c2) / a
                  for c1, c2 in zip (color1[:-1], color2[:-1])) + (a,)

def rounded_rectangle (cr, x, y, width, height, radius):
    radius = min (radius, width / 2, height / 2)

    cr.arc (x + radius, y + radius, radius, -math.pi, -math.pi / 2)
    cr.rel_line_to (width - 2 * radius, 0)

    cr.arc (x + width - radius, y + radius, radius, -math.pi / 2, 0)
    cr.rel_line_to (0, height - 2 * radius)

    cr.arc (x + width - radius, y + height - radius, radius, 0, math.pi / 2)
    cr.rel_line_to (-(width - 2 * radius), 0)

    cr.arc (x + radius, y + height - radius, radius, math.pi / 2, math.pi)
    cr.rel_line_to (0, -(height - 2 * radius))

    cr.close_path ()

def get_basename (path):
    match = re.fullmatch (".*[\\\\/](.+?)[\\\\/]?", path)

    return match[1] if match else path

search_path = list (filter (
    bool,
    os.environ.get ("PERFORMANCE_LOG_VIEWER_PATH", ".").split (":")
))

editor_command  = os.environ.get ("PERFORMANCE_LOG_VIEWER_EDITOR",
                                  "xdg-open {file}")
editor_command += " &"

def find_file (filename):
    def lookup (filename):
        filename = re.sub ("[\\\\/]", GLib.DIR_SEPARATOR_S, filename)

        if GLib.path_is_absolute (filename):
            file = Gio.File.new_for_path (filename)

            if file.query_exists ():
                return file

        for path in search_path:
            rest = filename

            while rest:
                file = Gio.File.new_for_path (GLib.build_filenamev ((path, rest)))

                if file.query_exists ():
                    return file

                sep = rest.find (GLib.DIR_SEPARATOR_S)

                rest = rest[sep + 1:] if sep >= 0 else ""

        return None

    if filename not in find_file.cache:
        find_file.cache[filename] = lookup (filename)

    return find_file.cache[filename]

find_file.cache = {}

def run_editor (file, line):
    subprocess.call (editor_command.format (file = "\"%s\"" % file.get_path (),
                                            line = line),
                     shell = True)

VariableType = namedtuple ("VariableType",
                           ("parse", "format", "format_numeric"))

var_types = {
    "boolean": VariableType (
        parse          = int,
        format         = compose (str, bool),
        format_numeric = format_float
    ),

    "integer": VariableType (
        parse          = int,
        format         = format_float,
        format_numeric = None
    ),

    "size": VariableType (
        parse          = int,
        format         = format_size,
        format_numeric = None
    ),

    "size-ratio": VariableType (
        parse          = lambda x: div (*map (int, x.split ("/"))),
        format         = format_percentage,
        format_numeric = None
    ),

    "int-ratio": VariableType (
        parse          = lambda x: div (*map (int, x.split (":"))),
        format         = lambda x: "%g:%g" % (
                             (0, 0)                     if math.isnan (x) else
                             (1, 0)                     if x == math.inf  else
                             (-1, 0)                    if x == -math.inf else
                             (0, 1)                     if x == 0         else
                             (round (100 * x) / 100, 1) if abs (x) >  1   else
                             (1, round (100 / x) / 100)
                         ),
        format_numeric = None
    ),

    "percentage": VariableType (
        parse          = float,
        format         = format_percentage,
        format_numeric = None
    ),

    "duration": VariableType (
        parse          = float,
        format         = format_duration,
        format_numeric = None
    ),

    "rate-of-change": VariableType (
        parse          = float,
        format         = lambda x: "%s/s" % format_size (x),
        format_numeric = None
    )
}
var_types = {
    type: VariableType (
        parse          = parse,
        format         = lambda x, f = format: \
                             f (x) if x is not None else "N/A",
        format_numeric = lambda x, f = format_numeric or format:
                             f (x) if x is not None else "N/A"
    )
    for type, (parse, format, format_numeric) in var_types.items ()
}

# Read performance log from STDIN
log = ElementTree.fromstring (sys.stdin.buffer.read ())

Variable = namedtuple ("Variable", ("type", "desc", "color"))
Value    = namedtuple ("Value",    ("value", "raw"))

var_colors = [
    (0.8, 0.4, 0.4),
    (0.8, 0.6, 0.4),
    (0.4, 0.8, 0.4),
    (0.8, 0.8, 0.4),
    (0.4, 0.4, 0.8),
    (0.4, 0.8, 0.8),
    (0.8, 0.4, 0.8),
    (0.8, 0.8, 0.8)
]

var_defs = {}

for var in log.find ("var-defs"):
    color = var_colors[len (var_defs) % len (var_colors)]

    var_defs[var.get ("name")] = Variable (var.get ("type"),
                                           var.get ("desc"),
                                           color)

AddressInfo = namedtuple ("AddressInfo", ("id",
                                          "name",
                                          "object",
                                          "symbol",
                                          "offset",
                                          "source",
                                          "line"))

address_map = {}

if log.find ("address-map") is not None:
    for address in log.find ("address-map").iterfind ("address"):
        value = int (address.get ("value"), 0)

        object = address.find ("object").text
        symbol = address.find ("symbol").text
        base   = address.find ("base").text
        source = address.find ("source").text
        line   = address.find ("line").text

        address_map[value] = AddressInfo (
            id     = int (base, 0) if base else value,
            name   = symbol or base or hex (value),
            object = object,
            symbol = symbol,
            offset = value - int (base, 0) if base else None,
            source = source,
            line   = int (line) if line else None
        )

class ThreadState (enum.Enum):
    SUSPENDED = enum.auto ()
    RUNNING   = enum.auto ()

    def __str__ (self):
        return {
            ThreadState.SUSPENDED: "S",
            ThreadState.RUNNING:   "R"
        }[self]

Thread = namedtuple ("Thread", ("id", "name", "state", "frames"))
Frame  = namedtuple ("Frame",  ("id", "address", "info"))

Sample = namedtuple ("Sample", ("t", "vars", "markers", "backtrace"))
Marker = namedtuple ("Marker", ("id", "t", "description"))

samples     = []
markers     = []
last_marker = 0

for element in log.find ("samples"):
    if element.tag == "sample":
        sample = Sample (
            t         = int (element.get ("t")),
            vars      = {},
            markers   = markers[last_marker:],
            backtrace = []
        )

        for var in element.find ("vars"):
            sample.vars[var.tag] = Value (
                value = var_types[var_defs[var.tag].type].parse (var.text) \
                        if var.text else None,
                raw   = var.text.strip () if var.text else None
            )

        if element.find ("backtrace") is not None:
            for thread in element.find ("backtrace").iterfind ("thread"):
                id      = thread.get ("id")
                name    = thread.get ("name")
                running = thread.get ("running")

                t = Thread (
                    id     = int (id),
                    name   = name,
                    state  = ThreadState.RUNNING if running and int (running) \
                             else ThreadState.SUSPENDED,
                    frames = []
                )

                for frame in thread.iterfind ("frame"):
                    address = int (frame.get ("address"), 0)

                    info = address_map.get (address, None)

                    if not info:
                        info = AddressInfo (
                            id     = address,
                            name   = hex (address),
                            object = None,
                            symbol = None,
                            offset = None,
                            source = None,
                            line   = None
                        )

                    t.frames.append (Frame (
                        id      = len (t.frames),
                        address = address,
                        info    = info
                    ))

                sample.backtrace.append (t)

        samples.append (sample)

        last_marker = len (markers)
    elif element.tag == "marker":
        marker = Marker (
            id          = int (element.get ("id")),
            t           = int (element.get ("t")),
            description = element.text.strip () if element.text else None
        )

        markers.append (marker)

if samples:
    samples[-1].markers.extend (markers[last_marker:])

DELTA_SAME = __builtins__.object ()

def delta_encode (dest, src):
    if type (dest) == type (src):
        if dest == src:
            return DELTA_SAME
        elif type (dest) == tuple:
            return tuple (delta_encode (d, s) for d, s in zip (dest, src)) + \
                   dest[len (src):]

    return dest

def delta_decode (dest, src):
    if dest == DELTA_SAME:
        return src
    elif type (dest) == type (src):
        if type (dest) == tuple:
            return tuple (delta_decode (d, s) for d, s in zip (dest, src)) + \
                   dest[len (src):]

    return dest

class History (GObject.GObject):
    Source = namedtuple ("HistorySource", ("get", "set"))

    def __init__ (self):
        GObject.GObject.__init__ (self)

        self.sources = []

        self.state = None

        self.undo_stack = []
        self.redo_stack = []

        self.blocked         = 0
        self.n_groups        = 0
        self.pending_record  = False

    @GObject.Property (type = bool, default = False)
    def can_undo (self):
        return bool (self.undo_stack)

    @GObject.Property (type = bool, default = False)
    def can_redo (self):
        return bool (self.redo_stack)

    def add_source (self, get, set):
        self.sources.append (self.Source (get, set))

    def block (self):
        self.blocked += 1

    def unblock (self):
        self.blocked -= 1

    def is_blocked (self):
        return self.blocked > 0

    def start_group (self):
        self.n_groups += 1

    def end_group (self):
        self.n_groups -= 1

        if self.n_groups == 0 and self.pending_record:
            self.record ()

    def record (self):
        if self.is_blocked ():
            return

        if self.n_groups == 0:
            state = tuple (source.get () for source in self.sources)

            if self.state is None:
                self.state = state
            else:
                self.pending_record = False

                delta = delta_encode (self.state, state)

                if delta == DELTA_SAME:
                    return

                self.undo_stack.append (delta_encode (self.state, state))
                self.redo_stack = []

                self.state = state

                self.notify ("can-undo")
                self.notify ("can-redo")
        else:
            self.pending_record = True

    def update (self):
        if self.is_blocked ():
            return

        if self.n_groups == 0:
            state = tuple (source.get () for source in self.sources)

            for stack in self.undo_stack, self.redo_stack:
                if stack:
                    stack[-1] = delta_encode (delta_decode (stack[-1],
                                                            self.state),
                                              state)

            self.state = state
        else:
            self.pending_record = True

    def move (self, src, dest):
        self.block ()

        state = src.pop ()

        for source, substate, prev_substate in \
            zip (self.sources, self.state, state):
            if prev_substate != DELTA_SAME:
                source.set (delta_decode (prev_substate, substate))

        state = delta_decode (state, self.state)

        dest.append (delta_encode (self.state, state))

        self.state = state

        self.notify ("can-undo")
        self.notify ("can-redo")

        self.unblock ()

    def undo (self):
        self.move (self.undo_stack, self.redo_stack)

    def redo (self):
        self.move (self.redo_stack, self.undo_stack)

history = History ()

class SelectionOp (enum.Enum):
    REPLACE   = enum.auto ()
    ADD       = enum.auto ()
    SUBTRACT  = enum.auto ()
    INTERSECT = enum.auto ()
    XOR       = enum.auto ()

class Selection (GObject.GObject):
    __gsignals__ = {
        "changed":           (GObject.SignalFlags.RUN_FIRST, None, ()),
        "change-complete":   (GObject.SignalFlags.RUN_FIRST, None, ()),
        "highlight-changed": (GObject.SignalFlags.RUN_FIRST, None, ())
    }

    def __init__ (self, iter = ()):
        GObject.GObject.__init__ (self)

        self.selection  = set (iter)
        self.highlight  = None
        self.cursor     = None
        self.cursor_dir = 0

        self.pending_change_completion = False

    def __eq__ (self, other):
        return type (self)     == type (other)     and \
               self.selection  == other.selection  and \
               self.cursor     == other.cursor     and \
               self.cursor_dir == other.cursor_dir

    def __str__ (self):
        n_sel = len (self.selection)

        if n_sel == 0 or n_sel == len (samples):
            return "All Samples"
        elif n_sel == 1:
            i, = self.selection

            return "Sample %d" % i
        else:
            sel = list (self.selection)

            sel.sort ()

            if all (sel[i] + 1 == sel[i + 1] for i in range (n_sel - 1)):
                return "Samples %d–%d" % (sel[0], sel[-1])
            else:
                return "%d Samples" % n_sel

    def copy (self):
        selection = Selection ()

        selection.highlight  = self.highlight
        selection.cursor     = self.cursor
        selection.cursor_dir = self.cursor_dir
        selection.selection  = self.selection.copy ()

        return selection

    def get_effective_selection (self):
        if self.selection:
            return self.selection
        else:
            return set (range (len (samples)))

    def select (self, selection, op = SelectionOp.REPLACE):
        if op == SelectionOp.REPLACE:
            self.selection = selection.copy ()
        elif op == SelectionOp.ADD:
            self.selection |= selection
        elif op == SelectionOp.SUBTRACT:
            self.selection -= selection
        elif op == SelectionOp.INTERSECT:
            self.selection &= selection
        elif op == SelectionOp.XOR:
            self.selection.symmetric_difference_update (selection)

        if len (self.selection) == 1:
            (self.cursor,) = self.selection
        else:
            self.cursor = None

        self.cursor_dir = 0

        self.pending_change_completion = True

        self.emit ("changed")

    def select_range (self, first, last, op = SelectionOp.REPLACE):
        if first > last:
            temp  = first
            first = last
            last  = temp

        first = max (first, 0)
        last  = min (last, len (samples) - 1)

        if first <= last:
            self.select (set (range (first, last + 1)), op)
        else:
            self.select (set (), op)

    def clear (self):
        self.select (set ())

    def invert (self):
        self.select_range (0, len (samples), SelectionOp.XOR)

    def change_complete (self):
        if self.pending_change_completion:
            self.pending_change_completion = False

            history.start_group ()

            history.record ()

            self.emit ("change-complete")

            history.end_group ()

    def set_highlight (self, highlight):
        self.highlight = highlight

        self.emit ("highlight-changed")

    def source_get (self):
        return self.copy ()

    def source_set (self, selection):
        self.cursor     = selection.cursor
        self.cursor_dir = selection.cursor_dir
        self.selection  = selection.selection.copy ()

        self.emit ("changed")
        self.emit ("change-complete")

    def add_history_source (self):
        history.add_source (self.source_get, self.source_set)

selection = Selection ()
selection.add_history_source ()

class FindSamplesPopover (Gtk.Popover):
    def __init__ (self, *args, **kwargs):
        Gtk.Popover.__init__ (self, *args, **kwargs)

        vbox = Gtk.Box (orientation  = Gtk.Orientation.VERTICAL,
                        border_width = 20,
                        spacing      = 8)
        self.add (vbox)
        vbox.show ()

        entry = Gtk.Entry (width_chars      = 40,
                           placeholder_text = "Python expression")
        self.entry = entry
        vbox.pack_start (entry, False, False, 0)
        entry.show ()

        entry.connect ("activate", self.find_samples)

        entry.get_buffer ().connect (
            "notify::text",
            lambda *args: self.entry.get_style_context ().remove_class ("error")
        )

        frame = Gtk.Frame (label       = "Selection",
                           shadow_type = Gtk.ShadowType.NONE)
        vbox.pack_start (frame, False, False, 8)
        frame.get_label_widget ().get_style_context ().add_class ("dim-label")
        frame.show ()

        vbox2 = Gtk.Box (orientation  = Gtk.Orientation.VERTICAL,
                         border_width = 8,
                         spacing      = 8)
        frame.add (vbox2)
        vbox2.show ()

        self.radios = []

        radio = Gtk.RadioButton.new_with_mnemonic (None, "_Replace")
        self.radios.append ((radio, SelectionOp.REPLACE))
        vbox2.pack_start (radio, False, False, 0)
        radio.show ()

        radio = Gtk.RadioButton.new_with_mnemonic_from_widget (radio, "_Add")
        self.radios.append ((radio, SelectionOp.ADD))
        vbox2.pack_start (radio, False, False, 0)
        radio.show ()

        radio = Gtk.RadioButton.new_with_mnemonic_from_widget (radio, "_Subtract")
        self.radios.append ((radio, SelectionOp.SUBTRACT))
        vbox2.pack_start (radio, False, False, 0)
        radio.show ()

        radio = Gtk.RadioButton.new_with_mnemonic_from_widget (radio, "_Intersect")
        self.radios.append ((radio, SelectionOp.INTERSECT))
        vbox2.pack_start (radio, False, False, 0)
        radio.show ()

        button = Gtk.Button.new_with_mnemonic ("_Find")
        vbox.pack_start (button, False, False, 0)
        button.set_halign (Gtk.Align.CENTER)
        button.show ()

        button.connect ("clicked", self.find_samples)

    def do_hide (self):
        self.entry.set_text ("")
        self.entry.get_style_context ().remove_class ("error")

        Gtk.Popover.do_hide (self)

    def find_samples (self, *args):
        def var_name (var):
            return var.replace ("-", "_")

        try:
            f = eval ("lambda thread, function, %s: %s" % (
                ", ".join (map (var_name, var_defs)),
                self.entry.get_text ()))
        except:
            self.entry.get_style_context ().add_class ("error")

            return

        sel = set ()

        for i in range (len (samples)):
            try:
                def match_thread (thread, id, state = None):
                    return (id is None or                         \
                            (type (id) == int                 and \
                             id == thread.id)                 or  \
                            (type (id) == str                 and \
                             thread.name                      and \
                             re.fullmatch (id, thread.name))) and \
                           (state is None                     or  \
                            re.fullmatch (state, str (thread.state)))

                def thread (id, state = None):
                    return any (match_thread (thread, id, state)
                                for thread in samples[i].backtrace or [])

                def function (name, id = None, state = None):
                    for thread in samples[i].backtrace or []:
                        if match_thread (thread, id, state):
                            for frame in thread.frames:
                                if re.fullmatch (name, frame.info.name):
                                    return True

                    return False

                if f (thread, function, **{
                    var_name (var): value.value
                    for var, value in samples[i].vars.items ()
                }):
                    sel.add (i)
            except:
                pass

        op = [op for radio, op in self.radios if radio.get_active ()][0]

        selection.select (sel, op)

        selection.change_complete ()

        self.hide ()

class CellRendererColorToggle (Gtk.CellRendererToggle):
    padding = 3

    color = GObject.Property (type = Gdk.RGBA, default = Gdk.RGBA (0, 0, 0))

    def do_render (self, cr, widget, background_area, cell_area, flags):
        state    = widget.get_state_flags ()
        style    = widget.get_style_context ()
        fg_color = style.get_color (state)
        active   = self.get_property ("active")
        size     = max (min (cell_area.width, cell_area.height) -
                        2 * self.padding,
                        0)

        (r, g, b, a) = self.color

        if is_bright_color (fg_color):
            bg = (0.75 * r, 0.75 * g, 0.75 * b)
            fg = (r, g, b)
        else:
            bg = (r, g, b)
            fg = (0.75 * r, 0.75 * g, 0.75 * b)

        x = cell_area.x + (cell_area.width  - size) // 2
        y = cell_area.y + (cell_area.height - size) // 2

        if active:
            cr.rectangle (x, y, size, size)

            cr.set_source_rgba (*bg)
            cr.fill ()
        else:
            style.save ()

            style.set_state (Gtk.StateFlags (state & ~Gtk.StateFlags.SELECTED))
            Gtk.render_background (style, cr, x, y, size, size)

            style.restore ()

        cr.rectangle (x, y, size, size)

        cr.set_source_rgb (*fg)
        cr.set_line_width (2)
        cr.stroke ()

class VariableSet (Gtk.TreeView):
    class Store (Gtk.ListStore):
        NAME   = 0
        DESC   = 1
        COLOR  = 2
        ACTIVE = 3

        def __init__ (self):
            Gtk.ListStore.__init__ (self, str, str, Gdk.RGBA, bool)

            for var, var_def in var_defs.items ():
                i = self.append ((var,
                                  var_def.desc,
                                  Gdk.RGBA (*var_def.color),
                                  False))

    def __init__ (self, *args, **kwargs):
        Gtk.TreeView.__init__ (self, *args, headers_visible = False, **kwargs)

        store = self.Store ()
        self.store = store
        self.set_model (store)
        self.set_tooltip_column (store.DESC)

        col = Gtk.TreeViewColumn ()
        self.append_column (col)

        cell = CellRendererColorToggle ()
        col.pack_start (cell, False)
        col.add_attribute (cell, "active", store.ACTIVE)
        col.add_attribute (cell, "color", store.COLOR)

        cell.connect ("toggled", self.var_toggled)

        cell = Gtk.CellRendererText ()
        col.pack_start (cell, True)
        col.add_attribute (cell, "text", store.NAME)

    def var_toggled (self, cell, path):
        self.store[path][self.store.ACTIVE] = not cell.get_property ("active")

class SampleGraph (Gtk.DrawingArea):
    def __init__ (self, model = None, *args, **kwargs):
        Gtk.DrawingArea.__init__ (self, *args, can_focus = True, **kwargs)

        self.style_widget = Gtk.Entry ()

        self.model = model

        if model:
            model.connect ("row-changed", lambda *args: self.update ())

            self.update ()

        self.selection = None
        self.sel       = None

        selection.connect ("changed", self.selection_changed)
        selection.connect ("highlight-changed",
                           lambda selection: self.queue_draw ())

        self.add_events (Gdk.EventMask.BUTTON_PRESS_MASK   |
                         Gdk.EventMask.BUTTON_RELEASE_MASK |
                         Gdk.EventMask.KEY_PRESS_MASK      |
                         Gdk.EventMask.KEY_RELEASE_MASK)

        self.selection_changed (selection)

    def sample_to_x (self, i):
        if not samples:
            return None

        width     = self.get_allocated_width ()
        n_samples = max (len (samples), 2)

        return 1 + (width - 3) * i / (n_samples - 1)

    def sample_to_range (self, i):
        if not samples:
            return None

        width     = self.get_allocated_width ()
        n_samples = max (len (samples), 2)

        return (1 + math.floor ((width - 3) * (i - 0.5) / (n_samples - 1)),
                1 + math.ceil  ((width - 3) * (i + 0.5) / (n_samples - 1)))

    def x_to_sample (self, x):
        if not samples:
            return None

        width     = max (self.get_allocated_width (), 4)
        n_samples = len (samples)

        return round ((n_samples - 1) * (x - 1) / (width - 3))

    def update (self):
        if not samples or not self.model:
            return

        self.max_value = 1

        for row in self.model:
            var_name   = row[self.model.NAME]
            var_active = row[self.model.ACTIVE]

            if var_active:
                values = (sample.vars[var_name].value for sample in samples)
                values = filter (lambda x: x is not None, values)
                values = filter (math.isfinite,           values)

                try:
                    self.max_value = max (self.max_value, max (values))
                except:
                    pass

        self.queue_draw ()

    def selection_changed (self, selection):
        if selection.selection:
            self.sel = list (selection.selection)
            self.sel.sort ()
        else:
            self.sel = None

        self.queue_draw ()

    def do_get_preferred_width (self):
        return (300, 300)

    def do_get_preferred_height (self):
        if self.model:
            return (32, 256)
        else:
            return (16, 16)

    def update_selection (self):
        sel = self.selection.copy ()

        i0 = self.selection_i0
        i1 = self.selection_i1

        if self.selection_range:
            swap = i0 > i1

            if swap:
                temp = i0
                i0   = i1
                i1   = temp

            n_samples = len (samples)

            while i0 > 0             and not samples[i0 - 1].markers: i0 -= 1
            while i1 < n_samples - 1 and not samples[i1 + 1].markers: i1 += 1

            if swap:
                temp = i0
                i0   = i1
                i1   = temp

        sel.select_range (i0, i1, self.selection_op)

        selection.select (sel.selection)

        selection.cursor     = i1
        selection.cursor_dir = i1 - i0

    def do_button_press_event (self, event):
        state = event.state & Gdk.ModifierType.MODIFIER_MASK

        self.grab_focus ()

        if event.button == 1:
            i = self.x_to_sample (event.x)

            if i is None:
                return False

            self.selection       = selection.copy ()
            self.selection_i0    = i
            self.selection_i1    = i
            self.selection_op    = SelectionOp.REPLACE
            self.selection_range = event.type != Gdk.EventType.BUTTON_PRESS

            if state == Gdk.ModifierType.SHIFT_MASK:
                self.selection_op = SelectionOp.ADD
            elif state == Gdk.ModifierType.CONTROL_MASK:
                self.selection_op = SelectionOp.SUBTRACT
            elif state == (Gdk.ModifierType.SHIFT_MASK |
                           Gdk.ModifierType.CONTROL_MASK):
                self.selection_op = SelectionOp.INTERSECT

            self.update_selection ()

            self.grab_add ()
        elif event.button == 3:
            if state == 0:
                selection.clear ()
            elif state == Gdk.ModifierType.CONTROL_MASK:
                selection.invert ()

            self.grab_add ()

        return True

    def do_button_release_event (self, event):
        if event.button == 1 or event.button == 3:
            self.selection = None

            selection.change_complete ()

            self.grab_remove ()

            return True

        return False

    def do_motion_notify_event (self, event):
        i = self.x_to_sample (event.x)

        selection.set_highlight (i)

        if self.selection and i is not None:
            self.selection_i1 = i

            self.update_selection ()

            return True

        return False

    def do_leave_notify_event (self, event):
        selection.set_highlight (None)

        return False

    def do_key_press_event (self, event):
        if event.keyval == Gdk.KEY_Left    or \
           event.keyval == Gdk.KEY_Right   or \
           event.keyval == Gdk.KEY_Home    or \
           event.keyval == Gdk.KEY_KP_Home or \
           event.keyval == Gdk.KEY_End     or \
           event.keyval == Gdk.KEY_KP_End:
            n_samples = len (samples)

            state = event.state & Gdk.ModifierType.MODIFIER_MASK

            op = SelectionOp.REPLACE

            if state == Gdk.ModifierType.SHIFT_MASK:
                op = SelectionOp.XOR

            cursor     = selection.cursor
            cursor_dir = selection.cursor_dir

            if event.keyval == Gdk.KEY_Left or \
               event.keyval == Gdk.KEY_Home or \
               event.keyval == Gdk.KEY_KP_Home:
                if selection.cursor is not None:
                    if cursor_dir <= 0 or op == SelectionOp.REPLACE:
                        cursor -= 1
                else:
                    cursor = n_samples - 1

                cursor_dir = -1
            elif event.keyval == Gdk.KEY_Right or \
                 event.keyval == Gdk.KEY_End   or \
                 event.keyval == Gdk.KEY_KP_End:
                if cursor is not None:
                    if cursor_dir >= 0 or op == SelectionOp.REPLACE:
                        cursor += 1
                else:
                    cursor = 0

                cursor_dir = +1

            if cursor < 0 or cursor >= n_samples:
                cursor = min (max (cursor, 0), n_samples - 1)

                selection.cursor     = cursor
                selection.cursor_dir = cursor_dir

                if op != SelectionOp.REPLACE:
                    return True

            i0 = cursor

            if event.keyval == Gdk.KEY_Home or \
               event.keyval == Gdk.KEY_KP_Home:
                cursor = 0
            elif event.keyval == Gdk.KEY_End or \
                 event.keyval == Gdk.KEY_KP_End:
                cursor = n_samples - 1

            if op == SelectionOp.REPLACE:
                i0 = cursor

            selection.select_range (i0, cursor, op)

            if len (selection.selection) > 1:
                selection.cursor     = cursor
                selection.cursor_dir = cursor_dir

            return True
        elif event.keyval == Gdk.KEY_Escape:
            selection.select (set ())

            return True

        return False

    def do_key_release_event (self, event):
        selection.change_complete ()

        return False

    def do_draw (self, cr):
        state           = self.get_state_flags ()
        style           = (self.style_widget if self.model else
                           self).get_style_context ()
        (width, height) = (self.get_allocated_width  (),
                           self.get_allocated_height ())

        fg_color        = tuple (style.get_color (state))
        grid_color      = (*fg_color[:3], 0.25 * fg_color[3])
        highlight_color = grid_color
        selection_color = (*fg_color[:3], 0.15 * fg_color[3])

        Gtk.render_background (style, cr, 0, 0, width, height)

        if self.model:
            max_value = self.max_value
            vscale    = (height - 4) / max_value

            cr.save ()

            cr.translate (0, height - 2)
            cr.scale (1, -1)

            first_sample = True
            has_infinite = False

            for row in self.model:
                var_name   = row[self.model.NAME]
                var_active = row[self.model.ACTIVE]

                if var_active:
                    is_boolean    = var_defs[var_name].type == "boolean"
                    is_continuous = not is_boolean

                    for i in range (len (samples)):
                        value = samples[i].vars[var_name].value

                        if value is not None:
                            first_sample = False

                            if math.isinf (value):
                                first_sample = True
                                has_infinite = True

                                value = max_value
                            elif is_boolean:
                                value *= max_value

                            y = value * vscale

                            if is_continuous:
                                x = self.sample_to_x (i)

                                if first_sample:
                                    cr.move_to (x, y)
                                else:
                                    cr.line_to (x, y)
                            else:
                                (x0, x1) = self.sample_to_range (i)

                                if first_sample:
                                    cr.move_to (x0, y)
                                else:
                                    cr.line_to (x0, y)

                                cr.line_to (x1, y)
                        else:
                            first_sample = True

                    (r, g, b) = var_defs[var_name].color

                    cr.set_source_rgb (r, g, b)
                    cr.set_line_width (2)
                    cr.stroke ()

                    if has_infinite:
                        cr.save ()

                        for i in range (len (samples)):
                            value = samples[i].vars[var_name].value

                            if value is not None and math.isinf (value):
                                first_sample = False

                                y = max_value * vscale

                                if is_continuous:
                                    x = self.sample_to_x (i)

                                    if first_sample:
                                        cr.move_to (x, y)
                                    else:
                                        cr.line_to (x, y)
                                else:
                                    (x0, x1) = self.sample_to_range (i)

                                    if first_sample:
                                        cr.move_to (x0, y)
                                    else:
                                        cr.line_to (x0, y)

                                    cr.line_to (x1, y)
                            else:
                                first_sample = True

                        cr.set_dash ([6, 6], 0)
                        cr.stroke ()

                        cr.restore ()

            cr.restore ()

            cr.set_line_width (1)

            cr.set_source_rgba (*grid_color)

            n_hgrid_lines = 4
            n_vgrid_lines = 1

            for i in range (n_hgrid_lines + 1):
                cr.move_to (0, round (i * (height - 1) / n_hgrid_lines) + 0.5)
                cr.rel_line_to (width, 0)

                cr.stroke ()

            for i in range (n_vgrid_lines + 1):
                cr.move_to (round (i * (width - 1) / n_vgrid_lines) + 0.5, 0)
                cr.rel_line_to (0, height)

                cr.stroke ()
        else:
            for i in range (len (samples)):
                if samples[i].markers:
                    (x0, x1) = self.sample_to_range (i)

                    cr.rectangle (x0, 0, x1 - x0, height)

            cr.set_source_rgba (*fg_color)
            cr.fill ()

        if selection.highlight is not None:
            (x0, x1) = self.sample_to_range (selection.highlight)

            cr.rectangle (x0, 0, x1 - x0, height)

            (r, g, b, a) = style.get_color (state)

            cr.set_source_rgba (*highlight_color)
            cr.fill ()

        if self.sel:
            def draw_selection ():
                x0 = self.sample_to_range (i0)[0]
                x1 = self.sample_to_range (i1)[1]

                cr.rectangle (x0, 0, x1 - x0, height)

                (r, g, b, a) = style.get_color (state)

                cr.set_source_rgba (*selection_color)
                cr.fill ()

            i0 = None

            for i in self.sel:
                if i0 is None:
                    i0 = i
                    i1 = i
                elif i == i1 + 1:
                    i1 = i
                else:
                    draw_selection ()

                    i0 = i
                    i1 = i

            if i0 is not None:
                draw_selection ()

class SampleGraphList (Gtk.Box):
    Item = namedtuple (
        "SampleGraphListGraph", ("widget",
                                 "model",
                                 "remove_button",
                                 "move_up_button",
                                 "move_down_button")
    )

    def __init__ (self, *args, **kwargs):
        Gtk.Box.__init__ (self,
                          *args,
                          orientation = Gtk.Orientation.VERTICAL,
                          **kwargs)

        self.items = []

        self.vset_size_group = Gtk.SizeGroup (
            mode = Gtk.SizeGroupMode.HORIZONTAL
        )

        hbox = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
        self.pack_start (hbox, False, False, 0)
        hbox.show ()

        empty = Gtk.DrawingArea ()
        hbox.pack_start (empty, False, True, 0)
        self.vset_size_group.add_widget (empty)
        empty.show ()

        graph = SampleGraph (has_tooltip = True)
        hbox.pack_start (graph, True, True, 0)
        graph.show ()

        graph.connect ("query-tooltip", self.graph_query_tooltip)

        separator = Gtk.Separator (orientation = Gtk.Orientation.HORIZONTAL)
        self.pack_start (separator, False, False, 0)
        separator.show ()

        vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
        self.items_vbox = vbox
        self.pack_start (vbox, False, False, 0)
        vbox.show ()

        self.add_item (0)

    def update_items (self):
        for widget in self.items_vbox.get_children ():
            self.items_vbox.remove (widget)

        i = 0

        for item in self.items:
            if i > 0:
                separator = Gtk.Separator (
                    orientation = Gtk.Orientation.HORIZONTAL
                )
                self.items_vbox.pack_start (separator, False, False, 0)
                separator.show ()

            self.items_vbox.pack_start (item.widget, False, False, 0)

            item.remove_button.set_sensitive    (len (self.items) > 1)
            item.move_up_button.set_sensitive   (i > 0)
            item.move_down_button.set_sensitive (i < len (self.items) - 1)

            i += 1

    def add_item (self, i):
        hbox = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
        hbox.show ()

        vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
        hbox.pack_start (vbox, False, True, 0)
        self.vset_size_group.add_widget (vbox)
        vbox.show ()

        scroll = Gtk.ScrolledWindow (
            hscrollbar_policy = Gtk.PolicyType.NEVER,
            vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
        )
        vbox.pack_start (scroll, True, True, 0)
        scroll.show ()

        vset = VariableSet ()
        scroll.add (vset)
        vset.show ()

        buttons = Gtk.ButtonBox (orientation = Gtk.Orientation.HORIZONTAL)
        vbox.pack_start (buttons, False, False, 0)
        buttons.set_layout (Gtk.ButtonBoxStyle.EXPAND)
        buttons.show ()

        button = Gtk.Button.new_from_icon_name ("list-add-symbolic",
                                                Gtk.IconSize.BUTTON)
        add_button = button
        buttons.add (button)
        button.show ()

        button = Gtk.Button.new_from_icon_name ("list-remove-symbolic",
                                                Gtk.IconSize.BUTTON)
        remove_button = button
        buttons.add (button)
        button.show ()

        button = Gtk.Button.new_from_icon_name ("go-up-symbolic",
                                                Gtk.IconSize.BUTTON)
        move_up_button = button
        buttons.add (button)
        button.show ()

        button = Gtk.Button.new_from_icon_name ("go-down-symbolic",
                                                Gtk.IconSize.BUTTON)
        move_down_button = button
        buttons.add (button)
        button.show ()

        graph = SampleGraph (vset.get_model (), has_tooltip = True)
        hbox.pack_start (graph, True, True, 0)
        graph.show ()

        graph.connect ("query-tooltip", self.graph_query_tooltip)

        item = self.Item (
            widget           = hbox,
            model            = vset.get_model (),
            remove_button    = remove_button,
            move_up_button   = move_up_button,
            move_down_button = move_down_button
        )
        self.items.insert (i, item)

        add_button.connect       ("clicked",
                                  lambda *args: self.add_item (
                                                    self.items.index (item) + 1
                                                ))
        remove_button.connect    ("clicked",
                                  lambda *args: self.remove_item (
                                                    self.items.index (item)
                                                ))
        move_up_button.connect   ("clicked",
                                  lambda *args: self.move_item (
                                                    self.items.index (item),
                                                    -1
                                                ))
        move_down_button.connect ("clicked",
                                  lambda *args: self.move_item (
                                                    self.items.index (item),
                                                    +1
                                                ))

        self.update_items ()

    def remove_item (self, i):
        del self.items[i]

        self.update_items ()

    def move_item (self, i, offset):
        item = self.items[i]
        del self.items[i]
        self.items.insert (i + offset, item)

        self.update_items ()

    def graph_query_tooltip (self, graph, x, y, keyboard_mode, tooltip):
        if keyboard_mode:
            return False

        i = graph.x_to_sample (x)

        if i is None or i < 0 or i >= len (samples):
            return False

        grid = Gtk.Grid (column_spacing = 4)
        tooltip.set_custom (grid)
        grid.show ()

        row = 0

        label = Gtk.Label ()
        grid.attach (label, 0, row, 2, 1)
        label.set_markup ("<b>Sample %d</b>" % i)
        label.show ()

        row += 1

        label = Gtk.Label ()
        grid.attach (label, 0, row, 2, 1)
        label.set_markup ("<sub>%s</sub>" %
                          format_duration (samples[i].t / 1000000))
        label.get_style_context ().add_class ("dim-label")
        label.show ()

        row += 1

        for item in self.items:
            model = item.model

            vars = tuple (var[model.NAME] for var in model if var[model.ACTIVE])

            if not vars:
                continue

            separator = Gtk.Separator (orientation = Gtk.Orientation.HORIZONTAL)
            grid.attach (separator, 0, row, 2, 1)
            separator.show ()

            row += 1

            for var in vars:
                color = format_color (var_defs[var].color)

                label = Gtk.Label (halign = Gtk.Align.START)
                grid.attach (label, 0, row, 1, 1)
                label.set_markup (
                    "<span color=\"%s\"><b>%s</b></span>" % (color, var)
                )
                label.show ()

                value = samples[i].vars[var].value
                text  = var_types[var_defs[var].type].format (value) \
                        if value is not None else "N/A"

                label = Gtk.Label (label = text, halign = Gtk.Align.END)
                grid.attach (label, 1, row, 1, 1)
                label.show ()

                row += 1

        markers = samples[i].markers

        if markers:
            separator = Gtk.Separator (orientation = Gtk.Orientation.HORIZONTAL)
            grid.attach (separator, 0, row, 2, 1)
            separator.show ()

            row += 1

            for marker in markers:
                label = Gtk.Label (halign = Gtk.Align.START)
                grid.attach (label, 0, row, 1, 1)
                label.set_markup ("<b>Marker %d</b>" % (marker.id))
                label.show ()

                if marker.description:
                    label = Gtk.Label (label  = marker.description,
                                       halign = Gtk.Align.END)
                    grid.attach (label, 1, row, 1, 1)
                    label.show ()

                row += 1

        return True

class InformationViewer (Gtk.ScrolledWindow):
    class Store (Gtk.ListStore):
        NAME  = 0
        VALUE = 1

        def __init__ (self):
            Gtk.ListStore.__init__ (self, str, str)

    def __init__ (self, *args, **kwargs):
        Gtk.ScrolledWindow.__init__ (
            self,
            *args,
            hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
            vscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
            **kwargs
        )

        vbox = Gtk.Box (orientation  = Gtk.Orientation.VERTICAL,
                        border_width = 32,
                        margin_left  = 64,
                        margin_right = 64,
                        spacing      = 32)
        self.add (vbox)
        vbox.show ()

        def add_element (element):
            name    = {
                "params":       "Log Parameters",
                "gimp-version": "GIMP Version",
                "env":          "Environment",
                "gegl-config":  "GEGL Config"
            }.get (element.tag, element.tag)
            text    = element.text.strip ()
            n_items = len (element)

            if not text and n_items == 0:
                return

            vbox2 = Gtk.Box (orientation = Gtk.Orientation.VERTICAL,
                             spacing     = 16)
            vbox.pack_start (vbox2, False, False, 0)
            vbox2.show ()

            label = Gtk.Label (xalign = 0)
            vbox2.pack_start (label, False, False, 0)
            label.set_markup ("<b>%s</b>" % name)
            label.show ()

            frame = Gtk.Frame (shadow_type = Gtk.ShadowType.IN)
            vbox2.pack_start (frame, False, False, 0)
            frame.show ()

            if text:
                scrolled = Gtk.ScrolledWindow (
                    hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
                    vscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
                    height_request    = 400
                )
                frame.add (scrolled)
                scrolled.show ()

                text = Gtk.TextView (editable      = False,
                                     monospace     = True,
                                     wrap_mode     = Gtk.WrapMode.WORD,
                                     left_margin   = 16,
                                     right_margin  = 16,
                                     top_margin    = 16,
                                     bottom_margin = 16)
                scrolled.add (text)
                text.get_buffer ().set_text (element.text.strip (), -1)
                text.show ()
            else:
                scrolled = Gtk.ScrolledWindow (
                    hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
                    vscrollbar_policy = Gtk.PolicyType.NEVER
                )
                frame.add (scrolled)
                scrolled.show ()

                store = self.Store ()

                for item in element:
                    store.append ((item.tag, item.text.strip ()))

                tree = Gtk.TreeView (model = store)
                scrolled.add (tree)
                tree.show ()

                col = Gtk.TreeViewColumn (title = "Name")
                tree.append_column (col)

                cell = Gtk.CellRendererText ()
                col.pack_start (cell, False)
                col.add_attribute (cell, "text", store.NAME)

                col = Gtk.TreeViewColumn (title = "Value")
                tree.append_column (col)
                col.set_alignment (0.5)

                cell = Gtk.CellRendererText (xalign = 1)
                col.pack_start (cell, False)
                col.add_attribute (cell, "text", store.VALUE)

        params = log.find ("params")

        if params is not None:
            add_element (params)

        info = log.find ("info")

        if info is not None:
            for element in info:
                add_element (element)

class MarkersViewer (Gtk.ScrolledWindow):
    class Store (Gtk.ListStore):
        ID   = 0
        TIME = 1
        DESC = 2

        def __init__ (self):
            Gtk.ListStore.__init__ (self, int, GObject.TYPE_INT64, str)

            for marker in markers:
                self.append ((marker.id, marker.t, marker.description))

    def __init__ (self, *args, **kwargs):
        Gtk.Box.__init__ (self,
                          *args,
                          hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
                          vscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
                          **kwargs)

        self.needs_update = True

        store = self.Store ()
        self.store = store

        tree = Gtk.TreeView (model = store)
        self.tree = tree
        self.add (tree)
        tree.show ()

        tree.get_selection ().set_mode (Gtk.SelectionMode.MULTIPLE)

        self.tree_selection_changed_handler = tree.get_selection ().connect (
            "changed", self.tree_selection_changed
        )

        col = Gtk.TreeViewColumn (title = "#")
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText (xalign = 1)
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", store.ID)

        def format_time_col (tree_col, cell, model, iter, col):
            time = model[iter][col]

            cell.set_property ("text", format_duration (time / 1000000))

        col = Gtk.TreeViewColumn (title = "Time")
        tree.append_column (col)
        col.set_resizable (True)
        col.set_alignment (0.5)

        cell = Gtk.CellRendererText (xalign = 1)
        col.pack_start (cell, False)
        col.set_cell_data_func (cell, format_time_col, store.TIME)

        col = Gtk.TreeViewColumn (title = "Description")
        tree.append_column (col)
        col.set_resizable (True)
        col.set_alignment (0.5)

        cell = Gtk.CellRendererText ()
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", store.DESC)

        col = Gtk.TreeViewColumn ()
        tree.append_column (col)

        selection.connect ("change-complete", self.selection_change_complete)

    def update (self):
        markers = set ()

        if not self.needs_update:
            return

        self.needs_update = False

        for i in selection.selection:
            markers.update (marker.id for marker in samples[i].markers)

        tree_sel = self.tree.get_selection ()

        GObject.signal_handler_block (tree_sel,
                                      self.tree_selection_changed_handler)

        tree_sel.unselect_all ()

        for row in self.store:
            if row[self.store.ID] in markers:
                tree_sel.select_iter (row.iter)

        GObject.signal_handler_unblock (tree_sel,
                                        self.tree_selection_changed_handler)

    def do_map (self):
        self.update ()

        Gtk.ScrolledWindow.do_map (self)

    def selection_change_complete (self, selection):
        self.needs_update = True

        if self.get_mapped ():
            self.update ()

    def tree_selection_changed (self, tree_sel):
        sel = set ()

        for row in self.store:
            if tree_sel.iter_is_selected (row.iter):
                id = row[self.store.ID]

                for i in range (len (samples)):
                    if any (marker.id == id for marker in samples[i].markers):
                        sel.add (i)

        selection.select (sel)

        selection.change_complete ()

class VariablesViewer (Gtk.ScrolledWindow):
    class Store (Gtk.ListStore):
        NAME        =  0
        DESC        =  1
        COLOR       =  2
        VALUE       =  3
        RAW         =  4
        MIN         =  5
        MAX         =  6
        MEDIAN      =  7
        MEAN        =  8
        STDEV       =  9
        LAST_COLUMN = 10

        def __init__ (self):
            n_stats = self.LAST_COLUMN - self.COLOR

            Gtk.ListStore.__init__ (self,
                                    *((str, str, Gdk.RGBA) + n_stats * (str,)))

            for var, var_def in var_defs.items ():
                 self.append (((var,
                                var_def.desc,
                                Gdk.RGBA (*var_def.color)) +
                               n_stats * ("",)))

    def __init__ (self, *args, **kwargs):
        Gtk.Box.__init__ (self,
                          *args,
                          hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
                          vscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
                          **kwargs)

        self.needs_update = True

        store = self.Store ()
        self.store = store

        tree = Gtk.TreeView (model = store)
        self.add (tree)
        tree.set_tooltip_column (store.DESC)
        tree.show ()

        self.single_sample_cols = []
        self.multi_sample_cols  = []

        col = Gtk.TreeViewColumn (title = "Variable")
        tree.append_column (col)
        col.set_resizable (True)

        cell = CellRendererColorToggle (active = True)
        col.pack_start (cell, False)
        col.add_attribute (cell, "color", store.COLOR)

        cell = Gtk.CellRendererText ()
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", store.NAME)

        def add_value_column (title, column, single_sample):
            col = Gtk.TreeViewColumn (title = title)
            tree.append_column (col)
            col.set_resizable (True)
            col.set_alignment (0.5)

            cell = Gtk.CellRendererText (xalign = 1)
            col.pack_start (cell, False)
            col.add_attribute (cell, "text", column)

            if single_sample:
                self.single_sample_cols.append (col)
            else:
                self.multi_sample_cols.append (col)

        add_value_column ("Value",     store.VALUE,  True)
        add_value_column ("Raw",       store.RAW,    True)
        add_value_column ("Min",       store.MIN,    False)
        add_value_column ("Max",       store.MAX,    False)
        add_value_column ("Median",    store.MEDIAN, False)
        add_value_column ("Mean",      store.MEAN,   False)
        add_value_column ("Std. Dev.", store.STDEV,  False)

        col = Gtk.TreeViewColumn ()
        tree.append_column (col)

        selection.connect ("change-complete", self.selection_change_complete)

    def update (self):
        if not self.needs_update:
            return

        self.needs_update = False

        sel   = selection.get_effective_selection ()
        n_sel = len (sel)

        if n_sel == 1:
            i, = sel

            for row in self.store:
                var_name = row[self.store.NAME]

                var      = samples[i].vars[var_name]
                var_type = var_types[var_defs[var_name].type]

                row[self.store.VALUE] = var_type.format (var.value)
                row[self.store.RAW]   = var.raw if var.raw is not None \
                                        else "N/A"
        else:
            for row in self.store:
                var_name = row[self.store.NAME]

                var_type = var_types[var_defs[var_name].type]

                vals = (samples[i].vars[var_name].value for i in sel)
                vals = tuple (val for val in vals if val is not None)

                if vals:
                    min_val = min               (vals)
                    max_val = max               (vals)
                    median  = statistics.median (vals)
                    mean    = statistics.mean   (vals)
                    stdev   = statistics.pstdev (vals, mean)

                    row[self.store.MIN]    = var_type.format         (min_val)
                    row[self.store.MAX]    = var_type.format         (max_val)
                    row[self.store.MEDIAN] = var_type.format         (median)
                    row[self.store.MEAN]   = var_type.format_numeric (mean)
                    row[self.store.STDEV]  = var_type.format_numeric (stdev)
                else:
                    row[self.store.MIN]    = \
                    row[self.store.MAX]    = \
                    row[self.store.MEDIAN] = \
                    row[self.store.MEAN]   = \
                    row[self.store.STDEV]  = var_type.format (None)

        for col in self.single_sample_cols: col.set_visible (n_sel == 1)
        for col in self.multi_sample_cols:  col.set_visible (n_sel > 1)

    def do_map (self):
        self.update ()

        Gtk.ScrolledWindow.do_map (self)

    def selection_change_complete (self, selection):
        self.needs_update = True

        if self.get_mapped ():
            self.update ()

class BacktraceViewer (Gtk.Box):
    class ThreadStore (Gtk.ListStore):
        INDEX = 0
        ID    = 1
        NAME  = 2
        STATE = 3

        def __init__ (self):
            Gtk.ListStore.__init__ (self, int, int, str, str)

    class FrameStore (Gtk.ListStore):
        ID       = 0
        ADDRESS  = 1
        OBJECT   = 2
        FUNCTION = 3
        OFFSET   = 4
        SOURCE   = 5
        LINE     = 6

        def __init__ (self):
            Gtk.ListStore.__init__ (self, int, str, str, str, str, str, str)

    class CellRendererViewSource (Gtk.CellRendererPixbuf):
        file = GObject.Property (type = Gio.File, default = None)
        line = GObject.Property (type = int,      default = 0)

        def __init__ (self, *args, **kwargs):
            Gtk.CellRendererPixbuf.__init__ (
                self,
                *args,
                icon_name = "text-x-generic-symbolic",
                mode      = Gtk.CellRendererMode.ACTIVATABLE,
                **kwargs)

            self.connect ("notify::file",
                          lambda *args:
                              self.set_property ("visible", bool (self.file)))

        def do_activate (self, event, widget, path, *args):
            if self.file:
                run_editor (self.file, self.line)

                return True

            return False

    def __init__ (self, *args, **kwargs):
        Gtk.Box.__init__ (self,
                          *args,
                          orientation = Gtk.Orientation.HORIZONTAL,
                          **kwargs)

        self.needs_update = True

        vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
        self.pack_start (vbox, False, False, 0)
        vbox.show ()

        header = Gtk.HeaderBar (title = "Threads", has_subtitle = False)
        vbox.pack_start (header, False, False, 0)
        header.show ()

        scrolled = Gtk.ScrolledWindow (
            hscrollbar_policy = Gtk.PolicyType.NEVER,
            vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
        )
        vbox.pack_start (scrolled, True, True, 0)
        scrolled.show ()

        store = self.ThreadStore ()
        self.thread_store = store
        store.set_sort_column_id (store.ID, Gtk.SortType.ASCENDING)

        tree = Gtk.TreeView (model = store)
        self.thread_tree = tree
        scrolled.add (tree)
        tree.set_search_column (store.NAME)
        tree.show ()

        tree.connect ("row-activated", self.threads_row_activated)

        tree.get_selection ().connect ("changed",
                                       self.threads_selection_changed)

        col = Gtk.TreeViewColumn (title = "ID")
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText (xalign = 1)
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", self.ThreadStore.ID)

        col = Gtk.TreeViewColumn (title = "Name")
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText ()
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", self.ThreadStore.NAME)

        col = Gtk.TreeViewColumn (title = "State")
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText ()
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", self.ThreadStore.STATE)

        separator = Gtk.Separator (orientation = Gtk.Orientation.VERTICAL)
        self.pack_start (separator, False, False, 0)
        separator.show ()

        vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
        self.pack_start (vbox, True, True, 0)
        vbox.show ()

        header = Gtk.HeaderBar (title = "Stack", has_subtitle = False)
        vbox.pack_start (header, False, False, 0)
        header.show ()

        scrolled = Gtk.ScrolledWindow (
            hscrollbar_policy = Gtk.PolicyType.AUTOMATIC,
            vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
        )
        vbox.pack_start (scrolled, True, True, 0)
        scrolled.show ()

        store = self.FrameStore ()
        self.frame_store = store

        tree = Gtk.TreeView (model = store, has_tooltip = True)
        scrolled.add (tree)
        tree.set_search_column (store.FUNCTION)
        tree.show ()

        tree.connect ("row-activated", self.frames_row_activated)
        tree.connect ("query-tooltip", self.frames_query_tooltip)

        def format_filename_col (tree_col, cell, model, iter, col):
            object = model[iter][col]

            cell.set_property ("text", get_basename (object) if object else "")

        self.tooltip_columns = {}

        col = Gtk.TreeViewColumn (title = "#")
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText (xalign = 1)
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", self.FrameStore.ID)

        col = Gtk.TreeViewColumn (title = "Address")
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText (xalign = 1)
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", self.FrameStore.ADDRESS)

        col = Gtk.TreeViewColumn (title = "Object")
        self.tooltip_columns[col] = store.OBJECT
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText ()
        col.pack_start (cell, False)
        col.set_cell_data_func (cell, format_filename_col, store.OBJECT)

        col = Gtk.TreeViewColumn (title = "Function")
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText ()
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", self.FrameStore.FUNCTION)

        col = Gtk.TreeViewColumn (title = "Offset")
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText (xalign = 1)
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", self.FrameStore.OFFSET)

        col = Gtk.TreeViewColumn (title = "Source")
        self.tooltip_columns[col] = store.SOURCE
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText ()
        col.pack_start (cell, False)
        col.set_cell_data_func (cell, format_filename_col, store.SOURCE)

        col = Gtk.TreeViewColumn (title = "Line")
        tree.append_column (col)
        col.set_resizable (True)

        cell = Gtk.CellRendererText (xalign = 1)
        col.pack_start (cell, False)
        col.add_attribute (cell, "text", self.FrameStore.LINE)

        def format_view_source_col (tree_col, cell, model, iter, cols):
            filename = model[iter][cols[0]] or None
            line     = model[iter][cols[1]] or "0"

            cell.set_property ("file", filename and find_file (filename))
            cell.set_property ("line", int (line))

        def format_view_source_tooltip (row):
            filename = row[store.SOURCE]

            if filename:
                file = find_file (filename)

                if file:
                    return file.get_path ()

            return None

        col = Gtk.TreeViewColumn ()
        self.tooltip_columns[col] = format_view_source_tooltip
        tree.append_column (col)

        cell = self.CellRendererViewSource (xalign = 0)
        col.pack_start (cell, False)
        col.set_cell_data_func (cell, format_view_source_col, (store.SOURCE,
                                                               store.LINE))

        selection.connect ("change-complete", self.selection_change_complete)

    @GObject.Property (type = bool, default = False)
    def available (self):
        sel = selection.get_effective_selection ()

        if len (sel) == 1:
            i, = sel

            return bool (samples[i].backtrace)

        return False

    def update (self):
        if not self.needs_update or not self.available:
            return

        self.needs_update = False

        tid = None

        sel_rows = self.thread_tree.get_selection ().get_selected_rows ()[1]

        if sel_rows:
            tid = self.thread_store[sel_rows[0]][self.ThreadStore.ID]

        i, = selection.get_effective_selection ()

        self.thread_store.clear ()

        for t in range (len (samples[i].backtrace)):
            thread = samples[i].backtrace[t]

            iter = self.thread_store.append (
                (t, thread.id, thread.name, str (thread.state))
            )

            if thread.id == tid:
                self.thread_tree.get_selection ().select_iter (iter)

    def do_map (self):
        self.update ()

        Gtk.Box.do_map (self)

    def selection_change_complete (self, selection):
        self.needs_update = True

        if self.get_mapped ():
            self.update ()

        self.notify ("available")

    def threads_row_activated (self, tree, path, col):
        iter = self.thread_store.get_iter (path)

        tid = self.thread_store[iter][self.ThreadStore.ID]

        sel = set ()

        for i in range (len (samples)):
            threads = filter (lambda thread:
                                thread.id    == tid and
                                thread.state == ThreadState.RUNNING,
                              samples[i].backtrace or [])

            if list (threads):
                sel.add (i)

        selection.select (sel)

        selection.change_complete ()

    def threads_selection_changed (self, tree_sel):
        self.frame_store.clear ()

        (store, rows) = tree_sel.get_selected_rows ()

        if not rows:
            return

        i, = selection.get_effective_selection ()

        try:
            frames = samples[i].backtrace[store[rows[0]][store.INDEX]].frames

            for frame in frames:
                info = frame.info

                self.frame_store.append ((
                    frame.id, hex (frame.address), info.object, info.symbol,
                    hex (info.offset) if info.offset is not None else None,
                    info.source, str (info.line) if info.line else None
                ))
        except:
            pass

    def frames_row_activated (self, tree, path, col):
        iter = self.frame_store.get_iter (path)

        address = int (self.frame_store[iter][self.FrameStore.ADDRESS], 0)
        info    = address_map.get (address, None)

        if not info:
            return

        id = info.id

        if not id:
            return

        sel = set ()

        def has_frame (sample, id):
            for thread in sample.backtrace or []:
                for frame in thread.frames:
                    if frame.info.id == id:
                        return True

            return False

        for i in range (len (samples)):
            if has_frame (samples[i], id):
                sel.add (i)

        selection.select (sel)

        selection.change_complete ()

    def frames_query_tooltip (self, tree, x, y, keyboard_mode, tooltip):
        hit, x, y, model, path, iter =  tree.get_tooltip_context (x, y,
                                                                  keyboard_mode)

        if hit:
            column = None

            if keyboard_mode:
                cursor_path, cursor_col = tree.get_cursor ()

                if path.compare (cursor_path) == 0:
                    column = self.tooltip_columns[cursor_col]
            else:
                for col in self.tooltip_columns:
                    area = tree.get_cell_area (path, col)

                    if x >= area.x and x < area.x + area.width and \
                       y >= area.y and y < area.y + area.height:
                        column = self.tooltip_columns[col]

                        break

            if column is not None:
                value = None

                if type (column) == int:
                    value = model[iter][column]
                else:
                    value = column (model[iter])

                if value:
                    tooltip.set_text (str (value))

                    return True

        return False

class CellRendererPercentage (Gtk.CellRendererText):
    padding = 0

    def __init__ (self, *args, **kwargs):
        Gtk.CellRendererText.__init__ (self, *args, xalign = 1, **kwargs)

        self.value = 0

    @GObject.Property (type = float)
    def value (self):
        return self.value_property

    @value.setter
    def value (self, value):
        self.value_property = value

        self.set_property ("text", format_percentage (value, 2))

    def do_render (self, cr, widget, background_area, cell_area, flags):
        full_width  = cell_area.width  - 2 * self.padding
        full_height = cell_area.height - 2 * self.padding

        if full_width <= 0 or full_height <= 0:
            return

        state    = widget.get_state_flags()
        style    = widget.get_style_context ()
        fg_color = style.get_color (state)

        rounded_rectangle (cr,
                           cell_area.x + self.padding,
                           cell_area.y + self.padding,
                           full_width,
                           full_height,
                           1)

        cr.clip ()

        cr.set_source_rgba (*blend_colors ((0, 0, 0, 0), fg_color, 0.2))
        cr.paint ()

        Gtk.CellRendererText.do_render (self,
                                        cr, widget,
                                        background_area, cell_area,
                                        flags)

        value  = min (max (self.value, 0), 1)
        width  = round (full_width * value)
        height = full_height

        if width > 0 and height > 0:
            state = Gtk.StateFlags        (state |
                                           Gtk.StateFlags.SELECTED)
            flags = Gtk.CellRendererState (flags |
                                           Gtk.CellRendererState.SELECTED)

            style.save ()
            style.set_state (state)

            x = round ((full_width - width) * self.get_property ("xalign"))

            cr.rectangle (cell_area.x + self.padding + x,
                          cell_area.y + self.padding,
                          width,
                          height)

            cr.clip ()

            Gtk.render_background (style, cr,
                                   cell_area.x,     cell_area.y,
                                   cell_area.width, cell_area.height)

            cr.set_source_rgba (0, 0, 0, 0.25)
            cr.paint ()

            Gtk.CellRendererText.do_render (self,
                                            cr, widget,
                                            background_area, cell_area,
                                            flags)

            style.restore ()

class ProfileViewer (Gtk.ScrolledWindow):
    class ThreadFilter (Gtk.TreeView):
        class Store (Gtk.ListStore):
            VISIBLE = 0
            ID      = 1
            NAME    = 2
            STATE   = {list (ThreadState)[i]: 3 + i
                       for i in range (len (ThreadState))}

            def __init__ (self):
                Gtk.ListStore.__init__ (self,
                                        bool, int, str,
                                        *(len (self.STATE) * (bool,)))

                threads = list ({thread.id
                                 for sample in samples
                                 for thread in sample.backtrace or ()})
                threads.sort ()

                states = [state == ThreadState.RUNNING for state in self.STATE]

                for id in threads:
                    self.append ((False, id, None, *states))

            def get_filter (self):
                return {row[self.ID]: {state
                                       for state, column in self.STATE.items ()
                                       if row[column]}
                        for row in self}

            def set_filter (self, filter):
                for row in self:
                    states = filter[row[self.ID]]

                    for state, column in self.STATE.items ():
                        row[column] = state in states

        def __init__ (self, *args, **kwargs):
            Gtk.TreeView.__init__ (self, *args, **kwargs)

            self.needs_update = True

            store = self.Store ()
            self.store = store

            filter = Gtk.TreeModelFilter (child_model = store)
            filter.set_visible_column (store.VISIBLE)
            self.set_model (filter)
            self.set_search_column (store.NAME)

            col = Gtk.TreeViewColumn (title = "ID")
            self.append_column (col)

            cell = Gtk.CellRendererText (xalign = 1)
            col.pack_start (cell, False)
            col.add_attribute (cell, "text", store.ID)

            col = Gtk.TreeViewColumn (title = "Name")
            self.append_column (col)

            cell = Gtk.CellRendererText ()
            col.pack_start (cell, False)
            col.add_attribute (cell, "text", store.NAME)

            for state in store.STATE:
                col = Gtk.TreeViewColumn (title = str (state))
                col.column = store.STATE[state]
                self.append_column (col)
                col.set_alignment (0.5)
                col.set_clickable (True)

                def col_clicked (col):
                    active = not all (row[col.column] for row in filter)

                    for row in filter:
                        row[col.column] = active

                col.connect ("clicked", col_clicked)

                cell = Gtk.CellRendererToggle ()
                cell.column = store.STATE[state]
                col.pack_start (cell, False)
                col.add_attribute (cell, "active", store.STATE[state])

                def cell_toggled (cell, path):
                    filter[path][cell.column] = not cell.get_property ("active")

                cell.connect ("toggled", cell_toggled)

            selection.connect ("change-complete",
                               self.selection_change_complete)

        def update (self):
            if not self.needs_update:
                return

            self.needs_update = False

            sel = selection.get_effective_selection ()

            threads = {thread.id: thread.name
                       for i in sel
                       for thread in samples[i].backtrace or ()}

            for row in self.store:
                id = row[self.store.ID]

                if id in threads:
                    row[self.store.VISIBLE] = True
                    row[self.store.NAME]    = threads[id]
                else:
                    row[self.store.VISIBLE] = False
                    row[self.store.NAME]    = None

        def do_map (self):
            self.update ()

            Gtk.TreeView.do_map (self)

        def selection_change_complete (self, selection):
            self.needs_update = True

            if self.get_mapped ():
                self.update ()

    class ThreadPopover (Gtk.Popover):
        def __init__ (self, *args, **kwargs):
            Gtk.Popover.__init__ (self, *args, border_width = 4, **kwargs)

            frame = Gtk.Frame (shadow_type = Gtk.ShadowType.IN)
            self.add (frame)
            frame.show ()

            scrolled = Gtk.ScrolledWindow (
                hscrollbar_policy        = Gtk.PolicyType.NEVER,
                vscrollbar_policy        = Gtk.PolicyType.AUTOMATIC,
                propagate_natural_height = True,
                max_content_height       = 400
            )
            frame.add (scrolled)
            scrolled.show ()

            thread_filter = ProfileViewer.ThreadFilter ()
            self.thread_filter = thread_filter
            scrolled.add (thread_filter)
            thread_filter.show ()

    class Profile (Gtk.Box):
        ProfileFrame = namedtuple ("ProfileFrame", ("sample", "stack", "i"))

        class Direction (enum.Enum):
            CALLEES = enum.auto ()
            CALLERS = enum.auto ()

        class Store (Gtk.ListStore):
            ID        = 0
            FUNCTION  = 1
            EXCLUSIVE = 2
            INCLUSIVE = 3

            def __init__ (self):
                Gtk.ListStore.__init__ (self,
                                        GObject.TYPE_UINT64, str, float, float)

        __gsignals__ = {
            "needs-update":       (GObject.SignalFlags.RUN_FIRST,
                                   None, (bool,)),
            "subprofile-added":   (GObject.SignalFlags.RUN_FIRST,
                                   None, (Gtk.Widget,)),
            "subprofile-removed": (GObject.SignalFlags.RUN_FIRST,
                                   None, (Gtk.Widget,)),
            "path-changed":       (GObject.SignalFlags.RUN_FIRST,
                                   None, ())
        }

        def __init__ (self,
                      root      = None,
                      id        = None,
                      title     = None,
                      frames    = None,
                      direction = Direction.CALLEES,
                      sort      = (Store.INCLUSIVE, Gtk.SortType.DESCENDING),
                      *args,
                      **kwargs):
            Gtk.Box.__init__ (self,
                              *args,
                              orientation = Gtk.Orientation.HORIZONTAL,
                              **kwargs)

            self.root      = root or self
            self.id        = id
            self.frames    = frames
            self.direction = direction

            self.subprofile = None

            vbox = Gtk.Box (orientation = Gtk.Orientation.VERTICAL)
            self.pack_start (vbox, False, False, 0)
            vbox.show ()

            header = Gtk.HeaderBar (title = title or "All Functions")
            self.header = header
            vbox.pack_start (header, False, False, 0)
            header.show ()

            if not id:
                popover = ProfileViewer.ThreadPopover ()

                thread_filter_store = popover.thread_filter.store

                self.thread_filter_store = thread_filter_store
                self.thread_filter       = thread_filter_store.get_filter ()

                history.add_source (self.thread_filter_source_get,
                                    self.thread_filter_source_set)

                button = Gtk.MenuButton (popover = popover)
                header.pack_end (button)
                button.show ()

                button.connect ("toggled", self.thread_filter_button_toggled)

                hbox = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL,
                                spacing     = 4)
                button.add (hbox)
                hbox.show ()

                label = Gtk.Label (label = "Threads")
                hbox.pack_start (label, False, False, 0)
                label.show ()

                image = Gtk.Image.new_from_icon_name ("pan-down-symbolic",
                                                      Gtk.IconSize.BUTTON)
                hbox.pack_start (image, False, False, 0)
                image.show ()

                history.add_source (self.direction_source_get,
                                    self.direction_source_set)

                button = Gtk.Button (tooltip_text = "Call-graph direction")
                header.pack_end (button)
                button.show ()

                button.connect ("clicked", self.direction_button_clicked)

                image = Gtk.Image ()
                self.direction_image = image
                button.add (image)
                image.show ()
            else:
                button = Gtk.Button.new_from_icon_name (
                    "edit-select-symbolic",
                    Gtk.IconSize.BUTTON
                )
                header.pack_end (button)
                button.set_tooltip_text (
                    str (Selection (frame.sample for frame in frames))
                )
                button.show ()

                button.connect ("clicked", self.select_samples_clicked)

            scrolled = Gtk.ScrolledWindow (
                hscrollbar_policy = Gtk.PolicyType.NEVER,
                vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
            )
            vbox.pack_start (scrolled, True, True, 0)
            scrolled.show ()

            store = self.Store ()
            self.store = store
            store.set_sort_column_id (*sort)

            tree = Gtk.TreeView (model = store)
            self.tree = tree
            scrolled.add (tree)
            tree.set_search_column (store.FUNCTION)
            tree.show ()

            tree.get_selection ().connect ("changed",
                                           self.tree_selection_changed)

            tree.connect ("row-activated",   self.tree_row_activated)
            tree.connect ("key-press-event", self.tree_key_press_event)

            col = Gtk.TreeViewColumn (title = "Function")
            tree.append_column (col)
            col.set_resizable (True)
            col.set_sort_column_id (store.FUNCTION)

            cell = Gtk.CellRendererText (ellipsize = Pango.EllipsizeMode.END)
            col.pack_start (cell, True)
            col.add_attribute (cell, "text", store.FUNCTION)
            cell.set_property ("width-chars", 40)

            col = Gtk.TreeViewColumn (title = "Self")
            tree.append_column (col)
            col.set_alignment (0.5)
            col.set_sort_column_id (store.EXCLUSIVE)

            cell = CellRendererPercentage ()
            col.pack_start (cell, False)
            col.add_attribute (cell, "value", store.EXCLUSIVE)

            col = Gtk.TreeViewColumn (title = "All")
            tree.append_column (col)
            col.set_alignment (0.5)
            col.set_sort_column_id (store.INCLUSIVE)

            cell = CellRendererPercentage ()
            col.pack_start (cell, False)
            col.add_attribute (cell, "value", store.INCLUSIVE)

            if id:
                self.update ()

        def update (self):
            self.remove_subprofile ()

            if not self.id:
                self.update_frames ()

            self.update_store ()

            self.update_ui ()

        def update_frames (self):
            self.frames = []

            for i in selection.get_effective_selection ():
                for thread in samples[i].backtrace or []:
                    if thread.state in self.thread_filter[thread.id]:
                        thread_frames = thread.frames

                        if self.direction == self.Direction.CALLERS:
                            thread_frames = reversed (thread_frames)

                        stack   = []
                        prev_id = 0

                        for frame in thread_frames:
                            id = frame.info.id

                            if id == prev_id:
                                continue

                            self.frames.append (self.ProfileFrame (
                                sample = i,
                                stack  = stack,
                                i      = len (stack)
                            ))

                            stack.append (frame)

                            prev_id = id

        def update_store (self):
            stacks  = {}
            symbols = {}

            sort = self.store.get_sort_column_id ()

            self.store = self.Store ()

            for frame in self.frames:
                info      = frame.stack[frame.i].info
                symbol_id = info.id
                stack_id  = builtins.id (frame.stack)

                symbol = symbols.get (symbol_id, None)

                if not symbol:
                    symbol             = [info, 0, 0]
                    symbols[symbol_id] = symbol

                stack = stacks.get (stack_id, None)

                if not stack:
                    stack            = set ()
                    stacks[stack_id] = stack

                if frame.i == 0:
                    symbol[1] += 1

                if symbol_id not in stack:
                    stack.add (symbol_id)

                    symbol[2] += 1

            n_stacks = len (stacks)

            for symbol in symbols.values ():
                id   = symbol[0].id
                name = symbol[0].name if id != self.id else "[Self]"

                self.store.append ((id,
                                    name,
                                    symbol[1] / n_stacks,
                                    symbol[2] / n_stacks))

            self.store.set_sort_column_id (*sort)

            self.tree.set_model (self.store)
            self.tree.set_search_column (self.store.FUNCTION)

        def update_ui (self):
            if not self.id:
                if self.direction == self.Direction.CALLEES:
                    icon_name = "format-indent-more-symbolic"
                else:
                    icon_name = "format-indent-less-symbolic"

                self.direction_image.set_from_icon_name (icon_name,
                                                         Gtk.IconSize.BUTTON)
            else:
                if self.direction == self.Direction.CALLEES:
                    subtitle = "Callees"
                else:
                    subtitle = "Callers"

                self.header.set_subtitle (subtitle)

        def select (self, id):
            if id is not None:
                for row in self.store:
                    if row[self.store.ID] == id:
                        iter = row.iter
                        path = self.store.get_path (iter)

                        self.tree.get_selection ().select_iter (iter)

                        self.tree.scroll_to_cell (path, None, True, 0.5, 0)

                        break
            else:
                self.tree.get_selection ().unselect_all ()

        def add_subprofile (self, subprofile):
            self.remove_subprofile ()

            box = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
            self.subprofile_box = box
            self.pack_start (box, True, True, 0)
            box.show ()

            separator = Gtk.Separator (orientation = Gtk.Orientation.VERTICAL)
            box.pack_start (separator, False, False, 0)
            separator.show ()

            self.subprofile = subprofile
            box.pack_start (subprofile, True, True, 0)
            subprofile.show ()

            subprofile.connect ("subprofile-added",
                                lambda profile, subprofile:
                                    self.emit ("subprofile-added",
                                               subprofile))
            subprofile.connect ("subprofile-removed",
                                lambda profile, subprofile:
                                    self.emit ("subprofile-removed",
                                               subprofile))
            subprofile.connect ("path-changed",
                                lambda profile: self.emit ("path-changed"))

            self.emit ("subprofile-added", subprofile)

        def remove_subprofile (self):
            if self.subprofile:
                subprofile = self.subprofile

                self.remove (self.subprofile_box)

                self.subprofile     = None
                self.subprofile_box = None

                self.emit ("subprofile-removed", subprofile)

        def get_path (self):
            tree_sel = self.tree.get_selection ()

            sel_rows = tree_sel.get_selected_rows ()[1]

            if not sel_rows:
                return ()

            id = self.store[sel_rows[0]][self.store.ID]

            if self.subprofile:
                return (id,) + self.subprofile.get_path ()
            else:
                return (id,)

        def set_path (self, path):
            self.select (path[0] if path else None)

            if self.subprofile:
                self.subprofile.set_path (path[1:])

        def thread_filter_source_get (self):
            return self.thread_filter_store.get_filter ()

        def thread_filter_source_set (self, thread_filter):
            self.thread_filter = thread_filter

            self.thread_filter_store.set_filter (thread_filter)

            self.emit ("needs-update", False)

        def thread_filter_button_toggled (self, button):
            if not button.get_active ():
                thread_filter = self.thread_filter_store.get_filter ()

                if thread_filter != self.thread_filter:
                    self.thread_filter = thread_filter

                    history.start_group ()

                    history.record ()

                    self.emit ("needs-update", True)

                    history.end_group ()

        def direction_source_get (self):
            return self.direction

        def direction_source_set (self, direction):
            self.direction = direction

            self.emit ("needs-update", False)

        def direction_button_clicked (self, button):
            if self.direction == self.Direction.CALLEES:
                self.direction = self.Direction.CALLERS
            else:
                self.direction = self.Direction.CALLEES

            history.start_group ()

            history.record ()

            self.emit ("needs-update", True)

            history.end_group ()

        def select_samples_clicked (self, button):
            selection.select ({frame.sample for frame in self.frames})

            selection.change_complete ()

        def tree_selection_changed (self, tree_sel):
            self.remove_subprofile ()

            sel_rows = tree_sel.get_selected_rows ()[1]

            if not sel_rows:
                self.emit ("path-changed")

                return

            id    = self.store[sel_rows[0]][self.store.ID]
            title = self.store[sel_rows[0]][self.store.FUNCTION]

            frames = []

            for frame in self.frames:
                if frame.stack[frame.i].info.id == id:
                    frames.append (frame)

                    if frame.i > 0 and id != self.id:
                        frames.append (self.ProfileFrame (sample = frame.sample,
                                                          stack  = frame.stack,
                                                          i      = frame.i - 1))

            if id != self.id:
                self.add_subprofile (ProfileViewer.Profile (
                    self.root,
                    id,
                    title,
                    frames,
                    self.direction,
                    self.store.get_sort_column_id ()
                ))
            else:
                filenames = {frame.stack[frame.i].info.source
                             for frame in frames}
                filenames = list (filter (bool, filenames))

                if len (filenames) == 1:
                    file = find_file (filenames[0])

                    if file:
                        self.add_subprofile (ProfileViewer.SourceProfile (
                            file,
                            frames[0].stack[frames[0].i].info.name,
                            frames
                        ))

            self.emit ("path-changed")

        def tree_row_activated (self, tree, path, col):
            if self.root != self:
                self.root.select (self.store[path][self.store.ID])

        def tree_key_press_event (self, tree, event):
            if event.keyval == Gdk.KEY_Escape:
                self.select (None)

                if self.root is not self:
                    self.get_parent ().get_ancestor (
                        ProfileViewer.Profile
                    ).tree.grab_focus ()

                return True

            return False

    class SourceProfile (Gtk.Box):
        class Store (Gtk.ListStore):
            LINE       = 0
            HAS_FRAMES = 1
            EXCLUSIVE  = 2
            INCLUSIVE  = 3
            TEXT       = 4

            def __init__ (self):
                Gtk.ListStore.__init__ (self, int, bool, float, float, str)

        __gsignals__ = {
            "subprofile-added":   (GObject.SignalFlags.RUN_FIRST,
                                   None, (Gtk.Widget,)),
            "subprofile-removed": (GObject.SignalFlags.RUN_FIRST,
                                   None, (Gtk.Widget,)),
            "path-changed":       (GObject.SignalFlags.RUN_FIRST,
                                   None, ())
        }

        def __init__ (self,
                      file,
                      function,
                      frames,
                      *args,
                      **kwargs):
            Gtk.Box.__init__ (self,
                              *args,
                              orientation = Gtk.Orientation.VERTICAL,
                              **kwargs)

            self.file   = file
            self.frames = frames

            header = Gtk.HeaderBar (title    = file.get_basename (),
                                    subtitle = function)
            self.header = header
            self.pack_start (header, False, False, 0)
            header.show ()

            box = Gtk.Box (orientation = Gtk.Orientation.HORIZONTAL)
            header.pack_start (box)
            box.get_style_context ().add_class ("linked")
            box.get_style_context ().add_class ("raised")
            box.show ()

            button = Gtk.Button.new_from_icon_name ("go-up-symbolic",
                                                    Gtk.IconSize.BUTTON)
            self.prev_button = button
            box.pack_start (button, False, True, 0)
            button.show ()

            button.connect ("clicked", lambda *args: self.move (-1))

            button = Gtk.Button.new_from_icon_name ("go-down-symbolic",
                                                    Gtk.IconSize.BUTTON)
            self.next_button = button
            box.pack_end (button, False, True, 0)
            button.show ()

            button.connect ("clicked", lambda *args: self.move (+1))

            button = Gtk.Button.new_from_icon_name ("edit-select-symbolic",
                                                    Gtk.IconSize.BUTTON)
            self.select_samples_button = button
            header.pack_end (button)
            button.show ()

            button.connect ("clicked", self.select_samples_clicked)

            button = Gtk.Button.new_from_icon_name ("text-x-generic-symbolic",
                                                    Gtk.IconSize.BUTTON)
            header.pack_end (button)
            button.set_tooltip_text (file.get_path ())
            button.show ()

            button.connect ("clicked", self.view_source_clicked)

            scrolled = Gtk.ScrolledWindow (
                hscrollbar_policy = Gtk.PolicyType.NEVER,
                vscrollbar_policy = Gtk.PolicyType.AUTOMATIC
            )
            self.pack_start (scrolled, True, True, 0)
            scrolled.show ()

            store = self.Store ()
            self.store = store

            tree = Gtk.TreeView (model = store)
            self.tree = tree
            scrolled.add (tree)
            tree.set_search_column (store.LINE)
            tree.show ()

            tree.get_selection ().connect ("changed",
                                           self.tree_selection_changed)

            scale = 0.85

            col = Gtk.TreeViewColumn (title = "Self")
            tree.append_column (col)
            col.set_alignment (0.5)
            col.set_sort_column_id (store.EXCLUSIVE)

            cell = CellRendererPercentage (scale = scale)
            col.pack_start (cell, False)
            col.add_attribute (cell, "visible", store.HAS_FRAMES)
            col.add_attribute (cell, "value",   store.EXCLUSIVE)

            col = Gtk.TreeViewColumn (title = "All")
            tree.append_column (col)
            col.set_alignment (0.5)
            col.set_sort_column_id (store.INCLUSIVE)

            cell = CellRendererPercentage (scale = scale)
            col.pack_start (cell, False)
            col.add_attribute (cell, "visible", store.HAS_FRAMES)
            col.add_attribute (cell, "value",   store.INCLUSIVE)

            col = Gtk.TreeViewColumn ()
            tree.append_column (col)

            cell = Gtk.CellRendererText (xalign = 1,
                                         xpad   = 8,
                                         family = "Monospace",
                                         weight = Pango.Weight.BOLD,
                                         scale  = scale)
            col.pack_start (cell, False)
            col.add_attribute (cell, "text", store.LINE)

            cell = Gtk.CellRendererText (family = "Monospace",
                                         scale  = scale)
            col.pack_start (cell, True)
            col.add_attribute (cell, "text", store.TEXT)

            self.update ()

        def get_samples (self):
            sel_rows = self.tree.get_selection ().get_selected_rows ()[1]

            if sel_rows:
                line = self.store[sel_rows[0]][self.store.LINE]

                sel = {frame.sample for frame in self.frames
                       if frame.stack[frame.i].info.line == line}

                return sel
            else:
                return {}

        def update (self):
            self.update_store ()
            self.update_ui ()

        def update_store (self):
            stacks = {}
            lines  = {}

            for frame in self.frames:
                info     = frame.stack[frame.i].info
                line_id  = info.line
                stack_id = builtins.id (frame.stack)

                line = lines.get (line_id, None)

                if not line:
                    line           = [0, 0]
                    lines[line_id] = line

                stack = stacks.get (stack_id, None)

                if not stack:
                    stack            = set ()
                    stacks[stack_id] = stack

                if frame.i == 0:
                    line[0] += 1

                if line_id not in stack:
                    stack.add (line_id)

                    line[1] += 1

            self.lines = list (lines.keys ())
            self.lines.sort ()

            n_stacks = len (stacks)

            self.store.clear ()

            i = 1

            for text in open (self.file.get_path (), "r"):
                text = text.rstrip ("\n")

                line = lines.get (i, None)

                if line:
                    self.store.append ((i,
                                        True,
                                        line[0] / n_stacks,
                                        line[1] / n_stacks,
                                        text))
                else:
                    self.store.append ((i,
                                        False,
                                        0,
                                        0,
                                        text))

                i += 1

            self.select (max (lines.items (), key = lambda line: line[1][1])[0])

        def update_ui (self):
            sel_rows = self.tree.get_selection ().get_selected_rows ()[1]

            if sel_rows:
                line = self.store[sel_rows[0]][self.store.LINE]

                i = bisect.bisect_left (self.lines, line)

                self.prev_button.set_sensitive (i > 0)

                if i < len (self.lines) and self.lines[i] == line:
                    i += 1

                self.next_button.set_sensitive (i < len (self.lines))
            else:
                self.prev_button.set_sensitive (False)
                self.next_button.set_sensitive (False)

            samples = self.get_samples ()

            if samples:
                self.select_samples_button.set_sensitive (True)
                self.select_samples_button.set_tooltip_text (
                    str (Selection (samples))
                )
            else:
                self.select_samples_button.set_sensitive (False)
                self.select_samples_button.set_tooltip_text (None)

        def select (self, line):
            if line is not None:
                for row in self.store:
                    if row[self.store.LINE] == line:
                        iter = row.iter
                        path = self.store.get_path (iter)

                        self.tree.get_selection ().select_iter (iter)

                        self.tree.scroll_to_cell (path, None, True, 0.5, 0)

                        break
            else:
                self.tree.get_selection ().unselect_all ()


        def move (self, dir):
            if dir == 0:
                return

            sel_rows = self.tree.get_selection ().get_selected_rows ()[1]

           
                line = self.store[sel_rows[0]][self.store.LINE]

                i = bisect.bisect_left (self.lines, line)

                if dir < 0:
                    i -= 1
                elif i < len (self.lines) and self.lines[i] == line:
                    i += 1

                if i >= 0 and i < len (self.lines):
                    self.select (self.lines[i])
                else:
                    self.select (None)

        def select_samples_clicked (self, button):
            selectionselect(elf.et_samples (java.lang.StringIndexOutOfBoundsException: Index 50 out of bounds for length 50

            selection.change_complete ()

        def view_source_clicked (self, button):
            line = 0

            sel_rows = self.treeL

ijava.lang.StringIndexOutOfBoundsException: Range [24, 23) out of bounds for length 24
                line = self.store[sel_rows[0]][self.store.LINE]

            run_editor (self.file, line)

        def get_path (self):
            tree_sel = self.tree.get_selection ()

            sel_rows = tree_sel.get_selected_rows ()[1]

            if not sel_rows:
                return ()

            line = self.store[sel_rows[0]][self.store.LINE]

            return (line,)

        def set_path (self, path):
            self.select (pathjava.lang.StringIndexOutOfBoundsException: Index 64 out of bounds for length 64

        def tree_selection_changed (self, tree_sel):
            selfupdate_ui (

            self.emit ("path-changed" started - stopping [label="vlc_player_Stop()" URL="@ref playing -> paused [label="vlc_player_Pause()" URL="@ref vlc_player_Pause"]java.lang.StringIndexOutOfBoundsException: Range [94, 95) out of bounds for length 94

  pa - stopping [abel=vlc_player_Stop) RL"vlc_player_Stop" fontcolor="red"];
        Gtk.ScrolledWindow.__init__ (
            self,
            *args,
             =GtkPolicyType.java.lang.StringIndexOutOfBoundsException: Index 57 out of bounds for length 57
            vscrollbar_policy = Gtk.PolicyType. -  l=vlc_player_Start(""r vlc_player_Start"fontcolor=darkgreen";
            **kwargs
        )

        self.java.lang.StringIndexOutOfBoundsException: Index 32 out of bounds for length 8
        self.eeds_update                
java.lang.StringIndexOutOfBoundsException: Range [17, 8) out of bounds for length 44

        profile =java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0
r=p
     *wdeadlock.
java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0

        selection.      ill succeed unlockthe layer,one call to

        profile.connect ("java.lang.StringIndexOutOfBoundsException: Index 30 out of bounds for length 7
        java.lang.StringIndexOutOfBoundsException: Range [0, 15) out of bounds for length 2
        profile.*pmpto edia_provider o ULL,the
         *structure mustb valid during java.lang.StringIndexOutOfBoundsException: Range [60, 37) out of bounds for length 60

)

    @GObject.Property (type = bool, *java.lang.StringIndexOutOfBoundsException: Range [0, 1) out of bounds for length 0
   a()
        sel = java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 2

  f en (el)>1java.lang.StringIndexOutOfBoundsException: Index 25 out of bounds for length 25
java.lang.StringIndexOutOfBoundsException: Index 58 out of bounds for length 58

        *

elf:
         /
            return

       block(

:
       .oot_profile.update(java.lang.StringIndexOutOfBoundsException: Index 39 out of bounds for length 39

            needs_update  

        self.root_profile.set_path (self.path)

history. (

    def queue_update (java.lang.StringIndexOutOfBoundsException: Index 24 out of bounds for length 3
       needs_update =

       fnow self.
            selfupdate (

    def do_map (self /
        self.(java.lang.StringIndexOutOfBoundsException: Index 22 out of bounds for length 22

        Gtk. *@aramtrue the layer reaching java.lang.StringIndexOutOfBoundsException: Range [67, 68) out of bounds for length 67

    def
        self.queue_update

        self.notify ("available")


selfqueue_update now)

    def java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 2
        if not history.is_blocked ():
            self.ath  profile.et_path )

        history.record ()

        if not self.adjustment_changed_handler:
            adjustment = self.get_hadjustment ()

            def adjustment_changed (adjustment):
               Object.(
adjustment,
                    self.djustment_changed_handler
              java.lang.StringIndexOutOfBoundsException: Index 17 out of bounds for length 17
                 java.lang.StringIndexOutOfBoundsException: Range [10, 9) out of bounds for length 76

 *infollowing order:STARTED,PLAYING  STOPPEDjava.lang.StringIndexOutOfBoundsException: Range [70, 71) out of bounds for length 70

            self.adjustment_changed_handler = adjustment.connect (
                "changed",
                adjustment_changed
            )

    def profile_subprofile_removed (self, profile, subprofile *media that can be .Thismeans thatSTOPPING,STOPPED states (or
        n . )java.lang.StringIndexOutOfBoundsException: Index 37 out of bounds for length 37
            self   cduring transition.

        history.record ()


    def profile_path_changed*
        if      Initial ,or   aninternal  the 
     java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0

        history.java.lang.StringIndexOutOfBoundsException: Index 20 out of bounds for length 7

    def source_get (self):
java.lang.StringIndexOutOfBoundsException: Range [20, 14) out of bounds for length 24

    def source_set*internaltransition (hen the media reach the end of file for.
        self.     *

    
.root_profile.set_path p)

(.Window:
def _init__ self args,*:
#  1<)
            self,
            *args,
            default_width    024,
            default_height  = 768,
            window_position=Gtk.CENTER
            **kwargs)

        =HeaderBar 
                        =GIMP Log ,
            show_close_button =True

        {
s (java.lang.StringIndexOutOfBoundsException: Index 34 out of bounds for length 34
        header.show ()

   =Gtk.ox (rientation =Gtk.HORIZONTAL)
        header.pack_start  *   isbeing played,thiswill be java.lang.StringIndexOutOfBoundsException: Range [77, 78) out of bounds for length 77
        box.get_style_context ().add_class ("linked")
        box. * user to send controls lseekr  Startingthe java.lang.StringIndexOutOfBoundsException: Index 73 out of bounds for length 73
          TOPPED)or asynchronous.In he   )

.(go-revious-"
                                               GtkIconSize)
       box.(,  ,0java.lang.StringIndexOutOfBoundsException: Index 47 out of bounds for length 47
button. )

tory.ind_property (canundo,
                               button, "sensitive",
                               GObject.BindingFlags.SYNC_CREATE)

        c (c,* undo()

       button  .Buttonnew_from_icon_name "-nextsymbolic"
                                                Gtk..)
        box.pack_end (button,   current , next  willbeopened from ) 
        button.show ()

        historybind_property(can-"java.lang.StringIndexOutOfBoundsException: Index 42 out of bounds for length 42
                               button, "sensitive",
.BindingFlags.java.lang.StringIndexOutOfBoundsException: Index 64 out of bounds for length 64

java.lang.StringIndexOutOfBoundsException: Range [8, 1) out of bounds for length 65

  .MenuButton)
        header.pack_end (button)
        button.java.lang.StringIndexOutOfBoundsException: Range [0, 31) out of bounds for length 3
        button.show ()

        image = Gtk.Image.new_from_icon_name ("edit- *
                        GtkIconSize)
        button.add (image)
        .how()

r=FindSamplesPopover (elative_to  button)
.=
returnitem):NULL;

       def (ction)java.lang.StringIndexOutOfBoundsException: Range [38, 39) out of bounds for length 38
            def f (*args):
                action (
                selection.change_complete ()

            return f

        button = Gtk.Button.new_from_icon_name (
            "bject-flip-orizontal-,
.IconSizeBUTTON
        )
p button)
        java.lang.StringIndexOutOfBoundsException: Index 1 out of bounds for length 0
        button.show ()

java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0

        button = Gtk.Button.new_from_icon_name (
            "java.lang.StringIndexOutOfBoundsException: Index 15 out of bounds for length 0
            Gtk.IconSize.BUTTON
        )
 .= button
        header.pack_end (button)
        button. *
        button.show ()

        ."" (.)java.lang.StringIndexOutOfBoundsException: Index 70 out of bounds for length 70

  Paned( =GtkOjava.lang.StringIndexOutOfBoundsException: Range [66, 67) out of bounds for length 66
        self.paned = paned
        java.lang.StringIndexOutOfBoundsException: Index 12 out of bounds for length 12
        *
show(

        graphs java.lang.StringIndexOutOfBoundsException: Index 2 out of bounds for length 2
        paned*@eturn the currentp java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
 *Getthe    java.lang.StringIndexOutOfBoundsException: Index 36 out of bounds for length 36
        graphs.show ()

      . orientation =Gtk..
        paned.java.lang.StringIndexOutOfBoundsException: Index 16 out of bounds for length 3
        hbox.show    {

        java.lang.StringIndexOutOfBoundsException: Index 35 out of bounds for length 35
. s,False ,0
        sidebar.            lc_player_Pause)

s  GtkStack.StackTransitionTypeC
        self.stack = stack
        hbox.pack_start (stack, True, True, 0)
        stack.show ()

        sidebar.set_stack (*@ef 

 )
.(, information,"")
java.lang.StringIndexOutOfBoundsException: Range [0, 8) out of bounds for length 3

i java.lang.StringIndexOutOfBoundsException: Index 19 out of bounds for length 19
            markers_viewer = MarkersViewerreturn (player  VLC_PLAYER_CAP_PAUSEjava.lang.StringIndexOutOfBoundsException: Index 69 out of bounds for length 69
static  java.lang.StringIndexOutOfBoundsException: Range [18, 19) out of bounds for length 18
            java.lang.StringIndexOutOfBoundsException: Index 23 out of bounds for length 0

        vars_viewer  bool
        stackreturn lc_player_GetCapabilities() &VLC_PLAYER_CAP_REWINDjava.lang.StringIndexOutOfBoundsException: Index 70 out of bounds for length 70
        vars_viewer*

        box 
  return  ofthe layer ( .is ,>.  )
        stack.java.lang.StringIndexOutOfBoundsException: Index 21 out of bounds for length 3

        backtrace_viewer = BacktraceViewer ()
 
       .(backtrace_viewer T,0)

        backtrace_viewer
           backtrace_viewer,visible"
                                        GObject.java.lang.StringIndexOutOfBoundsException: Index 58 out of bounds for length 39

        backtrace_viewer.connect  *
                                  self.  note  andplaying media doesn' have necessarily a valid length.

         java.lang.StringIndexOutOfBoundsException: Index 2 out of bounds for length 2
s. =
b. profile_viewer,True,True,0java.lang.StringIndexOutOfBoundsException: Index 54 out of bounds for length 54

        profile_viewer.bind_property ("available"
           visible,
                                      GObject.BindingFlags.SYNC_CREATE)

        profile_viewer.connect ("notify::available",
                                self.cflow_notify_available java.lang.StringIndexOutOfBoundsException: Index 2 out of bounds for length 2

 * doesnt haveanytime   notyetstarted or ase error)

        selection.connect

        self *@ee vlc_player_cbs.

    def selection_change_complete (self, selection):
java.lang.StringIndexOutOfBoundsException: Range [37, 12) out of bounds for length 50
lection_button.et_sensitive (election.election)

def cflow_notify_available (,*args:
        if self.java.lang.StringIndexOutOfBoundsException: Index 18 out of bounds for length 0
            self.stack.child_set (self.cflow_box, java.lang.StringIndexOutOfBoundsException: Index 2 out of bounds for length 2
            self.cflow_box.show ()
        elif *
            self.stack.child_set (self.cflow_box, title = "Profile")
.show(
lse:
            self.cflow_box.hide ()

gs. (.(gtkapplication-darktheme"
                                          *java.lang.StringIndexOutOfBoundsException: Index 0 out of bounds for length 0

window =  * a starting positio.
show (

window.connect ("destroy", Gtk.main_quit)

istory (

GtkVLC_API 

Messung
CHZG
849610093

¤ Dauer der Verarbeitung: 0.136 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104838
#Domains=499075