コード例 #1
0
ファイル: input.py プロジェクト: saitoha/sentimental-skk
    def __init__(self, session, screen, termenc, 
                 termprop, mousemode, inputmode, offset_x=0, offset_y=0):
        self._screen = screen
        try:
            from cStringIO import StringIO
            output = StringIO()
        except ImportError:
            from StringIO import StringIO
            output = StringIO()

        self._mouse_decoder = mouse.MouseDecoder(screen, termprop, mousemode)
        writer = codecs.getwriter(termenc)
        output = writer(output, errors='ignore')
        self._output = output
        self._termenc = termenc
        self._charbuf = CharacterContext()
        self._charbuf_alter = CharacterContext()
        self._inputmode = inputmode
        self._wordbuf = word.WordBuffer(termprop)
        self._listbox = Listbox(self, screen, termprop, mousemode, self._mouse_decoder)
        self._termprop = termprop
        self._mousemode = mousemode
        self._stack = []
        self._session = session
        self._screen.setlistener(self)

        y, x = screen.getyx()
        self._listbox.setposition(x, y)

        # detects libvte + Ambiguous=narrow environment
        self._selectmark = _SKK_MARK_SELECT
        self._bracket_left = _SKK_MARK_OPEN
        self._bracket_right = _SKK_MARK_CLOSE
        if not termprop.is_cjk and termprop.is_vte():
            pad = u' '
            self._selectmark += pad
            self._bracket_left += pad
            self._bracket_right += pad

        self._command_map = {
            u'@pass'                     : self._pass,
            u'@ignore'                   : self._ignore,
            u'@skk-cancel-pass'          : self._skk_cancel_pass,
            u'@skk-j-mode-off'           : self._skk_j_mode_off,
            u'@skk-kakutei-key'          : self._skk_kakutei_key,
            u'@skk-cancel'               : self._skk_cancel,
            u'@skk-abbrev-mode'          : self._start_abbrev,
            u'@skk-toggle-kana'          : self._skk_toggle_kana,
            u'@skk-start-eisuu'          : self._skk_start_eisuu,
            u'@skk-back'                 : self._skk_back,
            u'@skkmenu-prev'             : self._skkmenu_prev,
            u'@skkmenu-next'             : self._skkmenu_next,
            u'@skkwm-switch'             : self._skkwm_task_switch,
            u'@skkwm-prev'               : self._skkwm_prev,
            u'@skkwm-next'               : self._skkwm_next,
            u'@skkwm-blur'               : self._skkwm_blur,
            u'@skkwm-left'               : self._skkwm_left,
            u'@skkwm-down'               : self._skkwm_down,
            u'@skkwm-up'                 : self._skkwm_up,
            u'@skkwm-right'              : self._skkwm_right,
            u'@skksh-start'              : self._skksh_start,
            u'@skkconf-start'            : self._skkconf_start,
            u'@skk-set-henkan-point-subr': self._skk_set_henkan_point_subr,
            u'@skkapp-wikipedia'         : self._skkapp_wikipedia,
            u'@skk-delete-candidate'     : self._skk_delete_candidate,
            u'@skk-move-prev-clause'     : self._skk_move_prev_clause,
            u'@skk-move-next-clause'     : self._skk_move_next_clause,
            u'@skk-henkan'               : self._skk_henkan,
        }
