Passed
Push — master ( 3731d5...59a60b )
by Yoshihiro
02:04
created

PM   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 451
Duplicated Lines 0 %

Test Coverage

Coverage 98.25%

Importance

Changes 0
Metric Value
eloc 233
dl 0
loc 451
ccs 168
cts 171
cp 0.9825
rs 9.68
c 0
b 0
f 0
wmc 34

17 Methods

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