Test Failed
Push — master ( 32b012...756e6e )
by Yoshihiro
07:24 queued 04:56
created

neditor.pm.ProcessingMenuClass.pyttsx3_onreadend()   A

Complexity

Conditions 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 5
nop 1
dl 0
loc 10
ccs 5
cts 5
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
#!/usr/bin/env python3
2 1
import re
3 1
import textwrap
4 1
import threading
5 1
import itertools
6 1
import webbrowser
7 1
import tkinter as tk
8
import tkinter.ttk as ttk
9 1
import tkinter.messagebox as messagebox
10 1
import xml.etree.ElementTree as ET
11 1
12
import jaconv
13 1
import pyttsx3
14
import requests
15
16 1
from . import main
17
18
19
class ProcessingMenuClass(main.MainClass):
20
    """処理メニューバーのクラス.
21
22
    ・処理メニューバーにあるプログラム群
23
24
    Args:
25
        app (instance): MainProcessingClass のインスタンス
26
        tokenizer (instance): Tokenizer のインスタンス
27
        wiki_wiki (instance): wikipediaapi.Wikipedia のインスタンス
28 1
        locale_var (str): ロケーション
29
        master (instance): toplevel のインスタンス
30 1
    """
31
    font_size = 0
32
    """フォントのサイズ."""
33 1
    yahoo_appid = ""
34 1
    """Yahoo! Client ID"""
35
36 1
    def __init__(self, app, tokenizer, wiki_wiki, locale_var, master=None):
37 1
        super().__init__(locale_var, master)
38 1
        # yahooの校正支援
39 1
        self.KOUSEI = "{urn:yahoo:jp:jlp:KouseiService}"
40
        self.app = app
41 1
        self.tokenizer = tokenizer
42
        self.wiki_wiki = wiki_wiki
43
44
    def ruby_huri(self):
45
        """ルビをふり.
46 1
47
        ・選択文字列に小説家になろうのルビを振る。
48 1
        """
49
        hon = ""
50 1
        # 選択文字列を切り取る
51
        set_ruby = self.app.text.get('sel.first', 'sel.last')
52 1
        # 選択文字列を削除する
53
        self.app.text.delete('sel.first', 'sel.last')
54 1
        # 形態素解析を行う
55 1
        for token in self.tokenizer.tokenize(set_ruby):
56
            # ルビの取得
57 1
            ruby = ""
58 1
            ruby = jaconv.kata2hira(token.reading)
59 1
            # 解析している文字のひらがなの部分を取得
60
            hira = ""
61
            for i in token.surface:
62 1
                if self.is_hiragana(i):
63
                    hira += i
64
            # ルビがないときと、記号の時の処理
65
            if ruby.replace(
66
                hira, ''
67
            ) == "" or token.part_of_speech.split(
68
                ","
69
            )[0] == self.dic.get_dict("symbol"):
70 1
                hon += token.surface
71
            else:
72
                # ルビ振りを行う
73
                hon += "|{0}≪{1}≫{2}".format(
74
                    token.surface.replace(hira, ''),
75
                    ruby.replace(hira, ''),
76
                    hira
77 1
                )
78
79 1
        # テキストを表示する
80
        self.app.text.insert('insert', hon)
81
82
    @staticmethod
83
    def is_hiragana(char):
84
        """文字がひらがなか判断.
85
86
        ・与えられた文字がひらがなかどうか判断する。
87
88
        Args:
89
            char (str): 判断する文字
90
91 1
        Returns:
92
            bool: ひらがなならTrue、違うならFalse
93 1
        """
94
        return (0x3040 < ord(char) < 0x3097)
95
96
    def count_moji(self):
97
        """文字数と行数を表示.
98
99 1
        ・文字数と行数をカウントして表示する。
100
        """
101 1
        # 行数の取得
102
        new_line = int(self.app.text.index('end-1c').split('.')[0])
103 1
        # 文字列の取得
104 1
        moji = self.app.text.get('1.0', 'end')
105 1
        # 20文字で区切ったときの行数を数える
106
        gen_mai = 0
107 1
        for val in moji.splitlines():
108
            gen_mai += len(textwrap.wrap(val, 20))
109
        # メッセージボックスの表示
