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