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