コード例 #1
0
ファイル: file_transfer.py プロジェクト: stonewell/pymterm
class FileTransfer(object):
    def __init__(self, session, do_ask = True):
        self._transfer_task = None
        self._session = session
        self._transfered = 0
        self._total = 1
        self.__on_progress_task = Task(self._on_progress, .001, start=False)
        self._do_ask = do_ask

    def _upload(self, l_f, r_f, r_home = None, r_pwd = None):
        if not os.path.isfile(l_f):
            self._session.report_error("local file:{} is not existing, upload failed".format(l_f))
            return

        if len(r_f) == 0:
            r_f = '/'.join([".", os.path.basename(l_f)])
        elif len(os.path.basename(r_f)) == 0:
            r_f = '/'.join([r_f, os.path.basename(l_f)])

        self._transfer_thread = MyThread(target=lambda: self._session.transfer_file(l_f,
                                    r_f,
                                    r_home,
                                    r_pwd,
                                    True,
                                    self.on_progress))
        self._transfer_thread.start()

    def on_progress(self, transfered, total):
        self._transfered, self._total = transfered, total
        self.__on_progress_task.start()

    def _on_progress(self):
        pass

    def _download(self, l_f, r_f, r_home = None, r_pwd = None):
        if len(r_f) == 0:
            self._session.report_error("remote file is not existing, download failed")
            return

        if len(l_f) == 0:
            l_f = os.path.join('.', os.path.basename(r_f))

        l_f = os.path.expandvars(os.path.expanduser(l_f))

        if os.path.isdir(l_f):
            l_f = os.path.join(l_f, os.path.basename(r_f))

        if self._do_ask and os.path.isfile(l_f):
            if ask(u'file:{} exists, overwrite?'.format(l_f)) != 1:
                return

        self._transfer_thread = MyThread(target=lambda: self._session.transfer_file(l_f,
                                    r_f,
                                    r_home,
                                    r_pwd,
                                    False,
                                    self.on_progress))
        self._transfer_thread.start()
