OSDN Git Service

オランダ語プラグイン追加。
[wordring-tm/wordring-tm.git] / proxy / tmeditorwidget.cpp
1 #include "tmeditorwidget.h"
2 #include "tmservice.h"
3 #include "tmsocket.h"
4 #include "tmtext.h"
5
6 #include "settings.h"
7
8 #include <QToolBar>
9 #include <QAction>
10 #include <QMenu>
11 #include <QIcon>
12 #include <QDesktopServices>
13
14 #include <QVBoxLayout>
15
16 #include <QMutex>
17 #include <QMutexLocker>
18
19 #include <QTextEdit>
20 #include <QTextDocument>
21 #include <QTextCursor>
22 #include <QTextBlock>
23 #include <QTextBlockFormat>
24
25 #include <QBrush>
26 #include <QFont>
27 #include <QMimeData>
28 #include <QLocale>
29
30 #include <QList>
31
32 #include <memory>
33
34 #include "debug.h"
35
36 TM::EditorWidget::EditorWidget(Settings *settings, Service *service, QWidget *parent)
37         : QWidget(parent)
38         , m_mutex(QMutex::Recursive)
39         , m_service(service)
40         , m_settings(settings)
41         , m_http_port(80)
42         , m_socket(nullptr)
43 {
44         QVBoxLayout *vlayout = new QVBoxLayout(this);
45         vlayout->setSpacing(4);
46         vlayout->setContentsMargins(0, 0, 0, 0);
47
48         m_toolbar = new QToolBar("editor", this);
49
50         m_edit_mode = new QAction(QIcon(":/edit.png"), "edit", this);
51         m_edit_mode->setCheckable(true);
52         m_toolbar->addAction(m_edit_mode);
53         connect(m_edit_mode, SIGNAL(triggered(bool)), this, SLOT(onEditModeTriggered(bool)));
54
55         m_link = new QAction(QIcon(":/link.png"), "link", this);
56         m_link->setCheckable(true);
57         m_link->setDisabled(true);
58         m_toolbar->addAction(m_link);
59         connect(m_link, SIGNAL(triggered(bool)), this, SLOT(onLinkModeTriggered(bool)));
60
61         m_slang = new QAction("&source", this);
62         //m_slang->setDisabled(true);
63         m_slang->setMenu(new QMenu(this));
64         m_tlang = new QAction("&target", this);
65         //m_tlang->setDisabled(true);
66         m_tlang->setMenu(new QMenu(this));
67         m_toolbar->addAction(m_slang);
68         m_toolbar->addAction(m_tlang);
69
70         QAction *browser = new QAction(QIcon(":/browser.png"), "browser", this);
71         m_toolbar->addAction(browser);
72         connect(browser, SIGNAL(triggered(bool)), this, SLOT(onBrowserTriggered(bool)));
73
74         QAction *setting = new QAction(QIcon(":/setting.png"), "setting", this);
75         m_toolbar->addAction(setting);
76
77         m_edit = new Editor(settings, service, this);
78         vlayout->addWidget(m_toolbar);
79         vlayout->addWidget(m_edit);
80
81         connect(m_service, SIGNAL(languageLoaded(int, QString, QIcon)),
82                         this, SLOT(onLanguageLoaded(int,QString,QIcon)));
83 }
84
85 /*!
86  * \brief ブラウザ上でドキュメントがフォーカスを取得したとき、
87  * SocketConnectionから呼び出されます。
88  */
89 void TM::EditorWidget::attach(SocketConnection *socket)
90 {
91         if(m_socket == socket) return;
92         if(m_socket) detach(m_socket);
93
94         QMutexLocker lock(&m_mutex);
95         m_socket = socket;
96         connect(this, SIGNAL(editModeChanged(bool)), m_socket, SLOT(changeEditMode(bool)));
97         set_link_mode_disabled(true);
98         connect(this, SIGNAL(sourceLanguageChanged()), m_socket, SLOT(changeLanguage()));
99         connect(this, SIGNAL(targetLanguageChanged()), m_socket, SLOT(changeLanguage()));
100 }
101
102 /*!
103  * \brief ブラウザ上でドキュメントがフォーカスを取得することで、
104  * 現在のSocketConnectionがフォーカスを失った時に呼び出されます。
105  */
106 void TM::EditorWidget::detach(SocketConnection *)
107 {
108         QMutexLocker lock(&m_mutex);
109         disconnect(this, SIGNAL(editModeChanged(bool)), m_socket, SLOT(changeEditMode(bool)));
110         disconnect(this, SIGNAL(sourceLanguageChanged()), m_socket, SLOT(changeLanguage()));
111         disconnect(this, SIGNAL(targetLanguageChanged()), m_socket, SLOT(changeLanguage()));
112
113         m_edit->clear();
114         m_socket = nullptr;
115 }
116
117 void TM::EditorWidget::set_http_port(quint16 http_port) { m_http_port = http_port; }
118
119 /*!
120  * \brief 編集モードを変更します。
121  */
122 void TM::EditorWidget::set_edit_mode(bool mode)
123 {
124         QMutexLocker lock(&m_mutex);
125         bool old_mode = m_edit_mode->isChecked();
126         if(old_mode == mode) return;
127
128         m_edit_mode->setChecked(mode);
129 }
130
131 /*!
132  * \brief 編集モードの場合trueを返します。
133  */
134 bool TM::EditorWidget::edit_mode()
135 {
136         QMutexLocker lock(&m_mutex);
137         return m_edit_mode->isChecked();
138 }
139
140 /*!
141  * \brief リンクモードを設定します。
142  */
143 void TM::EditorWidget::set_link_mode(bool mode)
144 {
145         QMutexLocker lock(&m_mutex);
146         bool old_mode = m_link->isChecked();
147         if(old_mode == mode) return;
148
149         m_link->setChecked(mode);
150 }
151
152 /*!
153  * \brief リンクモードの場合、trueを返します。
154  */
155 bool TM::EditorWidget::link_mode()
156 {
157         QMutexLocker lock(&m_mutex);
158         return m_link->isChecked();
159 }
160
161 /*!
162  * \brief リンクボタンをグレーアウトします。
163  */
164 void TM::EditorWidget::set_link_mode_disabled(bool disable)
165 {
166         QMutexLocker lock(&m_mutex);
167         m_link->setDisabled(disable);
168 }
169
170 /*!
171  * \brief 原文の言語コードを返します。
172  */
173 int TM::EditorWidget::source_language()
174 {
175         QMutexLocker lock(&m_mutex);
176         return m_slang->data().toInt();
177 }
178
179 /*!
180  * \brief 訳文の言語コードを返します。
181  */
182 int TM::EditorWidget::target_language()
183 {
184         QMutexLocker lock(&m_mutex);
185         return m_tlang->data().toInt();
186 }
187
188 void TM::EditorWidget::set_segment(TextSegment::pointer segment)
189 {
190         QMutexLocker lock(&m_mutex);
191         m_edit->set_segment(segment);
192 }
193
194 /*!
195  * \brief SocketConnectionにセンテンスの保存を要求します。
196  * \param segment_id セグメントのID
197  * \param index センテンスのインデックス
198  *
199  * 結果としてデータベース、サーバ、ブラウザを更新します。
200  */
201 void TM::EditorWidget::save_sentence(int segment_id, int index)
202 {
203         m_socket->save_sentence(segment_id, index);
204 }
205
206 /*!
207  * \brief SocketConnectionにセンテンスのクリアを要求します。
208  * \param segment_id セグメントのID
209  * \param index センテンスのインデックス
210  *
211  * 結果としてデータベース、サーバ、ブラウザを更新します。
212  *
213  * チケット #35438 訳文を元に戻せない 対応
214  */
215 void TM::EditorWidget::remove_sentence(int segment_id, int index)
216 {
217         m_socket->remove_sentence(segment_id, index);
218 }
219
220 /*!
221  * \brief 言語プラグインが読み込まれるたびに呼び出されます。
222  * \param code 言語コード。
223  * \param name 言語名。
224  * \param icon 言語を表すアイコン。
225  */
226 void TM::EditorWidget::onLanguageLoaded(int code, QString name, QIcon icon)
227 {
228         if(!m_settings->contains("Widget/default_source_language"))
229                         m_settings->setValue("Widget/default_source_language", QLocale::Language::English);
230         if(!m_settings->contains("Widget/default_target_language"))
231                         m_settings->setValue("Widget/default_target_language", QLocale::Language::Japanese);
232
233         int scode = m_settings->value("Widget/default_source_language").toInt();
234         int tcode = m_settings->value("Widget/default_target_language").toInt();
235         if(code == scode)
236         {
237                 m_slang->setIcon(icon);
238                 m_slang->setData(code);
239         }
240         if(code == tcode)
241         {
242                 m_tlang->setIcon(icon);
243                 m_tlang->setData(code);
244         }
245         QAction *saction =  m_slang->menu()->addAction(icon, name);
246         saction->setData(code);
247         connect(saction, SIGNAL(triggered(bool)), this, SLOT(onSourceLanguageTriggered(bool)));
248         QAction *taction = m_tlang->menu()->addAction(icon, name);
249         taction->setData(code);
250         connect(taction, SIGNAL(triggered(bool)), this, SLOT(onTargetLanguageTriggered(bool)));
251 }
252
253 /*!
254  * \brief 編集アイコンがクリックされたとき呼び出されます。
255  */
256 void TM::EditorWidget::onEditModeTriggered(bool)
257 {
258         QAction *edit_mode = qobject_cast<QAction*>(sender());
259         bool checked = edit_mode->isChecked();
260         m_link->setDisabled(true);
261         //m_slang->setDisabled(!checked);
262         //m_tlang->setDisabled(!checked);
263         emit editModeChanged(checked);
264
265         m_edit->set_edit_mode(checked);
266 }
267
268 /*!
269  * \brief リンクアイコンがクリックされたとき呼び出されます。
270  */
271 void TM::EditorWidget::onLinkModeTriggered(bool checked)
272 {
273         //QAction *link_mode = qobject_cast<QAction*>(sender());
274         m_edit->set_link_mode(checked);
275 }
276
277 /*!
278  * \brief アクションから原文言語が変更されたとき呼び出されます。
279  */
280 void TM::EditorWidget::onSourceLanguageTriggered(bool)
281 {
282         QAction *saction = qobject_cast<QAction*>(sender());
283         assert(saction);
284         m_slang->setIcon(saction->icon());
285         m_slang->setData(saction->data());
286
287         m_settings->setValue("Widget/default_source_language", saction->data());
288
289         emit sourceLanguageChanged();
290 }
291
292 /*!
293  * \brief アクションから訳文言語が変更されたとき呼び出されます。
294  */
295 void TM::EditorWidget::onTargetLanguageTriggered(bool)
296 {
297         QAction *taction = qobject_cast<QAction*>(sender());
298         assert(taction);
299         m_tlang->setIcon(taction->icon());
300         m_tlang->setData(taction->data());
301
302         m_settings->setValue("Widget/default_target_language", taction->data());
303
304         emit targetLanguageChanged();
305 }
306
307 /*!
308  * \brief ブラウザ・アイコンがクリックされたとき呼び出されます。
309  */
310 void TM::EditorWidget::onBrowserTriggered(bool)
311 {
312         QString url("http://localhost:");
313         url += QString::number(m_http_port) + "/";
314
315         QDesktopServices::openUrl(QUrl(url));
316 }
317
318 // EditorPanel ----------------------------------------------------------------
319
320 TM::EditorPanel::EditorPanel(QWidget *parent)
321         : TextPanel(parent)
322         , m_parent_editor(nullptr)
323 {
324 }
325
326 TM::Editor* TM::EditorPanel::parent_editor() { return m_parent_editor; }
327
328 /*!
329  * \brief 親となるエディタを設定します。
330  */
331 void TM::EditorPanel::set_parent_editor(Editor *editor)
332 {
333         assert(!m_parent_editor);
334         m_parent_editor = editor;
335 }
336
337 /*!
338  * \brief 文を再表示します。
339  */
340 void TM::EditorPanel::ensure_sentence()
341 {
342         if(!sentence()) return;
343         clear();
344         QTextCursor c = textCursor();
345         for(Text::pointer p = sentence()->begin(); p; p = p->next()) // p=word
346         {
347                 QTextCharFormat cf;
348                 QVariant v = QVariant::fromValue(Text::weak_pointer(p));
349                 cf.setProperty(Word, v);
350                 c.insertText(p->string(), cf);
351         }
352 }
353
354 /*!
355  * \brief 内容が空の場合、trueを返します。
356  */
357 bool TM::EditorPanel::is_empty() const { return document()->isEmpty(); }
358
359 /*!
360  * \brief 引数として与えられた単語に該当する範囲を選択するカーソルを返します。
361  */
362 QTextCursor TM::EditorPanel::select_cursor(Text::pointer word)
363 {
364         RangeData *rd = static_cast<RangeData*>(word->data().get());
365         assert(rd);
366         int slength = word->string().length();
367         QTextCursor c = textCursor();
368         c.movePosition(QTextCursor::Start);
369         c.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, rd->begin());
370         c.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, slength);
371         return c;
372 }
373
374 /*!
375  * \brief 引数として与えられた位置にある単語を返します。
376  */
377 Text::pointer TM::EditorPanel::select_word(QPoint const &pos)
378 {
379         QTextCursor c = cursorForPosition(pos);
380         // Textを取得する。
381         QVariant v = c.charFormat().property(Word);
382         if(!v.isValid()) return Text::pointer();
383         Text::weak_pointer wp = v.value<Text::weak_pointer>();
384         assert(!wp.expired());
385         Text::pointer p = wp.lock();
386         return p;
387 }
388
389 /*!
390  * \brief wordの範囲に背景色colorを設定します。
391  *
392  * 透過色は、Qt::transparentです。
393  */
394 void TM::EditorPanel::highlight(Text::pointer word, QColor color)
395 {
396         QTextCursor c = select_cursor(word);
397         QTextCharFormat cf = c.charFormat();
398         cf.setBackground(color);
399         c.mergeCharFormat(cf);
400 }
401
402 void TM::EditorPanel::highlight(WordLink::storage_type *link, QColor color)
403 {
404         for(Text::pointer p : *link) highlight(p, color);
405 }
406
407 void TM::EditorPanel::clear_highlight() { ensure_sentence(); }
408
409 /*!
410  * \brief 引数として与えられた整数値から色を作成します。
411  *
412  * 色分け用に使えるQColorを返します。
413  */
414 QColor TM::EditorPanel::color(int index) const
415 {
416         QColor result;
417         result.setHsv(index * 199, 128, 128, 128);
418         return result;
419 }
420
421 // SourcePanel ----------------------------------------------------------------
422
423 TM::SourcePanel::SourcePanel(QWidget *parent)
424         : EditorPanel(parent)
425         , m_index(-1)
426         , m_target_panel(nullptr)
427 {
428         setUndoRedoEnabled(false);
429         setContextMenuPolicy(Qt::NoContextMenu);
430 }
431
432 int TM::SourcePanel::index() const { return m_index; }
433
434 void TM::SourcePanel::set_index(int index) { m_index = index; }
435
436 Text::pointer TM::SourcePanel::sentence() { return source_sentence(); }
437
438 Text::pointer TM::SourcePanel::source_sentence()
439 {
440         return m_text_sentence->source_sentence();
441 }
442
443 Text::pointer TM::SourcePanel::target_sentence()
444 {
445         return m_text_sentence->target_sentence();
446 }
447
448 void TM::SourcePanel::set_target_sentence(Text::pointer sentence)
449 {
450         m_text_sentence->set_target_sentence(sentence);
451 }
452
453 TM::TextSentence::pointer TM::SourcePanel::text_sentence()
454 {
455         return m_text_sentence;
456 }
457
458 void TM::SourcePanel::set_text_sentence(TextSentence::pointer text_sentence)
459 {
460         m_text_sentence = text_sentence;
461         ensure_sentence();
462         assert(target_panel());
463         target_panel()->ensure_sentence();
464 }
465
466 TM::TargetPanel* TM::SourcePanel::target_panel() { return m_target_panel; }
467
468 void TM::SourcePanel::set_target_panel(TargetPanel *target)
469 {
470         m_target_panel = target;
471 }
472
473 void TM::SourcePanel::commit_link()
474 {
475         linker()->commit();
476         clear_highlight();
477         m_target_panel->clear_highlight();
478 }
479
480 TM::WordLinker* TM::SourcePanel::linker()
481 {
482         return m_text_sentence->linker();
483 }
484
485 /*!
486  * \brief 保持するリンクを色分け表示します。
487  */
488 void TM::SourcePanel::ensure_highlight()
489 {
490         clear_highlight();
491         m_target_panel->clear_highlight();
492
493         WordLink::pointer wl = linker()->current();
494         if(wl)
495         {
496                 highlight(wl->sources(), Qt::cyan);
497                 target_panel()->highlight(wl->targets(), Qt::cyan);
498         }
499
500         for(WordLink::pointer wl : *linker())
501         {
502                 int index = linker()->index_of(wl);
503                 QColor c = color(index);
504                 highlight(wl->sources(), c);
505                 target_panel()->highlight(wl->targets(), c);
506         }
507 }
508
509 bool TM::SourcePanel::canInsertFromMimeData(const QMimeData *) const
510 {
511         return false; // ドロップ禁止カーソルにする
512 }
513
514 void TM::SourcePanel::insertFromMimeData(const QMimeData *) { } // ドロップされても何もしない
515
516 void TM::SourcePanel::inputMethodEvent(QInputMethodEvent *ev)
517 {
518         ev->setCommitString(""); // 入力禁止
519         TextPanel::inputMethodEvent(ev);
520 }
521
522 void TM::SourcePanel::keyPressEvent(QKeyEvent *ev)
523 {
524         Editor *editor = parent_editor();
525         int key = ev->key();
526         switch(key)
527         {
528         case Qt::Key_Enter:
529         case Qt::Key_Return:
530                 if(editor->link_mode()) editor->set_link_mode(false);
531                 break;
532         case Qt::Key_C:
533                 if(!ev->modifiers().testFlag(Qt::ControlModifier)) break;
534         case Qt::Key_Left:
535         case Qt::Key_Right:
536         case Qt::Key_Up:
537         case Qt::Key_Down:
538                 TextPanel::keyPressEvent(ev);
539                 break;
540         }
541 }
542
543 void TM::SourcePanel::do_click(QPoint const &pos)
544 {
545         Editor *editor = parent_editor();
546         if(editor->link_mode()) do_click_in_link_mode(pos);
547 }
548
549 void TM::SourcePanel::do_click_in_link_mode(QPoint const &pos)
550 {
551         Text::pointer w = select_word(pos);
552         if(!w) return;
553
554         linker()->toggle(WordLink::Source, w);
555         ensure_highlight();
556         setTextCursor(cursorForPosition(pos));
557 }
558
559 // TargetPanel ----------------------------------------------------------------
560
561 TM::TargetPanel::TargetPanel(QWidget *parent)
562         : EditorPanel(parent)
563         , m_source_panel(nullptr)
564         , m_text_dirty(false)
565         , m_text_saved(true)
566 {
567         //setContextMenuPolicy(Qt::NoContextMenu);
568 }
569
570 TM::SourcePanel* TM::TargetPanel::source_panel() { return m_source_panel; }
571
572 void TM::TargetPanel::set_source_panel(SourcePanel *source)
573 {
574         m_source_panel = source;
575 }
576
577 Text::pointer TM::TargetPanel::sentence()
578 {
579         return source_panel()->target_sentence();
580 }
581
582 void TM::TargetPanel::save_sentence()
583 {
584
585 }
586
587 void TM::TargetPanel::ensure_sentence()
588 {
589         EditorPanel::ensure_sentence();
590         set_text_dirty(false);
591         set_text_saved(false);
592 }
593
594 /*!
595  * \brief データベースへの登録が必要な場合、falseを返します。
596  *
597  * パネル内の文字列が編集された場合、trueを返します。
598  * 原文パネルがフォーカスを失うと、エディタがこのメンバを呼び出し、
599  * データベース登録の必要性を判定します。
600  * その後、エディタはデータベース登録を行い、フラグをクリアするので、
601  * 次に文字列が編集されるまでtrueを返し続けます。
602  */
603 bool TM::TargetPanel::is_text_saved() const { return m_text_saved; }
604
605 void TM::TargetPanel::set_text_saved(bool saved)
606 {
607         m_text_saved = saved;
608 }
609
610 /*!
611  * \brief パネル内の文字列が編集された場合、trueを返します。
612  *
613  * エディタがリンクモードに入るとき、このメンバを呼び出し、
614  * 単語へ分割する必要があるか判定します。
615  * エディタはパネルの文字列を単語に分割した後、set_sentence()によって
616  * パネルに文を設定するため、次に文字列を編集するまでfalseを返すようになります。
617  */
618 bool TM::TargetPanel::is_text_dirty() const { return m_text_dirty; }
619
620 /*!
621  * \brief パネルの文字列が編集されたかを示すフラグを設定します。
622  */
623 void TM::TargetPanel::set_text_dirty(bool dirty)
624 {
625         EditorWidget *editor_widget = parent_editor()->parent_editor_widget();
626         m_text_dirty = dirty;
627         if(dirty) editor_widget->set_link_mode_disabled(false);
628 }
629
630 bool TM::TargetPanel::canInsertFromMimeData(QMimeData const *source) const
631 {
632         if(source->hasText()) return true;
633         return false;
634 }
635
636 void TM::TargetPanel::insertFromMimeData(QMimeData const *source)
637 {
638         TextPanel::insertFromMimeData(source);
639         set_text_dirty(true);
640         set_text_saved(false);
641 }
642
643 void TM::TargetPanel::inputMethodEvent(QInputMethodEvent *ev)
644 {
645         Editor *editor = parent_editor();
646         if(editor->link_mode()) ev->setCommitString("");
647         if(!ev->commitString().isEmpty())
648         {
649                 set_text_dirty(true);
650                 set_text_saved(false);
651         }
652         TextPanel::inputMethodEvent(ev);
653 }
654
655 void TM::TargetPanel::keyPressEvent(QKeyEvent *ev)
656 {
657         Editor *editor = parent_editor();
658         if(editor->link_mode()) do_key_press_in_link_mode(ev);
659         else
660         {
661                 TextPanel::keyPressEvent(ev);
662                 if(!ev->text().isEmpty())
663                 {
664                         set_text_dirty(true);
665                         set_text_saved(false);
666                 }
667         }
668 }
669
670 void TM::TargetPanel::do_click(QPoint const &pos)
671 {
672         Editor *editor = parent_editor();
673         if(editor->link_mode()) do_click_in_link_mode(pos);
674 }
675
676 void TM::TargetPanel::do_click_in_link_mode(QPoint const &pos)
677 {
678         Text::pointer w = select_word(pos);
679         if(!w) return;
680
681         WordLinker *wl = m_source_panel->linker();
682         wl->toggle(WordLink::Target, w);
683         m_source_panel->ensure_highlight();
684         setTextCursor(cursorForPosition(pos));
685 }
686
687 void TM::TargetPanel::do_key_press_in_link_mode(QKeyEvent *ev)
688 {
689         Editor *editor = parent_editor();
690         int key = ev->key();
691         switch(key)
692         {
693         case Qt::Key_Enter:
694         case Qt::Key_Return:
695                 editor->set_link_mode(false);
696                 break;
697         case Qt::Key_Left:
698         case Qt::Key_Right:
699         case Qt::Key_Up:
700         case Qt::Key_Down:
701                 TextPanel::keyPressEvent(ev);
702                 break;
703         }
704 }
705
706 // Editor ---------------------------------------------------------------------
707
708 TM::Editor::Editor(Settings *settings, Service *service, QWidget *parent)
709         : TextWidget(parent)
710         , m_settings(settings)
711         , m_service(service)
712         , m_segment_id(-1)
713         , m_edit_mode(false)
714         , m_link_mode(false)
715         , m_current_source_panel(nullptr)
716 {
717         TextArea *ta = text_area();
718         connect(ta, SIGNAL(focusInChild(TextPanel*,TextPanel*)), this, SLOT(onFocusInChild(TextPanel*,TextPanel*)));
719 }
720
721 /*!
722  * \brief エディタが保持しているリソースを適切に解放します。
723  */
724 void TM::Editor::clear()
725 {
726         if(m_segment_id < 0) return; // 文を保持していない場合、何もしない。
727
728         // パネルの終了処理。
729         if(m_current_source_panel) do_panel_leave(m_current_source_panel);
730         m_current_source_panel = nullptr;
731         // 表示をクリアする。
732         TextArea *ta = text_area();
733         ta->clear();
734         // リンクモードの解除。
735         set_link_mode(false);
736         parent_editor_widget()->set_link_mode_disabled(true);
737
738         m_segment_id = -1;
739 }
740
741 void TM::Editor::set_segment(TextSegment::pointer segment)
742 {
743         clear();
744         m_segment_id = segment->segment_id();
745         TextArea *ta = text_area();
746         int i = 0;
747         for(TextSentence::pointer p : *segment)
748         {
749                 SourcePanel *sp = ta->append<SourcePanel>();
750                 TargetPanel *tp = ta->append<TargetPanel>();
751                 sp->set_index(i++);
752                 sp->set_target_panel(tp);
753                 sp->set_parent_editor(this);
754                 tp->set_source_panel(sp);
755                 tp->set_parent_editor(this);
756
757                 sp->set_text_sentence(p);
758
759                 sp->show();
760         }
761 }
762
763 bool TM::Editor::edit_mode() const { return m_edit_mode; }
764
765 void TM::Editor::set_edit_mode(bool mode_) { m_edit_mode = mode_; }
766
767 bool TM::Editor::link_mode() const { return m_link_mode; }
768
769 void TM::Editor::set_link_mode(bool mode_)
770 {
771         if(mode_ == m_link_mode) return; // モードが変化していない場合、何もしない。
772         assert(!mode_ || can_link_mode()); // リンクモードに入るには、条件がある。
773
774         m_link_mode = mode_;
775         parent_editor_widget()->set_link_mode(mode_);
776
777         if(mode_) do_link_mode_enter(m_current_source_panel);
778         else if(m_current_source_panel) do_link_mode_leave(m_current_source_panel);
779 }
780
781 bool TM::Editor::can_link_mode() const
782 {
783         if(!m_current_source_panel) return false;
784         if(!edit_mode()) return false;
785         if(current_target_panel()->is_empty()) return false;
786
787         return true;
788 }
789
790 TM::EditorWidget* TM::Editor::parent_editor_widget()
791 {
792         EditorWidget* result = qobject_cast<EditorWidget*>(parentWidget());
793         assert(result);
794         return result;
795 }
796
797 TM::TargetPanel* TM::Editor::current_target_panel()
798 {
799         TargetPanel *result = nullptr;
800         if(m_current_source_panel) result = m_current_source_panel->target_panel();
801         return result;
802 }
803
804 TM::TargetPanel const* TM::Editor::current_target_panel() const
805 {
806         return const_cast<Editor*>(this)->current_target_panel();
807 }
808
809 void TM::Editor::onFocusInChild(TextPanel *new_, TextPanel *)
810 {
811         SourcePanel *old_panel = m_current_source_panel;
812         SourcePanel *new_panel = find_source_panel(new_);
813         assert(new_panel);
814
815         if(old_panel && old_panel != new_panel) do_panel_leave(old_panel);
816         m_current_source_panel = new_panel;
817         if(old_panel != new_panel) do_panel_enter(new_panel);
818 }
819
820 TM::SourcePanel* TM::Editor::find_source_panel(TextPanel *panel)
821 {
822         SourcePanel *sp = qobject_cast<SourcePanel*>(panel);
823         if(sp) return sp;
824         TargetPanel *tp = qobject_cast<TargetPanel*>(panel);
825         if(tp) return tp->source_panel();
826         return nullptr;
827 }
828
829 /*!
830  * \brief 文を表示するパネルにフォーカスが入るとき呼び出されます。
831  */
832 void TM::Editor::do_panel_enter(SourcePanel *panel)
833 {
834         TargetPanel *tp = panel->target_panel();
835         assert(tp);
836         tp->show();
837         if(can_link_mode()) parent_editor_widget()->set_link_mode_disabled(false);
838 }
839
840 /*!
841  * \brief 文を表示するパネルからフォーカスが出るとき呼び出されます。
842  */
843 void TM::Editor::do_panel_leave(SourcePanel *panel)
844 {
845         TargetPanel *tp = panel->target_panel();
846         assert(tp);
847         tp->hide();
848
849         set_link_mode(false);
850         parent_editor_widget()->set_link_mode_disabled(true);
851
852         int index = panel->index(); // センテンスのインデックス。
853         // チケット #35438 訳文を元に戻せない
854         bool need_clear_target = (!tp->is_text_saved()) && tp->is_text_dirty() && tp->is_empty();
855         if(need_clear_target)
856         {
857                 parent_editor_widget()->remove_sentence(m_segment_id, index);
858         }
859         else if(!tp->is_text_saved())
860         {
861                 if(tp->is_text_dirty()) divide_target_sentence(panel);
862                 if(!tp->is_empty())
863                         parent_editor_widget()->save_sentence(m_segment_id, index);
864                 tp->set_text_saved(true);
865         }
866 }
867
868 /*!
869  * \brief リンクモードに入るとき呼び出されます。
870  */
871 void TM::Editor::do_link_mode_enter(SourcePanel *panel)
872 {
873         assert(panel);
874         TargetPanel *tp = panel->target_panel();
875         assert(tp);
876
877         if(tp->is_text_dirty()) divide_target_sentence(panel);
878         panel->ensure_highlight();
879 }
880
881 /*!
882  * \brief リンクモードから出るとき呼び出されます。
883  */
884 void TM::Editor::do_link_mode_leave(SourcePanel *panel)
885 {
886         assert(panel);
887         panel->commit_link();
888         //panel->clear_highlight();
889 }
890
891 /*!
892  * \brief 訳文パネルの文を単語に分割し、表示に反映します。
893  */
894 void TM::Editor::divide_target_sentence(SourcePanel *source_panel)
895 {
896         int tcode = parent_editor_widget()->target_language();
897         TargetPanel *tp = source_panel->target_panel();
898         QString string = tp->toPlainText();
899
900         Text::pointer sentences = m_service->divide_into_sentences(tcode, string);
901         if(sentences->size())
902         {
903                 source_panel->linker()->clear();
904                 Text::pointer words = m_service->divide_into_words(tcode, sentences->begin());
905                 source_panel->set_target_sentence(words);
906                 tp->ensure_sentence(); // ココで、text_dirtyがfalseになる。
907         }
908 }
909
910
911
912
913
914
915
916
917