1# vim:set et sts=4 sw=4:
2# -*- coding: utf-8 -*-
3#
4# ibus-anthy - The Anthy engine for IBus
5#
6# Copyright (c) 2007-2008 Peng Huang <shawn.p.huang@gmail.com>
7# Copyright (c) 2010-2017 Takao Fujiwara <takao.fujiwara1@gmail.com>
8# Copyright (c) 2007-2017 Red Hat, Inc.
9#
10# This program is free software; you can redistribute it and/or modify
11# it under the terms of the GNU General Public License as published by
12# the Free Software Foundation; either version 2 of the License, or
13# (at your option) any later version.
14#
15# This program is distributed in the hope that it will be useful,
16# but WITHOUT ANY WARRANTY; without even the implied warranty of
17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18# GNU General Public License for more details.
19#
20# You should have received a copy of the GNU General Public License along
21# with this program; if not, write to the Free Software Foundation, Inc.,
22# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23
24import romaji
25import kana
26import thumb
27
28from segment import unichar_half_to_full
29
30HalfSymbolTable = {}
31for i in range(32, 127):
32    if not chr(i).isalnum():
33        HalfSymbolTable[unichar_half_to_full(chr(i))] = chr(i)
34
35HalfNumberTable = {}
36for i in range(10):
37    HalfNumberTable[unichar_half_to_full(str(i))] = str(i)
38
39PeriodTable = {'。': '.', '、': ',', '。': '.', '、': ','}
40
41SymbolTable = {}
42SymbolTable[0] = {'「': '「', '」': '」', '/': '/'}
43SymbolTable[1] = {'「': '「', '」': '」', '/': '・'}
44SymbolTable[2] = {'「': '[', '」': ']', '/': '/'}
45SymbolTable[3] = {'「': '[', '」': ']', '/': '・'}
46
47TYPING_MODE_ROMAJI, \
48TYPING_MODE_KANA, \
49TYPING_MODE_THUMB_SHIFT = list(range(3))
50
51class JaString:
52    _prefs = None
53    _mode = TYPING_MODE_ROMAJI
54    _shift = False
55    _unshift = False
56
57    def __init__(self, mode=TYPING_MODE_ROMAJI, latin_with_shift=True):
58        self._init_mode(mode)
59        if mode == TYPING_MODE_ROMAJI:
60            romaji.RomajiSegment.SET_LATIN_WITH_SHIFT(latin_with_shift)
61
62    @classmethod
63    def _init_mode(cls, mode):
64        cls._mode = mode
65        cls._shift = False
66        cls._unshift = False
67        cls.__cursor = 0
68        cls.__segments = list()
69        if mode == TYPING_MODE_ROMAJI:
70            romaji.RomajiSegment.INIT_ROMAJI_TYPING_RULE(cls._prefs)
71        elif mode == TYPING_MODE_KANA:
72            kana.KanaSegment.INIT_KANA_TYPING_RULE(cls._prefs)
73        elif mode == TYPING_MODE_THUMB_SHIFT:
74            thumb.ThumbShiftSegment.INIT_THUMB_TYPING_RULE(cls._prefs)
75
76    @classmethod
77    def SET_PREFS(cls, prefs):
78        cls._prefs = prefs
79
80    @classmethod
81    def RESET(cls, prefs, section, key, value):
82        cls._prefs = prefs
83        if section == 'kana-typing-rule':
84            mode = TYPING_MODE_KANA
85            kana.KanaSegment.RESET(prefs, section, key, value)
86            cls._init_mode(mode)
87        if section == 'common' and key == 'latin-with-shift':
88            romaji.RomajiSegment.SET_LATIN_WITH_SHIFT(value)
89
90    def set_shift(self, shift):
91        self._shift = shift
92
93    def set_hiragana_katakana(self, mode):
94        if mode and self._mode == TYPING_MODE_ROMAJI:
95            self._unshift = True
96
97    def insert(self, c):
98        segment_before = None
99        segment_after = None
100        new_segments = None
101
102        if self.__cursor >= 1:
103            segment_before = self.__segments[self.__cursor - 1]
104        if self.__cursor < len(self.__segments):
105            segment_after = self.__segments[self.__cursor]
106        if segment_before and not segment_before.is_finished():
107            if type(segment_before) == romaji.RomajiSegment:
108                new_segments = segment_before.append(c,
109                                                     self._shift,
110                                                     self._unshift)
111                self._unshift = False
112            else:
113                new_segments = segment_before.append(c)
114        elif segment_after and not segment_after.is_finished():
115            if type(segment_after) == romaji.RomajiSegment:
116                new_segments = segment_after.prepend(c,
117                                                     self._shift,
118                                                     self._unshift)
119                self._unshift = False
120            else:
121                new_segments = segment_after.prepend(c)
122        else:
123            if c != '\0' and c != '':
124                if self._mode == TYPING_MODE_ROMAJI:
125                    new_segments = [romaji.RomajiSegment(c,
126                                                         '',
127                                                         self._shift,
128                                                         self._unshift)]
129                    self._unshift = False
130                elif self._mode == TYPING_MODE_KANA:
131                    # kana mode doesn't have shift latin in MS.
132                    new_segments = [kana.KanaSegment(c)]
133                elif self._mode == TYPING_MODE_THUMB_SHIFT:
134                    new_segments = [thumb.ThumbShiftSegment(c)]
135        if new_segments:
136            self.__segments[self.__cursor:self.__cursor] = new_segments
137            self.__cursor += len(new_segments)
138
139    def remove_before(self):
140        index = self.__cursor - 1
141        if index >= 0:
142            segment = self.__segments[index]
143            segment.pop()
144            if segment.is_empty():
145                del self.__segments[index]
146                self.__cursor = index
147            return True
148
149        return False
150
151    def remove_after(self):
152        index = self.__cursor
153        if index < len(self.__segments):
154            segment = self.__segments[index]
155            segment.pop()
156            if segment.is_empty():
157                del self.__segments[index]
158            return True
159
160        return False
161
162    def get_string(self, type):
163        pass
164
165    def move_cursor(self, delta):
166        self.__cursor += delta
167        if self.__cursor < 0:
168            self.__cursor = 0
169        elif self.__cursor > len(self.__segments):
170            self.__cursor = len(self.__segments)
171
172    # hiragana segments are not char lengths.
173    # e.g. 'ya' is 1 segment and 1 char and 'kya' is 1 segment and 2 chars.
174    def move_cursor_hiragana_length(self, length):
175        delta = length
176        if delta < 0:
177            if self.__cursor >= len(self.__segments):
178                delta = delta + (self.__cursor - len(self.__segments) + 1)
179                self.__cursor = len(self.__segments) - 1
180            while delta < 0:
181                text = str(self.__segments[self.__cursor].to_hiragana())
182                if len(text) > -delta:
183                    break
184                delta = delta + len(text)
185                self.__cursor = self.__cursor - 1
186        else:
187            if self.__cursor >= len(self.__segments):
188                self.__cursor = len(self.__segments)
189                return
190            while delta > 0:
191                text = str(self.__segments[self.__cursor].to_hiragana())
192                if len(text) > delta:
193                    break
194                delta = delta - len(text)
195                self.__cursor = self.__cursor + 1
196
197    def move_cursor_katakana_length(self, length):
198        delta = length
199        if delta < 0:
200            if self.__cursor >= len(self.__segments):
201                delta = delta + (self.__cursor - len(self.__segments) + 1)
202                self.__cursor = len(self.__segments) - 1
203            while delta < 0:
204                text = str(self.__segments[self.__cursor].to_katanaka())
205                if len(text) > -delta:
206                    break
207                delta = delta + len(text)
208                self.__cursor = self.__cursor - 1
209        else:
210            if self.__cursor >= len(self.__segments):
211                self.__cursor = len(self.__segments)
212                return
213            while delta > 0:
214                text = str(self.__segments[self.__cursor].to_katanaka())
215                if len(text) > delta:
216                    break
217                delta = delta - len(text)
218                self.__cursor = self.__cursor + 1
219
220    def move_cursor_half_with_katakana_length(self, length):
221        delta = length
222        if delta < 0:
223            if self.__cursor >= len(self.__segments):
224                delta = delta + (self.__cursor - len(self.__segments) + 1)
225                self.__cursor = len(self.__segments) - 1
226            while delta < 0:
227                text = str(self.__segments[self.__cursor].to_half_width_katakana())
228                if len(text) > -delta:
229                    break
230                delta = delta + len(text)
231                self.__cursor = self.__cursor - 1
232        else:
233            if self.__cursor >= len(self.__segments):
234                self.__cursor = len(self.__segments)
235                return
236            while delta > 0:
237                text = str(self.__segments[self.__cursor].to_half_width_katakana())
238                if len(text) > delta:
239                    break
240                delta = delta - len(text)
241                self.__cursor = self.__cursor + 1
242
243    def _chk_text(self, s):
244        period = self._prefs.get_value('common', 'period-style')
245        symbol = self._prefs.get_value('common', 'symbol-style')
246        half_symbol = self._prefs.get_value('common', 'half-width-symbol')
247        half_number = self._prefs.get_value('common', 'half-width-number')
248        ret = ''
249        for c in s:
250            c = c if not period else PeriodTable.get(c, c)
251            # thumb_left + '2' and '/' are different
252            if self._mode != TYPING_MODE_THUMB_SHIFT:
253                c = c if not symbol else SymbolTable[symbol].get(c, c)
254            c = c if not half_symbol else HalfSymbolTable.get(c, c)
255            c = c if not half_number else HalfNumberTable.get(c, c)
256            ret += c
257        return ret
258
259    def get_hiragana(self, commit=False):
260        conv = lambda s: s.to_hiragana()
261        R = lambda s: s if not (commit and s[-1:] == 'n') else s[:-1] + 'ん'
262        text_before = R(''.join(map(conv, self.__segments[:self.__cursor])))
263        text_after = R(''.join(map(conv, self.__segments[self.__cursor:])))
264        return self._chk_text(text_before + text_after), len(text_before)
265
266    def get_katakana(self, commit=False):
267        conv = lambda s: s.to_katakana()
268        R = lambda s: s if not (commit and s[-1:] == 'n') else s[:-1] + 'ン'
269        text_before = R(''.join(map(conv, self.__segments[:self.__cursor])))
270        text_after = R(''.join(map(conv, self.__segments[self.__cursor:])))
271        return self._chk_text(text_before + text_after), len(text_before)
272
273    def get_half_width_katakana(self, commit=False):
274        conv = lambda s: s.to_half_width_katakana()
275        R = lambda s: s if not (commit and s[-1:] == 'n') else s[:-1] + 'ン'
276        text_before = R(''.join(map(conv, self.__segments[:self.__cursor])))
277        text_after = R(''.join(map(conv, self.__segments[self.__cursor:])))
278        return self._chk_text(text_before + text_after), len(text_before)
279
280    def get_latin(self):
281        conv = lambda s: s.to_latin()
282        text_before = ''.join(map(conv, self.__segments[:self.__cursor]))
283        text_after = ''.join(map(conv, self.__segments[self.__cursor:]))
284        return text_before + text_after, len(text_before)
285
286    def get_wide_latin(self):
287        conv = lambda s: s.to_wide_latin()
288        text_before = ''.join(map(conv, self.__segments[:self.__cursor]))
289        text_after = ''.join(map(conv, self.__segments[self.__cursor:]))
290        return text_before + text_after, len(text_before)
291
292    def is_empty(self):
293        return all([s.is_empty() for s in self.__segments])
294
295    def get_raw(self, start, end):
296        i = 0
297        r = ''
298        for s in self.__segments:
299            if i >= end:
300                break
301            elif start <= i:
302                r += s.to_latin()
303            i += len(s.to_hiragana())
304        return r
305