Passed
Push — master ( acaf9b...fbbf97 )
by Yoshihiro
44s queued 10s
created

PM   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 452
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 233
dl 0
loc 452
rs 9.68
c 0
b 0
f 0
wmc 34

17 Methods

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