<commit_before>"""Neovim Gtk+ UI."""
import math

import cairo

from gi.repository import GLib, GObject, Gdk, Gtk, Pango, PangoCairo

from .screen import Screen


__all__ = ('GtkUI',)


SHIFT = Gdk.ModifierType.SHIFT_MASK
CTRL = Gdk.ModifierType.CONTROL_MASK
ALT = Gdk.ModifierType.MOD1_MASK


# Translation table for the names returned by Gdk.keyval_name that don't match
# the corresponding nvim key names.
KEY_TABLE = {
    'slash': '/',
    'backslash': '\\',
    'dead_circumflex': '^',
    'at': '@',
    'numbersign': '#',
    'dollar': '$',
    'percent': '%',
    'ampersand': '^',
    'asterisk': '*',
    'parenleft': '(',
    'parenright': ')',
    'underscore': '_',
    'plus': '+',
    'minus': '-',
    'bracketleft': '[',
    'bracketright': ']',
    'braceleft': '[',
    'braceright': ']',
    'dead_diaeresis': '"',
    'dead_acute': "'",
    'less': "<",
    'greater': ">",
    'comma': ",",
    'period': ".",
    'BackSpace': 'BS',
    'Return': 'CR',
    'Escape': 'Esc',
    'Delete': 'Del',
    'Page_Up': 'PageUp',
    'Page_Down': 'PageDown',
    'Enter': 'CR',
    'ISO_Left_Tab': 'Tab'
}


if (GLib.MAJOR_VERSION, GLib.MINOR_VERSION,) <= (2, 32,):
    GLib.threads_init()


class GtkUI(object):

    """Gtk+ UI class."""

    def __init__(self):
        """Initialize the UI instance."""
        self._redraw_arg = None
        self._foreground = -1
        self._background = -1
        self._font_size = 13
        self._font_name = 'Monospace'
        self._screen = None
        self._attrs = None
        self._busy = False
        self._mouse_enabled = False
        self._insert_cursor = False
        self._blink = False
        self._blink_timer_id = None
        self._resize_timer_id = None
        self._pressed = None
        self._invalid = None
        self._pending = [0, 0, 0]
        self._reset_cache()

    def start(self, bridge):
        """Start the UI event loop."""
        bridge.attach(80, 24, True)
        drawing_area = Gtk.DrawingArea()
        drawing_area.connect('draw', self._gtk_draw)
        window = Gtk.Window()
        window.add(drawing_area)
        window.set_events(window.get_events() |
                          Gdk.EventMask.BUTTON_PRESS_MASK |
                          Gdk.EventMask.BUTTON_RELEASE_MASK |
                          Gdk.EventMask.POINTER_MOTION_MASK |
                          Gdk.EventMask.SCROLL_MASK)
        window.connect('configure-event', self._gtk_configure)
        window.connect('delete-event', self._gtk_quit)
        window.connect('key-press-event', self._gtk_key)
        window.connect('button-press-event', self._gtk_button_press)
        window.connect('button-release-event', self._gtk_button_release)
        window.connect('motion-notify-event', self._gtk_motion_notify)
        window.connect('scroll-event', self._gtk_scroll)
        window.show_all()
        im_context = Gtk.IMContextSimple()
        im_context.connect('commit', self._gtk_input)
        self._pango_context = drawing_area.create_pango_context()
        self._drawing_area = drawing_area
        self._window = window
        self._im_context = im_context
        self._bridge = bridge
        Gtk.main()

    def quit(self):
        """Exit the UI event loop."""
        GObject.idle_add(Gtk.main_quit)

    def schedule_screen_update(self, apply_updates):
        """Schedule screen updates to run in the UI event loop."""
        def wrapper():
            apply_updates()
            self._flush()
            self._start_blinking()
            self._screen_invalid()
        GObject.idle_add(wrapper)

    def _screen_invalid(self):
        self._drawing_area.queue_draw()

    def _nvim_resize(self, columns, rows):
        da = self._drawing_area
        # create FontDescription object for the selected font/size
        font_str = '{0} {1}'.format(self._font_name, self._font_size)
        self._font, pixels, normal_width, bold_width = _parse_font(font_str)
        # calculate the letter_spacing required to make bold have the same
        # width as normal
        self._bold_spacing = normal_width - bold_width
        cell_pixel_width, cell_pixel_height = pixels
        # calculate the total pixel width/height of the drawing area
        pixel_width = cell_pixel_width * columns
        pixel_height = cell_pixel_height * rows
        gdkwin = da.get_window()
        content = cairo.CONTENT_COLOR
        self._cairo_surface = gdkwin.create_similar_surface(content,
                                                            pixel_width,
                                                            pixel_height)
        self._cairo_context = cairo.Context(self._cairo_surface)
        self._pango_layout = PangoCairo.create_layout(self._cairo_context)
        self._pango_layout.set_alignment(Pango.Alignment.LEFT)
        self._pango_layout.set_font_description(self._font)
        self._pixel_width, self._pixel_height = pixel_width, pixel_height
        self._cell_pixel_width = cell_pixel_width
        self._cell_pixel_height = cell_pixel_height
        self._screen = Screen(columns, rows)
        self._window.resize(pixel_width, pixel_height)

    def _nvim_clear(self):
        self._clear_region(self._screen.top, self._screen.bot + 1,
                           self._screen.left, self._screen.right + 1)
        self._screen.clear()

    def _nvim_eol_clear(self):
        row, col = self._screen.row, self._screen.col
        self._clear_region(row, row + 1, col, self._screen.right + 1)
        self._screen.eol_clear()

    def _nvim_cursor_goto(self, row, col):
        self._screen.cursor_goto(row, col)

    def _nvim_busy_start(self):
        self._busy = True

    def _nvim_busy_stop(self):
        self._busy = False

    def _nvim_mouse_on(self):
        self._mouse_enabled = True

    def _nvim_mouse_off(self):
        self._mouse_enabled = False

    def _nvim_insert_mode(self):
        self._insert_cursor = True

    def _nvim_normal_mode(self):
        self._insert_cursor = False

    def _nvim_set_scroll_region(self, top, bot, left, right):
        self._screen.set_scroll_region(top, bot, left, right)

    def _nvim_scroll(self, count):
        self._flush()
        top, bot = self._screen.top, self._screen.bot + 1
        left, right = self._screen.left, self._screen.right + 1
        # The diagrams below illustrate what will happen, depending on the
        # scroll direction. "=" is used to represent the SR(scroll region)
        # boundaries and "-" the moved rectangles. note that dst and src share
        # a common region
        if count > 0:
            # move an rectangle in the SR up, this can happen while scrolling
            # down
            # +-------------------------+
            # | (clipped above SR)      |            ^<commit_msg>fixes #131 AttributeError for _nvim_mode_change<commit_after>"""Neovim Gtk+ UI."""