コード例 #2
0
ファイル: input.py プロジェクト: saitoha/sentimental-skk
class InputHandler(tff.DefaultHandler,
                   IScreenListenerImpl,
                   IListboxListenerImpl,
                   IInnerFrameListenerImpl,
                   TitleTrait):

    _stack = None
    _prev_length = 0
    _selected_text = None
    _okuri = ""
    _bracket_left = _SKK_MARK_OPEN
    _bracket_right = _SKK_MARK_CLOSE
    _clauses = None
    _iframe = None
    _optimize = False

    def __init__(self, session, screen, termenc, 
                 termprop, mousemode, inputmode, offset_x=0, offset_y=0):
        self._screen = screen
        try:
            from cStringIO import StringIO
            output = StringIO()
        except ImportError:
            from StringIO import StringIO
            output = StringIO()

        self._mouse_decoder = mouse.MouseDecoder(screen, termprop, mousemode)
        writer = codecs.getwriter(termenc)
        output = writer(output, errors='ignore')
        self._output = output
        self._termenc = termenc
        self._charbuf = CharacterContext()
        self._charbuf_alter = CharacterContext()
        self._inputmode = inputmode
        self._wordbuf = word.WordBuffer(termprop)
        self._listbox = Listbox(self, screen, termprop, mousemode, self._mouse_decoder)
        self._termprop = termprop
        self._mousemode = mousemode
        self._stack = []
        self._session = session
        self._screen.setlistener(self)

        y, x = screen.getyx()
        self._listbox.setposition(x, y)

        # detects libvte + Ambiguous=narrow environment
        self._selectmark = _SKK_MARK_SELECT
        self._bracket_left = _SKK_MARK_OPEN
        self._bracket_right = _SKK_MARK_CLOSE
        if not termprop.is_cjk and termprop.is_vte():
            pad = u' '
            self._selectmark += pad
            self._bracket_left += pad
            self._bracket_right += pad

        self._command_map = {
            u'@pass'                     : self._pass,
            u'@ignore'                   : self._ignore,
            u'@skk-cancel-pass'          : self._skk_cancel_pass,
            u'@skk-j-mode-off'           : self._skk_j_mode_off,
            u'@skk-kakutei-key'          : self._skk_kakutei_key,
            u'@skk-cancel'               : self._skk_cancel,
            u'@skk-abbrev-mode'          : self._start_abbrev,
            u'@skk-toggle-kana'          : self._skk_toggle_kana,
            u'@skk-start-eisuu'          : self._skk_start_eisuu,
            u'@skk-back'                 : self._skk_back,
            u'@skkmenu-prev'             : self._skkmenu_prev,
            u'@skkmenu-next'             : self._skkmenu_next,
            u'@skkwm-switch'             : self._skkwm_task_switch,
            u'@skkwm-prev'               : self._skkwm_prev,
            u'@skkwm-next'               : self._skkwm_next,
            u'@skkwm-blur'               : self._skkwm_blur,
            u'@skkwm-left'               : self._skkwm_left,
            u'@skkwm-down'               : self._skkwm_down,
            u'@skkwm-up'                 : self._skkwm_up,
            u'@skkwm-right'              : self._skkwm_right,
            u'@skksh-start'              : self._skksh_start,
            u'@skkconf-start'            : self._skkconf_start,
            u'@skk-set-henkan-point-subr': self._skk_set_henkan_point_subr,
            u'@skkapp-wikipedia'         : self._skkapp_wikipedia,
            u'@skk-delete-candidate'     : self._skk_delete_candidate,
            u'@skk-move-prev-clause'     : self._skk_move_prev_clause,
            u'@skk-move-next-clause'     : self._skk_move_next_clause,
            u'@skk-henkan'               : self._skk_henkan,
        }

    def _reset(self):
        self._listbox.close()
        self._inputmode.endabbrev()
        self._wordbuf.reset()
        self._charbuf.reset()
        self._okuri = u''
        self._clauses = None

    def _draincharacters(self):
        charbuf = self._charbuf
        s = charbuf.getbuffer()
        if s == u'n':
            charbuf.put(0x6e)  # n
        s = charbuf.drain()
        return s

    def _iscooking(self):
        if self._clauses:
            return True
        if not self._wordbuf.isempty():
            return True
        if not self._charbuf.isempty():
            return True
        return False

    def _convert_word(self):
        key = self._wordbuf.get()
        if not key:
            return False

        if self._inputmode.iskata():
            key = kanadb.to_hira(key)

        result = dictionary.gettango(key)

        self._okuri = u''

        clauses = dictionary.Clauses()
        if result or key[0] == u'@':
            clauses.add(dictionary.Clause(key, result))
        elif not settings.get('cgi-api.enabled'):
            clauses.add(dictionary.Clause(key, [key]))
        elif not dictionary.get_from_cgi_api(clauses, key):
            clauses.add(dictionary.Clause(key, [key]))

        candidates = clauses.getcandidates()
        self._listbox.assign(candidates)
        self._clauses = clauses
        self.settitle(key)
        return True

    def _convert_okuri(self, nn=u''):

        clauses = self._clauses
        wordbuf = self._wordbuf
        charbuf = self._charbuf

        buf = charbuf.getbuffer()
        if not buf:
            return False
        okuri = nn + self._draincharacters()
        self._okuri = okuri
        if nn:
            buf = 'n'
        else:
            buf = buf[0]
        key = wordbuf.get()

        if self._inputmode.iskata():
            key = kanadb.to_hira(key)
        result = dictionary.getokuri(key + buf)
        clauses = dictionary.Clauses()

        self._draw_nothing(self._output)
        if result or key[0] == u'@':
            clauses.add(dictionary.Clause(key, result))
        else:
            if settings.get('cgi-api.enabled'):
                dictionary.get_from_cgi_api(clauses, key)
            if not clauses:
                clauses.add(dictionary.Clause(key, [key]))
            clauses.add(dictionary.Clause(okuri, [okuri]))
            self._okuri = u''

        self._clauses = clauses
        self._listbox.assign(clauses.getcandidates())
        wordbuf.startedit()

        if self._inputmode.iskata():
            key = kanadb.to_kata(key)
        self.settitle(u'%s - %s' % (key, buf))

        return True

    def _settle(self, context):
        ''' 確定 '''
        clauses = self._clauses
        wordbuf = self._wordbuf
        if clauses:
            key = clauses.getkey()
            remark = clauses.getcurrentremark()
            if key.startswith(u'@') and remark.startswith(u'builtin:'):
                self._dispatch_builtin_command(remark)
                word = u''
            else:
                for clause in clauses:
                    key = clause.getkey()
                    value = clause.getcurrentvalue()
                    dictionary.feedback(key, value)
                okuri = self._okuri
                if len(clauses) > 1:
                    key = clauses.getkey()
                    value = clauses.getvalue()
                    dictionary.feedback(key, value)
                word = clauses.getvalue() + okuri
            self._clauses = None
            self._okuri = u''
        else:
            s = self._draincharacters()
            word = wordbuf.get()
            if word.startswith(u'@'):
                self._convert_word()
                return
            word += s
            if word.startswith(u'@'):
                word = u''
        if word.startswith(u'$'):
            command = word[1:]
            self.open_with_command(command, context)
            word = u''
            return

        title.setmessage(u'\(^o^)/')
        self._refleshtitle()
        self._listbox.close()
        self._inputmode.endabbrev()
        wordbuf.reset()
        context.putu(word)

        #word_length = self._termprop.wcswidth(word)
        #screen = self._screen
        #y, x = screen.getyx()
        #screen._region.sub(x - word_length, y, word_length, 1)


    def _showpopup(self):
        ''' 次候補 '''
        wordbuf = self._wordbuf
        if wordbuf.has_okuri():
            # 送り有り変換
            self._convert_okuri()
            return
        # 送り無し変換
        s = self._draincharacters()
        wordbuf.append(s)
        self._convert_word()

    def _complete(self):
        charbuf = self._charbuf
        wordbuf = self._wordbuf
        listbox = self._listbox
        key = charbuf.complete()
        completions = wordbuf.suggest(key)
        if completions:
            listbox.assign(completions, -1)

    def _dispatch_builtin_command(self, command):
        self._reset()
        magic, category, key, value = command.split(':')
        if magic == 'builtin':
            if category == 'settings':
                try:
                    value = eval(value)
                except e:
                    logging.exception(e)
                    return False
                if key == 'romanrule':
                    self._charbuf.compile(value)
                    self._charbuf_alter.compile(value)
                elif key == 'use_title':
                    title.setenabled(value)
                settings.set(key, value)
                settings.save()
                return True
            elif category == 'task':
                if key == 'switch':
                    window_id = int(value)
                    if window_id == 0:
                        self._session.blur_process()
                    else:
                        self._draw_nothing(self._output)
                        self._inputmode.reset()
                        self._reset()
                        self._screen.taskswitch(window_id)

            return False
        return False

    def open_wikipedia(self, word, context):
        import urllib
        url = "http://ja.wikipedia.org/wiki/"
        url += urllib.quote_plus(word.encode('utf-8'))
        command = "w3m '%s'" % url
        self.open_with_command(command, context)

    def initialize_mouse(self, window):
        self._mouse_decoder.initialize_mouse(window)

    def uninitialize_mouse(self, window):
        self._mouse_decoder.uninitialize_mouse(window)

    def open_with_command(self, command, context):
        screen = self._screen
        session = self._session
        termprop = self._termprop
        termenc = self._termenc

        height = min(20, int(screen.height * 0.7))
        width = min(60, int(screen.width * 0.7))

        import random
        left = random.randint(0 + 1, screen.width - (width + 2) + 1)
        top = random.randint(0 + 1, screen.height - (height + 2) + 1)

        self._draw_nothing(self._output)
        self._inputmode.reset()
        self._reset()

        self._iframe = InnerFrame(self._session,
                                  self,
                                  screen,
                                  top, left, height, width,
                                  command,
                                  termenc,
                                  termprop)

    # override
    def handle_char(self, context, c):

        wordbuf = self._wordbuf
        charbuf = self._charbuf
        listbox = self._listbox
        inputmode = self._inputmode
        clauses = self._clauses
        mouse_decoder = self._mouse_decoder

        if not inputmode.getenabled():
            return False

        if mouse_decoder.handle_char(context, c):
            return True

        if clauses and listbox.handle_char(context, c):
            return True

        if inputmode.handle_char(context, c):
            return True

        if charbuf.handle_char(context, c):
            return True

        if self._handle_nonascii_char(context, c):
            return True

        if inputmode.isabbrev():
            # abbrev mode
            if charbuf.test(c):
                charbuf.save()
                charbuf.put(c)
                s = charbuf.drain()
                charbuf.reset()
                if s.startswith('@'):
                    if self._dispatch_command(context, c, s):
                        return True

            wordbuf.append(unichr(c))
