OSDN Git Service

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