import math

import cairo

from gi.repository import GLib, GObject, Gdk, Gtk, Pango, PangoCairo

from .screen import Screen


__all__ = ('GtkUI',)


SHIFT = Gdk.ModifierType.SHIFT_MASK
CTRL = Gdk.ModifierType.CONTROL_MASK
ALT = Gdk.ModifierType.MOD1_MASK


# Translation table for the names returned by Gdk.keyval_name that don't match
# the corresponding nvim key names.
KEY_TABLE = {
    'slash': '/',
    'backslash': '\\',
    'dead_circumflex': '^',
    'at': '@',
    'numbersign': '#',
    'dollar': '$',
    'percent': '%',
    'ampersand': '^',
    'asterisk': '*',
    'parenleft': '(',
    'parenright': ')',
    'underscore': '_',
    'plus': '+',
    'minus': '-',
    'bracketleft': '[',
    'bracketright': ']',
    'braceleft': '[',
    'braceright': ']',
    'dead_diaeresis': '"',
    'dead_acute': "'",
    'less': "<",
    'greater': ">",
    'comma': ",",
    'period': ".",
    'BackSpace': 'BS',
    'Return': 'CR',
    'Escape': 'Esc',
    'Delete': 'Del',
    'Page_Up': 'PageUp',
    'Page_Down': 'PageDown',
    'Enter': 'CR',
    'ISO_Left_Tab': 'Tab'
}


if (GLib.MAJOR_VERSION, GLib.MINOR_VERSION,) <= (2, 32,):
    GLib.threads_init()