110
        messagebox.showinfo(
111
            self.app.dic.get_dict("Number of characters etc"),
112
            self.app.dic.get_dict(
113
                "Characters : {0} Lines : {1}\n Manuscript papers : {2}"
114
            )
115
            .format(
116
                len(moji)-new_line,
117 1
                new_line,
118
                -(-gen_mai//20)))
119
120
    def find_wikipedia(self):
121
        """意味を検索.
122
123
        ・Wikipedia-APIライブラリを使ってWikipediaから選択文字の意味を
124 1
        検索する。
125 1
        """
126
        # wikipediaから
127 1
        select_text = self.app.text.selection_get()
128 1
        page_py = self.wiki_wiki.page(select_text)
129
        # ページがあるかどうか判断
130
        if page_py.exists():
131
            messagebox.showinfo(
132
                self.app.dic.get_dict(
133
                    "Meaning of [{0}]"
134
                ).format(select_text),
135 1
                page_py.summary
136
            )
137
        else:
138
            messagebox.showwarning(
139
                self.app.dic.get_dict(
140
                    "Meaning of [{0}]"
141
                ).format(select_text),
142 1
                self.app.dic.get_dict("Can't find.")
143
            )
144
145
    def open_becoming_novelist_page(self):
146
        """小説家になろうのユーザーページを開く.
147 1
148
        ・インターネットブラウザで小説家になろうのユーザーページを開く。
149 1
        """
150
        webbrowser.open("https://syosetu.com/user/top/")
151
152
    def read_text(self):
153
        """テキストを読み上げる.
154 1
155 1
        ・pyttsx3ライブラリを使ってテキストボックスに書かれているものを読み上げる。
156 1
        """
157 1
        self.app.engine = pyttsx3.init()
158 1
        self.speak = Speaking(self.app.text.get(1.0, tk.END),self.app, daemon=True)
159 1
        self.speak.start()
160 1
161 1
    def pyttsx3_onend(self):
162 1
        """文章を読み終えた時の処理.
163 1
164 1
        ・文章を読み終えたら中止ウインドウを削除する。
165
166 1
        """
167
        if self.speak:
168
            self.speak.stop()
169
            self.speak = None
170
171 1
    def yahoo(self):
172
        """Yahoo! 校正支援.
173 1
174
        ・Yahoo! 校正支援を呼び出し表示する。
175
176
        Args:
177
            event (instance): tkinter.Event のインスタンス
178
        """
179
        html = self.yahoocall(
180
            self.app.text.get('1.0', 'end -1c')
181
        )
182
        if not self.yahoo_appid == "":
183
            self.yahooresult(html)
184
            self.yahoo_tree.bind("<Double-1>", self.on_double_click_yahoo)
185 1
186
    def yahoocall(self, sentence=""):
187 1
        """yahooの校正支援を呼び出し.
188
189 1
        ・Yahoo! 校正支援をClient IDを使って呼び出す。
190
191
        Args:
192
            sentence (str): 校正をしたい文字列
193
194
        Returns:
195 1
            str: 校正結果
196
        """
197
        if self.yahoo_appid == "":
198
            messagebox.showerror(
199
                self.app.dic.get_dict("Yahoo! Client ID"),
200
                self.app.dic.get_dict(
201
                    "Yahoo! Client ID is not find."
202 1
                    "\nRead Readme.html and set it again."
203 1
                )
204 1
            )
205
            return
206 1
        url = "https://jlp.yahooapis.jp/KouseiService/V1/kousei"
207
        data = {
208 1
            "appid": self.yahoo_appid.rstrip('\n'),
209
            "sentence": sentence,
210 1
        }
211 1
        html = requests.post(url, data)
212
        return html.text
213
214
    def yahooresult(self, html):
215
        """校正支援を表示する画面を制作.
216
217
        ・校正結果を表示するダイアログを作成する。
218 1
219
        Args:
220 1
            html (str): 校正結果
221
        """
222 1
        xml = ET.fromstring(html)
223 1
        # サブウインドウの表示
224
        sub_win = tk.Toplevel(self.app)
225
        # ツリービューの表示
226 1
        self.yahoo_tree = ttk.Treeview(sub_win)
227
        self.yahoo_tree["columns"] = (1, 2, 3, 4, 5)
228 1
        # 表スタイルの設定(headingsはツリー形式ではない、通常の表形式)
229
        self.yahoo_tree["show"] = "headings"
230
        self.yahoo_tree.column(1, width=100)
231
        self.yahoo_tree.column(2, width=80)
232
        self.yahoo_tree.column(3, width=75)
233
        self.yahoo_tree.column(4, width=150)
234 1
        self.yahoo_tree.column(5, width=120)
235 1
        self.yahoo_tree.heading(
236 1
            1,
237 1
            text=self.dic.get_dict("Number of characters from the beginning")
238
        )
239 1
        self.yahoo_tree.heading(
240
            2,
241
            text=self.dic.get_dict("Number of target characters")
242
        )
243
        self.yahoo_tree.heading(
244
            3,
245
            text=self.dic.get_dict("Target notation")
246
        )
247
        self.yahoo_tree.heading(
248 1
            4,
249 1
            text=self.dic.get_dict("Paraphrase candidate string")
250 1
        )
251 1
        self.yahoo_tree.heading(
252
            5,
253 1
            text=self.dic.get_dict("Detailed information on the pointed out")
254
        )
255
        # 情報を取り出す
256
        for child in list(xml):
257
            StartPos = (child.findtext(self.KOUSEI+"StartPos"))
258
            Length = (child.findtext(self.KOUSEI+"Length"))
259
            Surface = (child.findtext(self.KOUSEI+"Surface"))
260
            ShitekiWord = (child.findtext(self.KOUSEI+"ShitekiWord"))
261 1
            ShitekiInfo = (child.findtext(self.KOUSEI+"ShitekiInfo"))
262
            self.yahoo_tree.insert(
263
                "",
264 1
                "end",
265 1
                values=(StartPos,
266 1
                        Length,
267
                        Surface,
268 1
                        ShitekiWord,
269
                        ShitekiInfo
270
                        )
271
                )
272
273
        self.yahoo_tree.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.E, tk.W))
274
        # スクロールバーを表示する
275
        SCRLBAR_Y = ttk.Scrollbar(
276
            sub_win,
277
            orient=tk.VERTICAL,
278
            command=self.yahoo_tree.yview
279 1
        )
280 1
        self.yahoo_tree.configure(yscroll=SCRLBAR_Y.set)
281
        SCRLBAR_Y.grid(row=0, column=1, sticky=(tk.N, tk.S))
282
        # 最前面に表示し続ける
283
        sub_win.attributes("-topmost", True)
284
        sub_win.title(
285
            self.app.dic.get_dict("Sentence structure")
286
        )
287 1
288 1
    def on_double_click_yahoo(self, event=None):
289 1
        """Yahoo! 校正支援リストをダブルクリック.
290
291
        ・Yahoo! 校正支援ダイアログのリストをダブルクリックすると
292
        その該当箇所を選択する。
293 1
294 1
        Args:
295
            event (instance): tkinter.Event のインスタンス
296 1
        """
297
        i = 0
298
        textlen = 0
299
        textforlen = 0
300
        curItem = self.yahoo_tree.focus()
301
        value = self.yahoo_tree.item(curItem)
302
        # 出てくる場所を取得
303
        val = int(value.get("values")[0])
304 1
        # 出てくる文字数を取得
305
        lenge = value.get("values")[1]
306 1
        # 何行目になるか確認する
307
        while True:
308 1
            if val > textlen:
309 1
                i += 1
310
                textforlen = textlen
311 1
                textlen += len(
312 1
                    self.app.text.get(
313 1
                        '{0}.0'.format(i),
314 1
                        '{0}.0'.format(i+1)
315 1
                    )
316 1
                )
317 1
            else:
318
                break
319
        if i == 0:
320
            i = 1
321 1
        # 選択状態を一旦削除
322
        self.app.text.tag_remove('sel', '1.0', 'end')
323
        # 選択状態にする
324
        self.app.text.tag_add(
325 1
            'sel',
326
            "{0}.{1}".format(i, val-textforlen),
327
            "{0}.{1}".format(i, val-textforlen+lenge)
328
        )
329 1
        # カーソルの移動
330
        self.app.text.mark_set('insert', '{0}.{1}'.format(i, val-textforlen))
331
        self.app.text.see('insert')
332
        # フォーカスを合わせる
333 1
        self.app.text.focus()
334
        return
335
336
    def font_dialog(self, event=None):
337
        """フォントサイズダイアログを作成.
338 1
339 1
        ・フォントサイズダイアログを作成し表示する。
340 1
341 1
        Args:
342 1
            event (instance): tkinter.Event のインスタンス
343 1
        """
344 1
        self.app.sub_wins = tk.Toplevel(self.app)
345
        self.app.intSpin = ttk.Spinbox(self.app.sub_wins, from_=12, to=72)
346
        self.app.intSpin.grid(
347
            row=0,
348
            column=0,
349
            columnspan=2,
350
            padx=5,
351
            pady=5,
352
            sticky=tk.W+tk.E,
353
            ipady=3
354
        )
355 1
        button = ttk.Button(
356
            self.app.sub_wins,
357 1
            text=self.app.dic.get_dict("Resize"),
358
            width=str(self.app.dic.get_dict("Resize")),
359
            padding=(10, 5),
360
            command=self.font_size_Change
361
        )
362 1
        button.grid(row=1, column=1)
363 1
        self.app.intSpin.set(ProcessingMenuClass.font_size)
364
        self.app.sub_wins.title(
365 1
            self.app.dic.get_dict("Font size")
366 1
        )
367
368
    def font_size_Change(self):
369
        """フォントのサイズの変更.
370 1
371
        ・サイズ変更を押されたときにサイズを変更する。
372
        上は72ptまで下は12ptまでにする。
373
        """
374
        # 比較のため数値列に変更
375
        font_size = int(self.app.intSpin.get())
376
        if font_size < 12:  # 12より下の値を入力した時、12にする
377
            font_size = 12
378
        elif 72 < font_size:  # 72より上の値を入力した時、72にする
379 1
            font_size = 72
380 1
        # 文字列にもどす
381 1
        self.font_size_input(str(font_size))
382 1
        self.app.sub_wins.destroy()
383 1
        # フォントサイズの変更
384
        self.app.text.configure(font=(self.app.font, self.font_size))
385 1
        # ラインナンバーの変更
386
        self.app.spc.update_line_numbers()
387 1
        # ハイライトのやり直し
388
        self.app.hpc.all_highlight()
389 1
390 1
    @classmethod
391 1
    def font_size_input(cls, font_size):
392 1
        """フォントサイズを入力.
393 1
394
        ・フォントサイズをクラス変数に入力する。
395
396
        Args:
397
            font_size (str): フォントサイズ
398
        """
399
        cls.font_size = font_size
400 1
401 1
402
class Speaking(threading.Thread):
403
    """テキスト読み上げのクラス.
404 1
405
    ・テキスト読み上げのプログラム群
406 1
407
    Args:
408
        sentence (str): テキストデータ
409
        app (instance): MainProcessingClass のインスタンス
410
        **kwargs (dict): 複数のキーワード引数を辞書として受け取る
411
    """
412 1
    def __init__(self, sentence, app, **kwargs):
413 1
        super().__init__(**kwargs)
414
        self.app = app
415 1
        # 改行と点丸で分割(行ごとの二次元配列)
416 1
        self.words = [re.split("、|。", text) for text in sentence.splitlines()]
417
        # 二次元配列を一次元配列に変換
418 1
        self.words = list(itertools.chain.from_iterable(self.words))
419
        self.paused = False
420
421
    def run(self):
422
        """読み上げを実行.
423
424
        ・テキストの読み上げを始める。
425
426 1
        """
427 1
        self.running = True
428 1
        # 読むのを中止するウインドウを作成する
429
        self.sub_read_win = tk.Toplevel(self.app)
430
        button = ttk.Button(
431
            self.sub_read_win,
432
            text=self.app.dic.get_dict("Stop"),
433
            width=str(self.app.dic.get_dict("Stop")),
434
            padding=(100, 5),
435
            command=self.stop
436
        )
437 1
        button.grid(row=1, column=1)
438
        # 最前面に表示し続ける
439
        self.sub_read_win.attributes("-topmost", True)
440
        # サイズ変更禁止
441
        self.sub_read_win.resizable(False, False)
442
        self.sub_read_win.title(
443
            self.app.dic.get_dict("Read aloud")
444 1
        )
445 1
        # 頭から読み上げる
446 1
        pos = [0,0]
447
        while self.words and self.running:
448
            if not self.paused:
449
                # 読み上げ場所の選択
450 1
                word = self.words.pop(0)
451
                pos[1] = len(word) + 1 + pos[1]
452
                if pos[1]-pos[0]>1:
453
                    start = '0.0 + {0}c'.format(pos[0])
454
                    end = '0.0 + {0}c'.format(pos[1])
455
                    self.app.text.tag_add('sel', start, end)
456
                    self.app.text.mark_set('insert', end)
457 1
                    self.app.text.see('insert')
458 1
                    self.app.text.focus()
459 1
                    # 読み上げ開始
460 1
                    self.app.engine.say(word)
461 1
                    self.app.engine.runAndWait()
462
                    self.app.text.tag_remove('sel', '1.0', 'end')
463 1
                    # 読み上げ終了
464 1
                pos[0] = pos[1]
465
        self.running = False
466 1
        self.sub_read_win.destroy()
467
        self.app.text.tag_remove('sel', '1.0', 'end')
468 1
469
    def stop(self):
470 1
        self.running = False
471
        self.sub_read_win.destroy()
472
        self.app.text.tag_remove('sel', '1.0', 'end')
473