コード例 #2
0
class DataControlWindow(Window):
    """Controls the data everyone sees."""

    resizable = True

    item_width = 200
    duration = datetime.timedelta(minutes=15)
    time = datetime.datetime(2011, 8, 8, 18, 0, 0)

    def __init__(self, manager):
        super(DataControlWindow, self).__init__(title="Data Control")

        self.manager = manager

        duration_text = ":".join(
            (
                "%02d" % (self.duration.seconds // 3600),
                "%02d" % ((self.duration.seconds % 3600) // 60),
                "%02d" % (self.duration.seconds % 60),
            )
        )
        self.folder_label = Label(text=manager.folder)
        self.time_field = TextField(
            text=self.time.strftime("%Y/%m/%d %H:%M:%S"), width=self.item_width, enter_action=self.update
        )
        self.duration_field = TextField(text=duration_text, width=self.item_width, enter_action=self.update)
        self.live_checkbox = CheckBox("Live", action=self.toggle_live)
        self.revsync_checkbox = CheckBox("Rev-sync", action=self.toggle_revsync)
        self.averaging_checkbox = CheckBox("Average data", action=self.toggle_averaging)
        self.convert_checkbox = CheckBox("Convert files", action=self.toggle_convert)
        self.update_button = Button(title="Refresh", action=self.update)
        self.auto_updater = Task(self.update, AUTO_UPDATE_INTERVAL, start=False, repeat=True)

        time_row = Row([Label("Start: "), self.time_field])
        duration_row = Row([Label("Duration: "), self.duration_field])

        self.place_column(
            [
                self.folder_label,
                time_row,
                duration_row,
                self.live_checkbox,
                self.revsync_checkbox,
                self.averaging_checkbox,
                self.convert_checkbox,
                self.update_button,
            ],
            left=PADDING,
            top=PADDING,
        )
        self.shrink_wrap()

    def toggle_live(self):
        self.update()
        if self.live_checkbox.value:
            self.time_field.enabled = False
            self.auto_updater.start()
        else:
            self.time_field.enabled = True
            self.auto_updater.stop()

    def toggle_revsync(self):
        TELESCOPE_10GHZ.sync = self.revsync_checkbox.value
        TELESCOPE_15GHZ.sync = self.revsync_checkbox.value

    def toggle_averaging(self):
        self.manager.averaging = self.averaging_checkbox.value
        self.manager.clear_cache()

    def toggle_convert(self):
        self.manager.set_convert(self.convert_checkbox.value)

    def update(self):
        try:
            duration = parse_timedelta(self.duration_field.text)
            if duration.seconds < 0:
                raise ValueError()
            self.duration = duration
        except ValueError:
            alert("stop", "Invalid duration. Requires 'hour:minute:second'.")
            return

        if self.live_checkbox.value:
            try:
                end = self.manager.get_last_filetime()
            except IndexError:
                print "No _ext.fits files exist in the given directory tree."
                return
            start = end - self.duration
        else:
            try:
                self.time = parse_datetime(self.time_field.text)
            except ValueError:
                alert("stop", "Invalid time. Requires 'year/month/day hour:minute:second'.")
                return
            start = self.time
            end = start + self.duration

        self.manager.set_interval(start, end)
        self.manager.read_data()
        self.update_plotwindow()

    def set_plotwindow(self, plotwindow):
        """Save reference to the plot window to trigger updates"""
        self._plotwindow = plotwindow

    def update_plotwindow(self):
        """Trigger update of the attached plot window"""
        self._plotwindow.plot_update()
コード例 #3
0
class TerminalPyGUIViewBase(TerminalWidget):
    def __init__(self, **kwargs):
        self.padding_x = 5
        self.padding_y = 5
        self.session = None
        self.selection_color = [0.1843, 0.6549, 0.8313, .5]
        self._width_cache = {}
        self._lock = threading.Lock()
        self._refresh_task = Task(self.__refresh, .02, False, False)

        TerminalWidget.__init__(self, **kwargs)

        self._generic_tabbing = False

    def gen_render_color(self, color_spec):
        c = map(lambda x: x / 255, map(float, color_spec))

        return c

    def __refresh(self):
        if self.session and not self.session.stopped:
            if pymterm.debug_log:
                logging.getLogger('term_pygui').debug('refresh called')
            self.invalidate()
            self.update()

    def refresh(self):
        self._refresh_task.start()

    def key_down(self, e):
        key_state = KeyState(e)

        if self.session.terminal.process_key(key_state):
            if pymterm.debug_log:
                logging.getLogger('term_pygui').debug(' processed by term_gui')
            return

        v, handled = term.term_keyboard.translate_key(self.session.terminal,
                                                      key_state)

        if len(v) > 0:
            self.session.send(v)
        elif len(e.char) > 0:
            self.session.send(e.char)
        elif key_state.has_text():
            self.session.send(key_state.get_text())

        if pymterm.debug_log:
            logging.getLogger('term_pygui').debug(
                ' - translated %r, %d' % (v, handled))

        # Return True to accept the key. Otherwise, it will be used by
        # the system.
        return

    def destroy(self):
        self.session.stop()
        super(TerminalPyGUIViewBase, self).destroy()

    def resized(self, delta):
        w, h = self.size

        if w <= 0 or h <= 0:
            return

        w -= self.padding_x * 2
        h -= self.padding_y * 2

        self._calculate_visible_rows(h)
        self._calculate_visible_cols(w)

        if pymterm.debug_log:
            logging.getLogger('term_pygui').debug('on size: cols={} rows={} width={} height={} size={} pos={}'.format(self.visible_cols, self.visible_rows, w, h, self.size, self.position))
        if self.session:
            self.session.resize_pty(self.visible_cols, self.visible_rows, w, h)
            self.session.terminal.resize_terminal()
            if pymterm.debug_log:
                logging.getLogger('term_pygui').debug('on size done: cols={} rows={} width={} height={} size={} pos={}'.format(self.visible_cols, self.visible_rows, w, h, self.size, self.position))

    def _calculate_visible_rows(self, h):
        self.visible_rows = int(h / self._get_line_height())
        if self.visible_rows <= 0:
            self.visible_rows = 1

    def _calculate_visible_cols(self, w):
        self.visible_cols = int(w / self._get_col_width())

        if self.visible_cols <= 0:
            self.visible_cols = 1

    def copy_to_clipboard(self, data):
        application().set_clipboard(data.encode('utf-8'))

    def paste_from_clipboard(self):
        return application().get_clipboard().decode('utf-8')

    def mouse_down(self, event):
        self.become_target()

        self.cancel_selection()

        self._selection_from = self._selection_to = \
            self._get_cursor_from_xy(*event.position)

        mouse_tracker = self.track_mouse()
        while True:
            event = mouse_tracker.next()
            to = self._get_cursor_from_xy(*event.position)

            if to != self._selection_to:
                self._selection_to = to
                self.session.terminal.set_selection(self._selection_from,
                                                    self._selection_to)
                self.refresh()

            if event.kind == 'mouse_up':
                try:
                    mouse_tracker.next()
                except StopIteration:
                    pass
                break

    def _get_cursor_from_xy(self, x, y):
        '''Return the (row, col) of the cursor from an (x, y) position.
        '''
        padding_left = self.padding_x
        padding_top = self.padding_y
        l = self.lines
        dy = self._get_line_height()
        cx = x
        cy = y - padding_top
        cy = int(boundary(round(cy / dy - 0.5), 0, len(l) - 1))

        if cy >= len(l) or cy < 0:
            return 0, 0

        # reserve double width padding char to calculate width
        text = self.norm_text(l[cy].get_text(raw=True), False)
        width_before = 0

        for i in range(0, len(text)):
            if text[i] == '\000':
                continue

            self_width = self._get_col_width()

            if i + 1 < len(text) and text[i + 1] == '\000':
                self_width += self._get_col_width()

            if width_before + self_width * 0.6 + padding_left > cx:
                return i, cy

            width_before += self_width

        return l[cy].cell_count(), cy

    def setup_menus(self, m):
        if self.session and self.session.terminal:
            m.copy_cmd.enabled = self.session.terminal.has_selection()
            m.paste_cmd.enabled = self.session.terminal.has_selection() or application().query_clipboard()
            m.clear_cmd.enabled = self.session.terminal.has_selection()
            m.transfer_file_cmd.enabled = hasattr(self.session, "transfer_file")
        else:
            m.transfer_file_cmd.enabled = False
            m.copy_cmd.enabled = False
            m.paste_cmd.enabled = False
            m.clear_cmd.enabled = False

    def next_handler(self):
        return application().target_window

    def copy_cmd(self):
        if self.session and self.session.terminal:
            self.session.terminal.copy_data()

    def paste_cmd(self):
        if self.session and self.session.terminal:
            self.session.terminal.paste_data()

    @lru_cache(1)
    def _get_col_width(self):
        f = self._get_font()

        col_width = max(map(lambda x:self._get_width(f, x), SINGLE_WIDE_CHARACTERS))

        if pymterm.debug_log:
            logging.getLogger('term_pygui').debug('col_width:{}'.format(col_width))

        return col_width

    def get_prefered_size(self):
        w = int(self._get_col_width() * self.visible_cols + self.padding_x * 2 + 0.5)
        h = int(self._get_line_height() * self.visible_rows + self.padding_y * 2 + 0.5)

        return (w, h)

    def _get_width(self, f = None, t = ''):
        w, h = self._get_size(f, t)
        return w

    def _get_cache_key(self, line):
        return line.get_hash_value()

    def _refresh_font(self, cfg):
        self.font_file, self.font_name, self.font_size = cfg.get_font_info()

    @lru_cache(1)
    def _get_line_height(self):
        f = self._get_font()

        w, h = self._get_size(f, SINGLE_WIDE_CHARACTERS)

        return h + 1


    def _paint_line_surface(self, v_context, line_surf, x, y):
        pass

    def _prepare_line_context(self, line_surf, x, y, width, height):
        pass

    def _layout_line_text(self, context, text, font, l, t, w, h, cur_f_color):
        pass

    def _fill_line_background(self, line_context, cur_b_color, l, t, w, h):
        pass

    def _draw_layouted_line_text(self, line_context, layout, cur_f_color, l, t, w, h):
        pass

    def _do_cache(self):
        return True

    def _draw_canvas(self, v_context):
        def locked_draw_canvas():
            self._real_draw_canvas(v_context)

        self.session.terminal.lock_display_data_exec(locked_draw_canvas)

    def _real_draw_canvas(self, v_context):
        x = self.padding_x
        b_x = self.padding_x
        y = self.padding_y

        lines = self.lines

        c_col, c_row = self.term_cursor

        font = self._get_font();

        line_height = self._get_line_height()
        col_width = int(self._get_col_width())

        width, height = self.size

        for i in range(len(lines)):
            x = b_x = self.padding_x
            line = lines[i]

            col = 0
            last_col = 0
            text = ''

            if self._do_cache():
                key = self._get_cache_key(line)
                cached_line_surf = _get_surf(key, width, line_height)
                line_surf = cached_line_surf.surf

                if cached_line_surf.cached:
                    self._paint_line_surface(v_context, line_surf, 0, y)

                    y += line_height
                    continue

                cached_line_surf.cached = self._do_cache()
            else:
                line_surf = create_line_surface(width, line_height)

            line_context = self._prepare_line_context(line_surf, x, y, width, line_height)

            def render_text(xxxx, cell):
                t = cell.get_char()

                if len(t) == 0:
                    return xxxx

                t = self.norm_text(t)

                if len(t) == 0:
                    return xxxx

                cur_f_color, cur_b_color = self.session.terminal.determin_colors(cell.get_attr())

                wide_char = cell.is_widechar()

                t_w, t_h, layout = self._layout_line_text(line_context, t, font,
                                                              xxxx, y, col_width * 2 if wide_char else col_width, line_height,
                                                              cur_f_color)

                self._draw_layouted_line_text(line_context, layout, cur_f_color, xxxx, 0, t_w, t_h)

                if cell.get_attr().has_mode(TextMode.BOLD):
                    self._draw_layouted_line_text(line_context, layout, cur_f_color, xxxx + 1, 1, t_w, t_h)

                return xxxx + t_w

            last_b_color = self.session.cfg.default_background_color
            last_col = 0
            cur_col = 0

            for cell in line.get_cells():
                if cell.get_char() == '\000':
                    cur_col += 1
                    continue
                cur_f_color, cur_b_color = self.session.terminal.determin_colors(cell.get_attr())
                t_w, t_h, layout = self._layout_line_text(line_context, cell.get_char(), font,
                                                              0, 0, col_width * 2 if cell.is_widechar() else col_width, line_height,
                                                              cur_f_color)

                if cur_b_color != last_b_color:
                    if last_b_color != self.session.cfg.default_background_color and cur_col > last_col:
                        self._fill_line_background(line_context, last_b_color, b_x + last_col * col_width, 0,
                                                       col_width * (cur_col - last_col),
                                                       line_height)
                    last_b_color = cur_b_color
                    last_col = cur_col

                cur_col += 1

            if last_col < cur_col:
                if last_b_color != self.session.cfg.default_background_color:
                    self._fill_line_background(line_context, last_b_color, b_x + last_col * col_width, 0,
                                                   col_width * (cur_col - last_col),
                                                   line_height)

            for cell in line.get_cells():
                if cell.get_char() != ' ':
                    render_text(b_x, cell)

                b_x += col_width

            self._paint_line_surface(v_context, line_surf, 0, y)

            y += line_height
コード例 #4
0
class TerminalPyGUIViewBase(TerminalWidget):

    def __init__(self, **kwargs):
        self.padding_x = 5
        self.padding_y = 5
        self.session = None
        self.selection_color = [0.1843, 0.6549, 0.8313, .5]
        self._width_cache = {}
        self._lock = threading.Lock()
        self._refresh_task = Task(self.__refresh, .02, False, False)

        TerminalWidget.__init__(self, **kwargs)
        
        self._generic_tabbing = False

    def _get_color(self, color_spec):
        key = repr(color_spec)
        if key in _color_map:
            return _color_map[key]

        c = map(lambda x: x / 255, map(float, color_spec))

        _color_map[key] = r = rgb(*c)

        return r

    def __refresh(self):
        if self.session and not self.session.stopped:
            logging.getLogger('term_pygui').debug('refresh called')
            self.invalidate()
            self.update()

    def refresh(self):
        self._refresh_task.start()

    def key_down(self, e):
        key = term_pygui_key_translate.translate_key(e)

        keycode = (e.char, key)
        text = key if len(key) == 1 and key[0] in string.printable else e.char if len(e.char) > 0 else None
        modifiers = []

        if e.option:
            modifiers.append('alt')
        if e.control:
            modifiers.append('ctrl')
        if e.shift:
            modifiers.append('shift')

        logging.getLogger('term_pygui').debug('view key_down:{}'.format(e))
        logging.getLogger('term_pygui').debug('view key_down:{}, {}, {}'.format(keycode, text, modifiers))
        if self.session.terminal.process_key(keycode,
                                             text,
                                             modifiers):
            logging.getLogger('term_pygui').debug(' processed by term_gui')
            return

        v, handled = term.term_keyboard.translate_key(self.session.terminal,
                                                 keycode,
                                                 text,
                                                 modifiers)

        if len(v) > 0:
            self.session.send(v)
        elif len(e.char) > 0:
            self.session.send(e.char)
        elif text:
            self.session.send(text)

        logging.getLogger('term_pygui').debug(' - translated %r, %d' % (v, handled))

        # Return True to accept the key. Otherwise, it will be used by
        # the system.
        return

    def destroy(self):
        self.session.stop()
        super(TerminalPyGUIViewBase, self).destroy()

    def resized(self, delta):
        w, h = self.size

        if w <= 0 or h <=0:
            return

        w -= self.padding_x * 2
        h -= self.padding_y * 2

        self._calculate_visible_rows(h)
        self._calculate_visible_cols(w)

        logging.getLogger('term_pygui').debug('on size: cols={} rows={} width={} height={} size={} pos={}'.format(self.visible_cols, self.visible_rows, w, h, self.size, self.position))
        if self.session:
            self.session.resize_pty(self.visible_cols, self.visible_rows, w, h)
            self.session.terminal.resize_terminal()
            logging.getLogger('term_pygui').debug('on size done: cols={} rows={} width={} height={} size={} pos={}'.format(self.visible_cols, self.visible_rows, w, h, self.size, self.position))

    def _calculate_visible_rows(self, h):
        self.visible_rows = int(h / self._get_line_height())
        if self.visible_rows <= 0:
            self.visible_rows = 1

    def _calculate_visible_cols(self, w):
        self.visible_cols = int(w / self._get_col_width())

        if self.visible_cols <= 0:
            self.visible_cols = 1

    def copy_to_clipboard(self, data):
        application().set_clipboard(data.encode('utf-8'))

    def paste_from_clipboard(self):
        return application().get_clipboard().decode('utf-8')

    def mouse_down(self, event):
        self.become_target()

        self.cancel_selection()

        self._selection_from = self._selection_to = self._get_cursor_from_xy(*event.position)

        mouse_tracker = self.track_mouse()
        while True:
            event = mouse_tracker.next()
            self._selection_to = self._get_cursor_from_xy(*event.position)

            self.refresh()

            if event.kind == 'mouse_up':
                try:
                    mouse_tracker.next()
                except StopIteration:
                    pass
                break

    def _get_cursor_from_xy(self, x, y):
        '''Return the (row, col) of the cursor from an (x, y) position.
        '''
        padding_left = self.padding_x
        padding_top = self.padding_y
        l = self.lines
        dy = self._get_line_height()
        cx = x
        cy = y - padding_top
        cy = int(boundary(round(cy / dy - 0.5), 0, len(l) - 1))

        if cy >= len(l) or cy < 0:
            return 0, 0

        text = self.norm_text(''.join(l[cy]), False)#reserve double width padding char to calculate width
        width_before = 0

        for i in range(0, len(text)):
            if text[i] == '\000':
                continue

            self_width = self._get_col_width()

            if i + 1 < len(text) and text[i + 1] == '\000':
                self_width += self._get_col_width()

            if width_before + self_width * 0.6 + padding_left > cx:
                return i, cy

            width_before += self_width

        return len(l[cy]), cy

    def _merge_color(self, c1, c2):
        return [c1[i] * c2[i] for i in range(len(c1))]

    def setup_menus(self, m):
        if self.session and self.session.terminal:
            m.copy_cmd.enabled = self.session.terminal.has_selection()
            m.paste_cmd.enabled = self.session.terminal.has_selection() or application().query_clipboard()
            m.clear_cmd.enabled = self.session.terminal.has_selection()
            m.transfer_file_cmd.enabled = hasattr(self.session, "transfer_file")
        else:
            m.transfer_file_cmd.enabled = False
            m.copy_cmd.enabled = False
            m.paste_cmd.enabled = False
            m.clear_cmd.enabled = False

    def next_handler(self):
        return application().target_window

    def copy_cmd(self):
        if self.session and self.session.terminal:
            self.session.terminal.copy_data()

    def paste_cmd(self):
        if self.session and self.session.terminal:
            self.session.terminal.paste_data()

    @lru_cache(1)
    def _get_col_width(self):
        f = self._get_font()

        col_width = max(map(lambda x:self._get_width(f, x), SINGLE_WIDE_CHARACTERS))

        logging.getLogger('term_pygui').info('col_width:{}'.format(col_width))

        return col_width

    def get_prefered_size(self):
        w = int(self._get_col_width() * self.visible_cols + self.padding_x * 2 + 0.5)
        h = int(self._get_line_height() * self.visible_rows + self.padding_y * 2 + 0.5)

        return (w, h)

    def _get_width(self, f = None, t = ''):
        w, h = self._get_size(f, t)
        return w

    def _get_cache_key(self, line):
        return line.get_hash_value()

    def _get_line_cache_key(self, line):
        return repr(line)

    def _get_line_option_cache_key(self, line_option):
        return repr(line_option)

    def _refresh_font(self, cfg):
        self.font_file, self.font_name, self.font_size = cfg.get_font_info()


    @lru_cache(1)
    def _get_line_height(self):
        f = self._get_font()

        w, h = self._get_size(f, SINGLE_WIDE_CHARACTERS)

        return h + 1


    def _paint_line_surface(self, v_context, line_surf, x, y):
        pass

    def _prepare_line_context(self, line_surf, x, y, width, height):
        pass

    def _layout_line_text(self, context, text, font, l, t, w, h, cur_f_color):
        pass

    def _fill_line_background(self, line_context, cur_b_color, l, t, w, h):
        pass

    def _draw_layouted_line_text(self, line_context, layout, cur_f_color, l, t, w, h):
        pass

    def _do_cache(self):
        return True

    def _draw_canvas(self, v_context):
        x = self.padding_x
        b_x = self.padding_x
        y = self.padding_y

        lines = [line[:] for line in self.lines]
        line_options = [line_option[:] for line_option in self.line_options]

        c_col, c_row = self.term_cursor

        s_f, s_t = self.get_selection()

        s_f_c, s_f_r = s_f
        s_t_c, s_t_r = s_t


        last_f_color = self.session.cfg.default_foreground_color
        last_b_color = self.session.cfg.default_background_color
        last_mode = 0

        font = self._get_font();

        line_height = self._get_line_height()
        col_width = int(self._get_col_width())

        width, height = self.size

        for i in range(len(lines)):
            x = b_x = self.padding_x
            line = lines[i]
            line_option = line_options[i] if i < len(line_options) else []

            last_mode &= ~TextMode.CURSOR
            last_mode &= ~TextMode.SELECTION

            # temprary add cusor and selection mode
            if self.cursor_visible and i == c_row:
                reserve(line_option, c_col + 1, TextAttribute(None, None, None))
                reserve(line, c_col + 1, ' ')
                line_option[c_col].set_mode(TextMode.CURSOR)

            if s_f != s_t:
                if s_f_r == s_t_r and i == s_f_r:
                    reserve(line_option, s_t_c, TextAttribute(None, None, None))
                    for mm in range(s_f_c, s_t_c):
                        line_option[mm].set_mode(TextMode.SELECTION)
                else:
                    if i == s_f_r:
                        reserve(line_option, len(line), TextAttribute(None, None, None))
                        for mm in range(s_f_c, len(line)):
                            line_option[mm].set_mode(TextMode.SELECTION)
                    elif i == s_t_r:
                        reserve(line_option, s_t_c, TextAttribute(None, None, None))
                        for mm in range(0, s_t_c):
                            line_option[mm].set_mode(TextMode.SELECTION)
                    elif i > s_f_r and i < s_t_r:
                        reserve(line_option, len(line), TextAttribute(None, None, None))
                        for mm in range(len(line)):
                            line_option[mm].set_mode(TextMode.SELECTION)

            col = 0
            last_col = 0
            text = ''
            last_option = None

            if self._do_cache():
                key = self._get_cache_key(line, line_option)
                cached_line_surf = _get_surf(key, width, line_height)
                line_surf = cached_line_surf.surf

                if cached_line_surf.cached:
                    self._paint_line_surface(v_context, line_surf, 0, y)

                    y += line_height
                    continue

                cached_line_surf.cached = self._do_cache()
            else:
                line_surf = create_line_surface(width, line_height)

            line_context = self._prepare_line_context(line_surf, x, y, width, line_height)

            def render_text(t, xxxx, wide_char):
                cur_f_color, cur_b_color = last_f_color, last_b_color

                if len(t) == 0:
                    return xxxx

                t = self.norm_text(t)

                if len(t) == 0:
                    return xxxx

                if last_mode & TextMode.REVERSE:
                    cur_f_color, cur_b_color = last_b_color, last_f_color

                if last_mode & TextMode.CURSOR:
                    cur_f_color, cur_b_color = cur_b_color, self.session.cfg.default_cursor_color

                if last_mode & TextMode.SELECTION:
                    cur_f_color = self._merge_color(cur_f_color, self.selection_color)
                    cur_b_color = self._merge_color(cur_b_color, self.selection_color)

                t_w, t_h, layout = self._layout_line_text(line_context, t, font,
                                                              xxxx, y, col_width * 2 if wide_char else col_width, line_height,
                                                              cur_f_color)

                if cur_b_color != self.session.cfg.default_background_color:
                    self._fill_line_background(line_context, cur_b_color, xxxx, 0,
                                                   max(t_w, col_width * 2 if wide_char else col_width),
                                                   t_h)

                self._draw_layouted_line_text(line_context, layout, cur_f_color, xxxx, 0, t_w, t_h)

                return xxxx + t_w

            for col in range(len(line_option)):
                if line_option[col] is None:
                    continue

                if last_option == line_option[col]:
                    continue

                f_color, b_color, mode = line_option[col]

                n_f_color, n_b_color, n_mode = last_f_color, last_b_color, last_mode

                # foreground
                if f_color and len(f_color) > 0:
                    n_f_color = f_color
                elif f_color is None:
                    n_f_color = self.session.cfg.default_foreground_color

                # background
                if b_color and len(b_color) > 0:
                    n_b_color = b_color
                elif b_color is None:
                    n_b_color = self.session.cfg.default_background_color

                #mode
                if mode is not None:
                    n_mode = mode
                else:
                    n_mode &= ~TextMode.CURSOR
                    n_mode &= ~TextMode.SELECTION

                if (n_f_color, n_b_color, n_mode) == (last_f_color, last_b_color, last_mode):
                    continue

                if last_col < col:
                    for r_col in range(last_col, col):
                        if r_col >= len(line):
                            continue

                        wide_char = False
                        if r_col + 1 < len(line):
                            wide_char = line[r_col + 1] == '\000'
                        render_text(line[r_col], b_x, wide_char)
                        b_x += col_width

                last_col = col
                last_option = line_option[col]
                last_f_color, last_b_color, last_mode = n_f_color, n_b_color, n_mode

            if last_col < len(line):
                for r_col in range(last_col, len(line)):
                    wide_char = False
                    if r_col + 1 < len(line):
                        wide_char = line[r_col + 1] == '\000'

                    render_text(line[r_col], b_x, wide_char)
                    b_x += col_width

            self._paint_line_surface(v_context, line_surf, 0, y)

            y += line_height