OSDN Git Service

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