NovelEditor.ProcessingMenu   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 448
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 39
eloc 235
dl 0
loc 448
rs 9.28
c 0
b 0
f 0

18 Methods

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