OSDN Git Service

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