#            charbuf.reset()
            self._complete()
            return True

        # ひらがな変換モード・カタカナ変換モード
        if charbuf.test(c):
            charbuf.save()
            charbuf.put(c)
            # a - z @
            # 小文字のとき
            # 先行する入力があるか
            if wordbuf.isempty():
                s = charbuf.drain()
                if s.startswith('@'):
                    if self._dispatch_command(context, c, s):
                        return True
                    return False
                context.putu(s)
                if clauses:
                    self._optimize = True
                return True
            if wordbuf.has_okuri():
                # 送り仮名変換
                self._convert_okuri()
                return True
            s = charbuf.getbuffer()
            if s in u'、。,.:;[],.:;[]':
                self._convert_okuri()
                return True
            s = charbuf.drain()
            if s.startswith('@'):
                if self._dispatch_command(context, c, s):
                    return True
                return False
            wordbuf.append(s)
            self._complete()
            return True

        if 0x41 <= c and c <= 0x5a:
            # A - Z
            # 大文字のとき
            # 先行する入力があるか
            c += 0x20
            if wordbuf.isempty() or not wordbuf.get():
                # ない
                wordbuf.startedit()
                charbuf.put(c)
                if charbuf.isfinal():  # 文字バッファに溜める
                    s = charbuf.drain()
                    wordbuf.append(s)
                    self._complete()
                return True
            # ある
            s = charbuf.getbuffer()
            nn = u''
            if s == u'n' and c != 0x4e:
                charbuf.put(0x6e)  # n
                nn = charbuf.drain()
                charbuf.reset()
            charbuf.put(c)
            # 先行する入力があるとき、送り仮名マーク('*')をつける
            wordbuf.startokuri()
            # キャラクタバッファが終了状態か
            if charbuf.isfinal():
                # 送り仮名変換
                self._convert_okuri(nn)
            return True

        #charbuf.reset()
        charbuf_alter = self._charbuf_alter
        if charbuf_alter.test(c):
            charbuf_alter.put(c)
            s = charbuf_alter.drain()
            if s.startswith('@'):
                #wordbuf.reset()
                #charbuf.reset()
                #listbox.close()
                self._dispatch_command(context, c, s)
                return True
            #wordbuf.reset()
            charbuf.reset()
            #listbox.close()
            self.handle_char(context, c)
            return True
        #wordbuf.reset()
        charbuf.reset()
        #listbox.close()
        #if wordbuf.isempty():
        #    context.puts(chr(c))
        #else:
        #    wordbuf.append(chr(c))

        return True  # handled

    def _handle_nonascii_char(self, context, c):
        if c > 0x7f:
            self._wordbuf.append(unichr(c))
            return True
        return False

    def _pass(self, context, c):
        return False

    def _ignore(self, context, c):
        return True

    def _skksh_start(self, context, c):
        if not self._iscooking():
            self._wordbuf.startedit()
            self._inputmode.startabbrev()
            self._wordbuf.append(u'$')
            self._complete()
            return True
        return False

    def _skkconf_start(self, context, c):
        if not self._iscooking():
            self._wordbuf.startedit()
            self._inputmode.startabbrev()
            self._wordbuf.append('@')
            self._complete()
            return True
        return False

    def _start_abbrev(self, context, c):
        if not self._iscooking():
            self._inputmode.startabbrev()
            wordbuf = self._wordbuf
            wordbuf.reset()
            wordbuf.startedit()
            #wordbuf.append(' ')
            return True
        return False

    def _skk_j_mode_off(self, context, c):
        inputmode = self._inputmode
        if inputmode.isabbrev():
            return False
        if self._listbox.isshown():
            self._settle(context)
        inputmode.reset()
        self._reset()
        return True

    def _skk_start_eisuu(self, context, c):
        if self._iscooking():
            self._settle(context)
        self._inputmode.startzen()
        self._reset()
        return True

    def _skk_toggle_kana(self, context, c):
        wordbuf = self._wordbuf
        inputmode = self._inputmode
        if self._iscooking():
            s = self._draincharacters()
            wordbuf.append(s)
            word = wordbuf.get()
            self._reset()
            if inputmode.ishira():
                s = kanadb.to_kata(word)
            else:
                s = kanadb.to_hira(word)
            context.putu(s)
            return True
        else:
            self._charbuf.toggle()
            if inputmode.ishira():
                inputmode.startkata()
            elif inputmode.iskata():
                inputmode.starthira()
            self._reset()
            return True
        return False

    def _skk_kakutei_key(self, context, c):
        if self._iscooking():
            self._settle(context)
            return True
        return False

    def _skk_set_henkan_point_subr(self, context, c):
        wordbuf = self._wordbuf
        listbox = self._listbox

        if listbox.isshown():
            listbox.close()

        if self._inputmode.isabbrev():
            word = wordbuf.get()
            word = eisuudb.to_zenkaku(word)
            context.putu(word)
            self._inputmode.endabbrev()
            wordbuf.reset()
            return True
        if not wordbuf.isempty():
            s = self._draincharacters()
            word = wordbuf.get()
            str_hankata = kanadb.to_hankata(word + s)
            context.putu(str_hankata)
            wordbuf.reset()
            return True
        return False

    def _skkwm_task_switch(self, context, c):
        clauses = dictionary.Clauses()
        value = [u'%%0 メインウィンドウ;builtin:task:switch:%d' % 0]
        for window in self._screen.enumwindows():
            label = window.getlabel()
            if label:
                value.append(u'%%%d %s;builtin:task:switch:%d' % (window.id, label, window.id))
        clauses.add(dictionary.Clause(u'', value))
        candidates = clauses.getcandidates()
        self._listbox.assign(candidates)
        self._clauses = clauses
        return True

    def _skk_cancel_pass(self, context, c):
        self._reset()
        return False

    def _skk_cancel(self, context, c):
        self._reset()
        return True

    def _skk_back(self, context, c):
        wordbuf = self._wordbuf
        charbuf = self._charbuf
        listbox = self._listbox
        clauses = self._clauses
        if clauses:
            clauses.shift_left()
            candidates = clauses.getcandidates()
            listbox.assign(candidates)
            return True
        if not charbuf.isempty():
            charbuf.back()
            if not charbuf.getbuffer():
                listbox.close()
            else:
                self._complete()
            return True
        if not wordbuf.isempty():
            wordbuf.back()
            if not wordbuf.getbuffer():
                listbox.close()
            else:
                self._complete()
            return True
        return False

    def _skkapp_wikipedia(self, context, c):
        wordbuf = self._wordbuf
        if not wordbuf.isempty():
            word = wordbuf.get()
            self.open_wikipedia(word, context)
            return True
        self._reset()
        return False

    def _skk_delete_candidate(self, context, c):
        # not implemented yet
        return False

    def _skk_henkan(self, context, c):
        wordbuf = self._wordbuf
        charbuf = self._charbuf
        word = wordbuf.get()
        listbox = self._listbox
        if word.startswith(u'$'):
            wordbuf.append(u' ')
            return True 
        if self._clauses:
            listbox.movenext()
            return True 
        if not wordbuf.isempty():
            s = self._draincharacters()
            wordbuf.append(s)
            if wordbuf.length() > 0:
                self._showpopup()
            return True 
        if not charbuf.isempty():
            s = self._draincharacters()
            wordbuf.startedit()
            wordbuf.append(s)
            if wordbuf.length() > 0:
                self._settle(context)
            return True 
        return False

    def _skkwm_prev(self, context, c):
        self._screen.task_prev()
        return True

    def _skkwm_next(self, context, c):
        self._screen.task_next()
        return True

    def _skkwm_blur(self, context, c):
        self._session.blur_process()
        return True

    def _skkwm_left(self, context, c):
        screen = self._screen
        widget = screen.getactivewidget()
        left = widget.left + 1
        top = widget.top - 1
        widget.onmousedown(context, left, top)
        widget.ondragstart(context, left, top)
        widget.ondragmove(context, left - 1, top)
        widget.ondragend(context, left, top)
        self._charbuf.restore()
        return True

    def _skkwm_right(self, context, c):
        screen = self._screen
        widget = screen.getactivewidget()
        left = widget.left + 1
        top = widget.top - 1
        widget.onmousedown(context, left, top)
        widget.ondragstart(context, left, top)
        widget.ondragmove(context, left + 1, top)
        widget.ondragend(context, left, top)
        self._charbuf.restore()
        return True

    def _skkwm_up(self, context, c):
        screen = self._screen
        widget = screen.getactivewidget()
        left = widget.left + 1
        top = widget.top - 1
        widget.onmousedown(context, left, top)
        widget.ondragstart(context, left, top)
        widget.ondragmove(context, left, top - 1)
        widget.ondragend(context, left, top)
        self._charbuf.restore()
        return True

    def _skkwm_down(self, context, c):
        screen = self._screen
        widget = screen.getactivewidget()
        left = widget.left + 1
        top = widget.top - 1
        widget.onmousedown(context, left, top)
        widget.ondragstart(context, left, top)
        widget.ondragmove(context, left, top + 1)
        widget.ondragend(context, left, top)
        self._charbuf.restore()
        return True

    def _skkmenu_complete(self, context, c):
        wordbuf = self._wordbuf
        charbuf = self._charbuf
        listbox = self._listbox
        if not wordbuf.isempty():
            # ワードバッファ編集中
            s = self._draincharacters()
            wordbuf.append(s)
            wordbuf.complete()
            charbuf.reset()
            listbox.movenext()
            return True
        return False

    def _skkmenu_next(self, context, c):
        listbox = self._listbox
        if listbox.isshown():
            listbox.movenext()
            return True
        elif not self._wordbuf.isempty():
            self._showpopup()
            return True
        elif not self._charbuf.isempty():
            self._showpopup()
            return True
        return False

    def _skkmenu_prev(self, context, c):
        listbox = self._listbox
        if listbox.isshown():
            listbox.moveprev()
            return True
        if not self._wordbuf.isempty():
            return True
        if not self._charbuf.isempty():
            return True
        return False

    def _dispatch_command(self, context, c, key):
        if key in self._command_map:
            f = self._command_map[key]
            if f(context, c):
                return True
            #context.write(c)
            return False

        logging.warning('Unknown command: %s' % key)
        return False

    def _handle_amb_report(self, context, parameter, intermediate, final):
        """
        minttyのambiguous width reportingをハンドルする
        http://code.google.com/p/mintty/wiki/CtrlSeqs#Ambiguous_width_reporting
        http://code.google.com/p/mintty/issues/detail?id=88
        """
        if not intermediate:
            if final == 0x57:  # W
                if not parameter:
                    """
                    CSI W
                    """
                    self._termprop.set_nocjk()
                elif parameter[0] == 0x32:
                    """
                    CSI 2 W
                    """
                    self._termprop.set_cjk()
                elif parameter[0] == 0x31:
                    """
                    CSI 1 W
                    """
                    self._termprop.set_nocjk()
                return True
        return False

    def _skk_move_next_clause(self, context, final):
        clauses = self._clauses
        if clauses:
            clauses.movenext()
            result = clauses.getcandidates()

            self._listbox.assign(result)

            return True
        return False

    def _skk_move_prev_clause(self, context, final):
        clauses = self._clauses
        if clauses:
            clauses.moveprev()
            result = clauses.getcandidates()

            self._listbox.assign(result)
            return True
        return False

    def _handle_csi_cursor(self, context, parameter, intermediate, final):
        listbox = self._listbox
        if listbox.isshown():
            if final == 0x43:  # C
                self._skk_move_next_clause(context, final)
                return True
            elif final == 0x44:  # D
                self._skk_move_prev_clause(context, final)
                return True
        return False

    def _handle_ss3_cursor(self, context, final):
        if self._listbox.isshown():
            if final == 0x43:  # C
                self._skk_move_next_clause(context, final)
                return True
            elif final == 0x44:  # D
                self._skk_move_prev_clause(context, final)
                return True
        return False

    # override
    def handle_csi(self, context, parameter, intermediate, final):
        if not self._inputmode.getenabled():
            return False
        if self._mouse_decoder.handle_csi(context, parameter, intermediate, final):
            return True
        if self._listbox.handle_csi(context, parameter, intermediate, final):
            return True
        if self._handle_csi_cursor(context, parameter, intermediate, final):
            return True
        if self._handle_amb_report(context, parameter, intermediate, final):
            return True
        if not self._wordbuf.isempty():
            return True
        if not self._charbuf.isempty():
            return True
        return False

    def handle_esc(self, context, intermediate, final):
        if not self._inputmode.getenabled():
            return False
        if self._listbox.handle_esc(context, intermediate, final):
            return True
        return False

    def handle_ss3(self, context, final):
        if not self._inputmode.getenabled():
            return False
        if self._listbox.handle_ss3(context, final):
            return True
        if self._handle_ss3_cursor(context, final):
            return True
        if final == 0x5b:  # [
            if self._iscooking():
                self._settle(context)
                self._inputmode.reset()
                self._reset()
            return False
        if not self._wordbuf.isempty():
            return True
        if not self._charbuf.isempty():
            return True
        return False

    def _draw_clauses_with_popup(self, output):
        screen = self._screen
        wcswidth = self._termprop.wcswidth
        listbox = self._listbox
        y, x = screen.getyx()
        cur_width = 0

        clauses = self._clauses
        selected_clause = clauses.getcurrentclause()
        for clause in clauses:
            word = clause.getcurrentvalue()
            if id(clause) == id(selected_clause):
                cur_width += wcswidth(self._selectmark)
                if listbox.is_moved():
                    listbox.set_offset(cur_width, 0)
            cur_width += wcswidth(word)
        if self._okuri:
            cur_width += wcswidth(self._okuri)
        char = self._charbuf.getbuffer()
        cur_width += wcswidth(char)

        if self._prev_length > cur_width:
            length = self._prev_length - cur_width
            width = screen.width
            if x + cur_width + length < width:
                screen.copyline(output, x + cur_width, y, length)
            else:
                screen.copyline(output, 0, y, width)
                if y + 1 < screen.height:
                    screen.copyline(output, 0, y + 1, width)
        self._prev_length = cur_width

        output.write(u'\x1b[%d;%dH' % (y + 1, x + 1))
        for clause in clauses:
            word = clause.getcurrentvalue()
            if id(clause) == id(selected_clause):
                word = self._selectmark + word
                output.write(u'\x1b[0;1;4;31m')
            else:
                output.write(u'\x1b[0;32m')
            output.write(word)
        if self._okuri:
            output.write(u'\x1b[0;32m' + self._okuri)

    def _draw_word(self, output):
        screen = self._screen
        wcswidth = self._termprop.wcswidth
        listbox = self._listbox
        y, x = screen.getyx()
        cur_width = 0

        word = self._wordbuf.getbuffer()
        char = self._charbuf.getbuffer()

        cur_width += wcswidth(word)
        cur_width += wcswidth(char)

        listbox.setposition(x, y)

        if self._prev_length > cur_width:
            length = self._prev_length - cur_width
            width = screen.width
            if x + cur_width + length < width:
                screen.copyline(output, x + cur_width, y, length)
            else:
                screen.copyline(output, 0, y, width)
                if y + 1 < screen.height:
                    screen.copyline(output, 0, y + 1, width)
        self._prev_length = cur_width

        output.write(u'\x1b[%d;%dH' % (y + 1, x + 1))
        output.write(u'\x1b[0;1;4;31m' + word)
        output.write(u'\x1b[0;1;32m' + char)

    def _draw_nothing(self, output):
        screen = self._screen
        length = self._prev_length
        if length > 0:
            y, x = screen.getyx()
            width = screen.width
            if x + length < width:
                screen.copyline(output, x, y, length)
            else:
                screen.copyline(output, 0, y, width)
                if y + 1 < screen.height:
                    screen.copyline(output, 0, y + 1, width)
            output.write('\x1b[%d;%dH' % (y + 1, x + 1))
            self._prev_length = 0

    def handle_resize(self, context, row, col):
        y, x = self._screen.getyx()
        self._listbox.setposition(x, y)

    # override
    def handle_draw(self, context):
        if not self._inputmode.getenabled():
            return False

        output = self._output
        clauses = self._clauses
        iframe = self._iframe
        screen = self._screen
        listbox = self._listbox
        wordbuf = self._wordbuf
        charbuf = self._charbuf

        widget = screen.getactivewidget()
        output.write('\x1b[?25l')

        try:
            screen.drawwindows(context)

            if self._optimize:
                self._optimize = False
            elif clauses and not listbox.isempty():
                self._draw_clauses_with_popup(output)
            elif not wordbuf.isempty():
                self._draw_word(output)
            elif not charbuf.isempty():
                self._draw_word(output)
            else:
                self._draw_nothing(output)

            self._refleshtitle()

            if widget:
                widget.drawcursor()
            else:
                screen.cursor.draw(output)

            buf = output.getvalue()
            if buf:
                context.puts(buf)
                output.truncate(0)

        finally:
            if widget:
                if widget.innerscreen.dectcem:
                    context.puts('\x1b[?25h')
            else:
                if screen.dectcem:
                    context.puts('\x1b[?25h')