Passed
Push — master ( 63399a...705b0f )
by Yoshihiro
02:57
created

NovelEditor.PM.ProcessingMenuClass.font_dialog()   A

Complexity

Conditions 1

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

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