class GtkUI(object):

    """Gtk+ UI class."""

    def __init__(self):
        """Initialize the UI instance."""
        self._redraw_arg = None
        self._foreground = -1
        self._background = -1
        self._font_size = 13
        self._font_name = 'Monospace'
        self._screen = None
        self._attrs = None
        self._busy = False
        self._mouse_enabled = False
        self._insert_cursor = False
        self._blink = False
        self._blink_timer_id = None
        self._resize_timer_id = None
        self._pressed = None
        self._invalid = None
        self._pending = [0, 0, 0]
        self._reset_cache()

    def start(self, bridge):
        """Start the UI event loop."""
        bridge.attach(80, 24, True)
        drawing_area = Gtk.DrawingArea()
        drawing_area.connect('draw', self._gtk_draw)
        window = Gtk.Window()
        window.add(drawing_area)
        window.set_events(window.get_events() |
                          Gdk.EventMask.BUTTON_PRESS_MASK |
                          Gdk.EventMask.BUTTON_RELEASE_MASK |
                          Gdk.EventMask.POINTER_MOTION_MASK |
                          Gdk.EventMask.SCROLL_MASK)
        window.connect('configure-event', self._gtk_configure)
        window.connect('delete-event', self._gtk_quit)
        window.connect('key-press-event', self._gtk_key)
        window.connect('button-press-event', self._gtk_button_press)
        window.connect('button-release-event', self._gtk_button_release)
        window.connect('motion-notify-event', self._gtk_motion_notify)
        window.connect('scroll-event', self._gtk_scroll)
        window.show_all()
        im_context = Gtk.IMContextSimple()
        im_context.connect('commit', self._gtk_input)
        self._pango_context = drawing_area.create_pango_context()
        self._drawing_area = drawing_area
        self._window = window
        self._im_context = im_context
        self._bridge = bridge
        Gtk.main()

    def quit(self):
        """Exit the UI event loop."""
        GObject.idle_add(Gtk.main_quit)

    def schedule_screen_update(self, apply_updates):
        """Schedule screen updates to run in the UI event loop."""
        def wrapper():
            apply_updates()
            self._flush()
            self._start_blinking()
            self._screen_invalid()
        GObject.idle_add(wrapper)

    def _screen_invalid(self):
        self._drawing_area.queue_draw()

    def _nvim_resize(self, columns, rows):
        da = self._drawing_area
        # create FontDescription object for the selected font/size
        font_str = '{0} {1}'.format(self._font_name, self._font_size)
        self._font, pixels, normal_width, bold_width = _parse_font(font_str)
        # calculate the letter_spacing required to make bold have the same
        # width as normal
        self._bold_spacing = normal_width - bold_width
        cell_pixel_width, cell_pixel_height = pixels
        # calculate the total pixel width/height of the drawing area
        pixel_width = cell_pixel_width * columns
        pixel_height = cell_pixel_height * rows
        gdkwin = da.get_window()
        content = cairo.CONTENT_COLOR
        self._cairo_surface = gdkwin.create_similar_surface(content,
                                                            pixel_width,
                                                            pixel_height)
        self._cairo_context = cairo.Context(self._cairo_surface)
        self._pango_layout = PangoCairo.create_layout(self._cairo_context)
        self._pango_layout.set_alignment(Pango.Alignment.LEFT)
        self._pango_layout.set_font_description(self._font)
        self._pixel_width, self._pixel_height = pixel_width, pixel_height
        self._cell_pixel_width = cell_pixel_width
        self._cell_pixel_height = cell_pixel_height
        self._screen = Screen(columns, rows)
        self._window.resize(pixel_width, pixel_height)

    def _nvim_clear(self):
        self._clear_region(self._screen.top, self._screen.bot + 1,
                           self._screen.left, self._screen.right + 1)
        self._screen.clear()

    def _nvim_eol_clear(self):
        row, col = self._screen.row, self._screen.col
        self._clear_region(row, row + 1, col, self._screen.right + 1)
        self._screen.eol_clear()

    def _nvim_cursor_goto(self, row, col):
        self._screen.cursor_goto(row, col)

    def _nvim_busy_start(self):
        self._busy = True

    def _nvim_busy_stop(self):
        self._busy = False

    def _nvim_mouse_on(self):
        self._mouse_enabled = True

    def _nvim_mouse_off(self):
        self._mouse_enabled = False

    def _nvim_mode_change(self, mode):
        self._insert_cursor = mode == 'insert'

    def _nvim_set_scroll_region(self, top, bot, left, right):
        self._screen.set_scroll_region(top, bot, left, right)

    def _nvim_scroll(self, count):
        self._flush()
        top, bot = self._screen.top, self._screen.bot + 1
        left, right = self._screen.left, self._screen.right + 1
        # The diagrams below illustrate what will happen, depending on the
        # scroll direction. "=" is used to represent the SR(scroll region)
        # boundaries and "-" the moved rectangles. note that dst and src share
        # a common region
        if count > 0:
            # move an rectangle in the SR up, this can happen while scrolling
            # down
            # +-------------------------+
            # | (clipped above SR)      |            ^
{'fraction_non_alphanumeric': 0.11808416715715127, 'fraction_numerical': 0.003752207180694526, 'mean_word_length': 2.284126600628171, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2087161', 'n_tokens_mistral': 4247, 'n_tokens_neox': 4113, 'n_words': 866}