OSDN Git Service

HTTPリクエストパーサがフィールド値のクォートで遷移を間違えていた問題を修正。
[wordring-tm/wordring-tm.git] / proxy / tmeditorwidget.cpp
index b4c5daa..daf44a5 100644 (file)
@@ -25,6 +25,7 @@
 #include <QBrush>
 #include <QFont>
 #include <QMimeData>
+#include <QLocale>
 
 #include <QList>
 
 
 TM::EditorWidget::EditorWidget(Settings *settings, Service *service, QWidget *parent)
        : QWidget(parent)
-       , m_mutex(QMutex::Recursive)
        , m_service(service)
        , m_settings(settings)
+       , m_http_port(80)
        , m_socket(nullptr)
 {
        QVBoxLayout *vlayout = new QVBoxLayout(this);
        vlayout->setSpacing(4);
        vlayout->setContentsMargins(0, 0, 0, 0);
 
-       m_toolbar = new QToolBar("Editor", this);
+       m_toolbar = new QToolBar("editor", this);
 
        m_edit_mode = new QAction(QIcon(":/edit.png"), "edit", this);
        m_edit_mode->setCheckable(true);
@@ -56,11 +57,11 @@ TM::EditorWidget::EditorWidget(Settings *settings, Service *service, QWidget *pa
        m_toolbar->addAction(m_link);
        connect(m_link, SIGNAL(triggered(bool)), this, SLOT(onLinkModeTriggered(bool)));
 
-       m_slang = new QAction("&Source", this);
-       m_slang->setDisabled(true);
+       m_slang = new QAction("&source", this);
+       //m_slang->setDisabled(true);
        m_slang->setMenu(new QMenu(this));
-       m_tlang = new QAction("&Target", this);
-       m_tlang->setDisabled(true);
+       m_tlang = new QAction("&target", this);
+       //m_tlang->setDisabled(true);
        m_tlang->setMenu(new QMenu(this));
        m_toolbar->addAction(m_slang);
        m_toolbar->addAction(m_tlang);
@@ -76,122 +77,163 @@ TM::EditorWidget::EditorWidget(Settings *settings, Service *service, QWidget *pa
        vlayout->addWidget(m_toolbar);
        vlayout->addWidget(m_edit);
 
-       connect(m_service, SIGNAL(languageLoaded(int, QString, QIcon)), this, SLOT(onLanguageLoaded(int,QString,QIcon)));
+       connect(m_service, SIGNAL(languageLoaded(int, QString, QIcon)),
+                       this, SLOT(onLanguageLoaded(int,QString,QIcon)));
 }
 
 /*!
  * \brief ブラウザ上でドキュメントがフォーカスを取得したとき、
  * SocketConnectionから呼び出されます。
- *
- * SocketConnectionは別スレッドに在ります。
  */
 void TM::EditorWidget::attach(SocketConnection *socket)
 {
        if(m_socket == socket) return;
        if(m_socket) detach(m_socket);
 
-       QMutexLocker lock(&m_mutex);
        m_socket = socket;
-       connect(this, SIGNAL(editModeChanged(bool)), m_socket, SLOT(changeEditMode(bool)));
+//     connect(this, SIGNAL(editModeChanged(bool)), m_socket, SLOT(changeEditMode(bool)));
        set_link_mode_disabled(true);
+       connect(this, SIGNAL(sourceLanguageChanged()), m_socket, SLOT(changeLanguage()));
+       connect(this, SIGNAL(targetLanguageChanged()), m_socket, SLOT(changeLanguage()));
 }
 
 /*!
  * \brief ブラウザ上でドキュメントがフォーカスを取得することで、
  * 現在のSocketConnectionがフォーカスを失った時に呼び出されます。
- *
- * このメンバはattach()から呼び出されますが、その呼び出し元は別スレッドに在ります。
  */
-void TM::EditorWidget::detach(SocketConnection *)
+void TM::EditorWidget::detach(SocketConnection *connection)
 {
-       QMutexLocker lock(&m_mutex);
-       disconnect(this, SIGNAL(editModeChanged(bool)), m_socket, SLOT(changeEditMode(bool)));
+       if(m_socket != connection) return;
+
+//     disconnect(this, SIGNAL(editModeChanged(bool)), m_socket, SLOT(changeEditMode(bool)));
+       disconnect(this, SIGNAL(sourceLanguageChanged()), m_socket, SLOT(changeLanguage()));
+       disconnect(this, SIGNAL(targetLanguageChanged()), m_socket, SLOT(changeLanguage()));
 
        m_edit->clear();
        m_socket = nullptr;
 }
 
+void TM::EditorWidget::set_http_port(quint16 http_port) { m_http_port = http_port; }
+
 /*!
- * \brief ç·¨é\9b\86ã\83¢ã\83¼ã\83\89ã\82\92å¤\89æ\9b´します。
+ * \brief ç·¨é\9b\86ã\83¢ã\83¼ã\83\89ã\81®å ´å\90\88trueã\82\92è¿\94します。
  */
-void TM::EditorWidget::set_edit_mode(bool mode)
-{
-       QMutexLocker lock(&m_mutex);
-       bool old_mode = m_edit_mode->isChecked();
-       if(old_mode == mode) return;
-
-       m_edit_mode->setChecked(mode);
-}
-
 bool TM::EditorWidget::edit_mode()
 {
-       QMutexLocker lock(&m_mutex);
        return m_edit_mode->isChecked();
 }
 
+/*!
+ * \brief リンクモードを設定します。
+ */
 void TM::EditorWidget::set_link_mode(bool mode)
 {
-       QMutexLocker lock(&m_mutex);
        bool old_mode = m_link->isChecked();
        if(old_mode == mode) return;
 
        m_link->setChecked(mode);
 }
 
+/*!
+ * \brief リンクモードの場合、trueを返します。
+ */
 bool TM::EditorWidget::link_mode()
 {
-       QMutexLocker lock(&m_mutex);
        return m_link->isChecked();
 }
 
 /*!
  * \brief リンクボタンをグレーアウトします。
- *
- *
  */
 void TM::EditorWidget::set_link_mode_disabled(bool disable)
 {
-       QMutexLocker lock(&m_mutex);
        m_link->setDisabled(disable);
 }
 
+/*!
+ * \brief 原文の言語コードを返します。
+ */
 int TM::EditorWidget::source_language()
 {
-       QMutexLocker lock(&m_mutex);
        return m_slang->data().toInt();
 }
 
+/*!
+ * \brief 訳文の言語コードを返します。
+ */
 int TM::EditorWidget::target_language()
 {
-       QMutexLocker lock(&m_mutex);
        return m_tlang->data().toInt();
 }
 
-void TM::EditorWidget::set_string(QString source_, QString target_)
+void TM::EditorWidget::set_segment(TextSegment::pointer segment)
 {
-       QMutexLocker lock(&m_mutex);
-       int code = m_slang->data().toInt();
+       m_edit->set_segment(segment);
+}
 
-       Text::pointer result = Text::create();
-       Text::pointer sentences = m_service->divide_into_sentences(code, source_);
-       for(Text::pointer s = sentences->begin(); s; s = s->next())
-       {
-               Text::pointer words = m_service->divide_into_words(code, s);
-               result->append(words);
-       }
-       m_edit->set_sentences(result);
+/*!
+ * \brief SocketConnectionにセンテンスの保存を要求します。
+ * \param segment_id セグメントのID
+ * \param index センテンスのインデックス
+ *
+ * 結果としてデータベース、サーバ、ブラウザを更新します。
+ */
+void TM::EditorWidget::save_sentence(TextSentence::pointer text_sentence)
+{
+       m_socket->save_sentence(text_sentence);
 }
 
+/*!
+ * \brief SocketConnectionにセンテンスのクリアを要求します。
+ * \param segment_id セグメントのID
+ * \param index センテンスのインデックス
+ *
+ * 結果としてデータベース、サーバ、ブラウザを更新します。
+ *
+ * チケット #35438 訳文を元に戻せない 対応
+ */
+void TM::EditorWidget::remove_sentence(TextSentence::pointer text_sentence)
+{
+       m_socket->remove_sentence(text_sentence);
+}
+
+void TM::EditorWidget::do_panel_entered(SourcePanel *panel)
+{
+       //assert(m_socket);
+       if(!m_socket) return;
+
+       m_socket->segment_list()->set_current_sentence(panel->text_sentence());
+}
+
+void TM::EditorWidget::do_panel_leaved(SourcePanel *)
+{
+       assert(m_socket);
+       if(!m_socket) return;
+
+       m_socket->segment_list()->set_current_sentence(TextSentence::pointer());
+}
+
+/*!
+ * \brief 言語プラグインが読み込まれるたびに呼び出されます。
+ * \param code 言語コード。
+ * \param name 言語名。
+ * \param icon 言語を表すアイコン。
+ */
 void TM::EditorWidget::onLanguageLoaded(int code, QString name, QIcon icon)
 {
-       QString dslanguage = m_settings->value("Widget/defaultSourceLanguage", "English").toString();
-       QString tslanguage = m_settings->value("Widget/defaultSourceLanguage", "Japanese").toString();
-       if(dslanguage == name)
+       if(!m_settings->contains("Widget/default_source_language"))
+                       m_settings->setValue("Widget/default_source_language", QLocale::Language::English);
+       if(!m_settings->contains("Widget/default_target_language"))
+                       m_settings->setValue("Widget/default_target_language", QLocale::Language::Japanese);
+
+       int scode = m_settings->value("Widget/default_source_language").toInt();
+       int tcode = m_settings->value("Widget/default_target_language").toInt();
+       if(code == scode)
        {
                m_slang->setIcon(icon);
                m_slang->setData(code);
        }
-       if(tslanguage == name)
+       if(code == tcode)
        {
                m_tlang->setIcon(icon);
                m_tlang->setData(code);
@@ -204,32 +246,51 @@ void TM::EditorWidget::onLanguageLoaded(int code, QString name, QIcon icon)
        connect(taction, SIGNAL(triggered(bool)), this, SLOT(onTargetLanguageTriggered(bool)));
 }
 
+/*!
+ * \brief 編集アイコンがクリックされたとき呼び出されます。
+ */
 void TM::EditorWidget::onEditModeTriggered(bool)
 {
        QAction *edit_mode = qobject_cast<QAction*>(sender());
        bool checked = edit_mode->isChecked();
        m_link->setDisabled(true);
-       m_slang->setDisabled(!checked);
-       m_tlang->setDisabled(!checked);
-       emit editModeChanged(checked);
+       //m_slang->setDisabled(!checked);
+       //m_tlang->setDisabled(!checked);
+       //emit editModeChanged(checked);
+
+       m_service->change_edit_mode(checked);
+       if(!checked) m_edit->clear();
 
        m_edit->set_edit_mode(checked);
 }
 
+/*!
+ * \brief リンクアイコンがクリックされたとき呼び出されます。
+ */
 void TM::EditorWidget::onLinkModeTriggered(bool checked)
 {
        //QAction *link_mode = qobject_cast<QAction*>(sender());
        m_edit->set_link_mode(checked);
 }
 
+/*!
+ * \brief アクションから原文言語が変更されたとき呼び出されます。
+ */
 void TM::EditorWidget::onSourceLanguageTriggered(bool)
 {
        QAction *saction = qobject_cast<QAction*>(sender());
        assert(saction);
        m_slang->setIcon(saction->icon());
        m_slang->setData(saction->data());
+
+       m_settings->setValue("Widget/default_source_language", saction->data());
+
+       emit sourceLanguageChanged();
 }
 
+/*!
+ * \brief アクションから訳文言語が変更されたとき呼び出されます。
+ */
 void TM::EditorWidget::onTargetLanguageTriggered(bool)
 {
        QAction *taction = qobject_cast<QAction*>(sender());
@@ -237,36 +298,50 @@ void TM::EditorWidget::onTargetLanguageTriggered(bool)
        m_tlang->setIcon(taction->icon());
        m_tlang->setData(taction->data());
 
+       m_settings->setValue("Widget/default_target_language", taction->data());
+
+       emit targetLanguageChanged();
 }
 
+/*!
+ * \brief ブラウザ・アイコンがクリックされたとき呼び出されます。
+ */
 void TM::EditorWidget::onBrowserTriggered(bool)
 {
-       QDesktopServices::openUrl(QUrl("http://localhost/"));
+       QString url("http://localhost:");
+       url += QString::number(m_http_port) + "/";
+
+       QDesktopServices::openUrl(QUrl(url));
 }
 
 // EditorPanel ----------------------------------------------------------------
 
 TM::EditorPanel::EditorPanel(QWidget *parent)
        : TextPanel(parent)
-       , m_editor(nullptr)
+       , m_parent_editor(nullptr)
 {
 }
 
-void TM::EditorPanel::set_editor(Editor *editor)
+TM::Editor* TM::EditorPanel::parent_editor() { return m_parent_editor; }
+
+/*!
+ * \brief 親となるエディタを設定します。
+ */
+void TM::EditorPanel::set_parent_editor(Editor *editor)
 {
-       assert(!m_editor);
-       m_editor = editor;
+       assert(!m_parent_editor);
+       m_parent_editor = editor;
 }
 
-Text::pointer TM::EditorPanel::sentence() { return m_sentence; }
-
-void TM::EditorPanel::set_sentence(Text::pointer sentence)
+/*!
+ * \brief 文を再表示します。
+ */
+void TM::EditorPanel::ensure_sentence()
 {
-       assert(sentence);
-       m_sentence = sentence;
+       if(!sentence()) return;
        clear();
        QTextCursor c = textCursor();
-       for(Text::pointer p = sentence->begin(); p; p = p->next()) // p=word
+       for(Text::pointer p = sentence()->begin(); p; p = p->next()) // p=word
        {
                QTextCharFormat cf;
                QVariant v = QVariant::fromValue(Text::weak_pointer(p));
@@ -275,16 +350,14 @@ void TM::EditorPanel::set_sentence(Text::pointer sentence)
        }
 }
 
-bool TM::EditorPanel::is_empty() const
-{
-       qDebug() << document()->isEmpty();
-       return document()->isEmpty();
-       if(!m_sentence) return true;
-       for(Text::const_pointer p = m_sentence->begin(); p; p = p->next())
-               if(!p->string().isEmpty()) return true;
-       return false;
-}
+/*!
+ * \brief 内容が空の場合、trueを返します。
+ */
+bool TM::EditorPanel::is_empty() const { return document()->isEmpty(); }
 
+/*!
+ * \brief 引数として与えられた単語に該当する範囲を選択するカーソルを返します。
+ */
 QTextCursor TM::EditorPanel::select_cursor(Text::pointer word)
 {
        RangeData *rd = static_cast<RangeData*>(word->data().get());
@@ -297,6 +370,9 @@ QTextCursor TM::EditorPanel::select_cursor(Text::pointer word)
        return c;
 }
 
+/*!
+ * \brief 引数として与えられた位置にある単語を返します。
+ */
 Text::pointer TM::EditorPanel::select_word(QPoint const &pos)
 {
        QTextCursor c = cursorForPosition(pos);
@@ -327,10 +403,7 @@ void TM::EditorPanel::highlight(WordLink::storage_type *link, QColor color)
        for(Text::pointer p : *link) highlight(p, color);
 }
 
-void TM::EditorPanel::clear_highlight()
-{
-       if(m_sentence) set_sentence(m_sentence);
-}
+void TM::EditorPanel::clear_highlight() { ensure_sentence(); }
 
 /*!
  * \brief 引数として与えられた整数値から色を作成します。
@@ -348,12 +421,47 @@ QColor TM::EditorPanel::color(int index) const
 
 TM::SourcePanel::SourcePanel(QWidget *parent)
        : EditorPanel(parent)
+       , m_index(-1)
        , m_target_panel(nullptr)
 {
        setUndoRedoEnabled(false);
        setContextMenuPolicy(Qt::NoContextMenu);
 }
 
+int TM::SourcePanel::index() const { return m_index; }
+
+void TM::SourcePanel::set_index(int index) { m_index = index; }
+
+Text::pointer TM::SourcePanel::sentence() { return source_sentence(); }
+
+Text::pointer TM::SourcePanel::source_sentence()
+{
+       return m_text_sentence->ssentence();
+}
+
+Text::pointer TM::SourcePanel::target_sentence()
+{
+       return m_text_sentence->tsentence();
+}
+
+void TM::SourcePanel::set_target_sentence(Text::pointer sentence)
+{
+       m_text_sentence->set_tsentence(sentence);
+}
+
+TM::TextSentence::pointer TM::SourcePanel::text_sentence()
+{
+       return m_text_sentence;
+}
+
+void TM::SourcePanel::set_text_sentence(TextSentence::pointer text_sentence)
+{
+       m_text_sentence = text_sentence;
+       ensure_sentence();
+       assert(target_panel());
+       target_panel()->ensure_sentence();
+}
+
 TM::TargetPanel* TM::SourcePanel::target_panel() { return m_target_panel; }
 
 void TM::SourcePanel::set_target_panel(TargetPanel *target)
@@ -363,31 +471,34 @@ void TM::SourcePanel::set_target_panel(TargetPanel *target)
 
 void TM::SourcePanel::commit_link()
 {
-       m_linker.commit();
+       linker()->commit();
        clear_highlight();
        m_target_panel->clear_highlight();
 }
 
 TM::WordLinker* TM::SourcePanel::linker()
 {
-       return &m_linker;
+       return m_text_sentence->linker();
 }
 
+/*!
+ * \brief 保持するリンクを色分け表示します。
+ */
 void TM::SourcePanel::ensure_highlight()
 {
        clear_highlight();
        m_target_panel->clear_highlight();
 
-       WordLink::pointer wl = m_linker.current();
+       WordLink::pointer wl = linker()->current();
        if(wl)
        {
                highlight(wl->sources(), Qt::cyan);
                target_panel()->highlight(wl->targets(), Qt::cyan);
        }
 
-       for(WordLink::pointer wl : m_linker)
+       for(WordLink::pointer wl : *linker())
        {
-               int index = m_linker.index_of(wl);
+               int index = linker()->index_of(wl);
                QColor c = color(index);
                highlight(wl->sources(), c);
                target_panel()->highlight(wl->targets(), c);
@@ -404,17 +515,18 @@ void TM::SourcePanel::insertFromMimeData(const QMimeData *) { } // ドロップ
 void TM::SourcePanel::inputMethodEvent(QInputMethodEvent *ev)
 {
        ev->setCommitString(""); // 入力禁止
-       QPlainTextEdit::inputMethodEvent(ev);
+       TextPanel::inputMethodEvent(ev);
 }
 
 void TM::SourcePanel::keyPressEvent(QKeyEvent *ev)
 {
+       Editor *editor = parent_editor();
        int key = ev->key();
        switch(key)
        {
        case Qt::Key_Enter:
        case Qt::Key_Return:
-               if(m_editor->link_mode()) m_editor->set_link_mode(false);
+               if(editor->link_mode()) editor->set_link_mode(false);
                break;
        case Qt::Key_C:
                if(!ev->modifiers().testFlag(Qt::ControlModifier)) break;
@@ -429,7 +541,8 @@ void TM::SourcePanel::keyPressEvent(QKeyEvent *ev)
 
 void TM::SourcePanel::do_click(QPoint const &pos)
 {
-       if(m_editor->link_mode()) do_click_in_link_mode(pos);
+       Editor *editor = parent_editor();
+       if(editor->link_mode()) do_click_in_link_mode(pos);
 }
 
 void TM::SourcePanel::do_click_in_link_mode(QPoint const &pos)
@@ -437,26 +550,18 @@ void TM::SourcePanel::do_click_in_link_mode(QPoint const &pos)
        Text::pointer w = select_word(pos);
        if(!w) return;
 
-       m_linker.toggle(WordLink::Source, w);
+       linker()->toggle(WordLink::Source, w);
        ensure_highlight();
        setTextCursor(cursorForPosition(pos));
 }
 
-void TM::SourcePanel::do_focusin()
-{
-       //if(m_target_panel) m_target_panel->show();
-}
-
-void TM::SourcePanel::do_focusout()
-{
-}
-
 // TargetPanel ----------------------------------------------------------------
 
 TM::TargetPanel::TargetPanel(QWidget *parent)
        : EditorPanel(parent)
        , m_source_panel(nullptr)
        , m_text_dirty(false)
+       , m_text_saved(true)
 {
        //setContextMenuPolicy(Qt::NoContextMenu);
 }
@@ -468,18 +573,57 @@ void TM::TargetPanel::set_source_panel(SourcePanel *source)
        m_source_panel = source;
 }
 
-void TM::TargetPanel::set_sentence(Text::pointer sentence)
+Text::pointer TM::TargetPanel::sentence()
 {
-       EditorPanel::set_sentence(sentence);
+       return source_panel()->target_sentence();
+}
+
+void TM::TargetPanel::save_sentence()
+{
+
+}
+
+void TM::TargetPanel::ensure_sentence()
+{
+       EditorPanel::ensure_sentence();
        set_text_dirty(false);
+       set_text_saved(false);
+}
+
+/*!
+ * \brief データベースへの登録が必要な場合、falseを返します。
+ *
+ * パネル内の文字列が編集された場合、trueを返します。
+ * 原文パネルがフォーカスを失うと、エディタがこのメンバを呼び出し、
+ * データベース登録の必要性を判定します。
+ * その後、エディタはデータベース登録を行い、フラグをクリアするので、
+ * 次に文字列が編集されるまでtrueを返し続けます。
+ */
+bool TM::TargetPanel::is_text_saved() const { return m_text_saved; }
+
+void TM::TargetPanel::set_text_saved(bool saved)
+{
+       m_text_saved = saved;
 }
 
+/*!
+ * \brief パネル内の文字列が編集された場合、trueを返します。
+ *
+ * エディタがリンクモードに入るとき、このメンバを呼び出し、
+ * 単語へ分割する必要があるか判定します。
+ * エディタはパネルの文字列を単語に分割した後、set_sentence()によって
+ * パネルに文を設定するため、次に文字列を編集するまでfalseを返すようになります。
+ */
 bool TM::TargetPanel::is_text_dirty() const { return m_text_dirty; }
 
+/*!
+ * \brief パネルの文字列が編集されたかを示すフラグを設定します。
+ */
 void TM::TargetPanel::set_text_dirty(bool dirty)
 {
+       EditorWidget *editor_widget = parent_editor()->parent_editor_widget();
        m_text_dirty = dirty;
-       if(dirty) m_editor->parent_widget()->set_link_mode_disabled(false);
+       if(dirty) editor_widget->set_link_mode_disabled(false);
 }
 
 bool TM::TargetPanel::canInsertFromMimeData(QMimeData const *source) const
@@ -492,28 +636,40 @@ void TM::TargetPanel::insertFromMimeData(QMimeData const *source)
 {
        TextPanel::insertFromMimeData(source);
        set_text_dirty(true);
+       set_text_saved(false);
 }
 
 void TM::TargetPanel::inputMethodEvent(QInputMethodEvent *ev)
 {
-       if(m_editor->link_mode()) ev->setCommitString("");
-       if(!ev->commitString().isEmpty()) set_text_dirty(true);
-       QPlainTextEdit::inputMethodEvent(ev);
+       Editor *editor = parent_editor();
+       if(editor->link_mode()) ev->setCommitString("");
+       if(!ev->commitString().isEmpty())
+       {
+               set_text_dirty(true);
+               set_text_saved(false);
+       }
+       TextPanel::inputMethodEvent(ev);
 }
 
 void TM::TargetPanel::keyPressEvent(QKeyEvent *ev)
 {
-       if(m_editor->link_mode()) do_key_press_in_link_mode(ev);
+       Editor *editor = parent_editor();
+       if(editor->link_mode()) do_key_press_in_link_mode(ev);
        else
        {
                TextPanel::keyPressEvent(ev);
-               if(!ev->text().isEmpty()) set_text_dirty(true);
+               if(!ev->text().isEmpty())
+               {
+                       set_text_dirty(true);
+                       set_text_saved(false);
+               }
        }
 }
 
 void TM::TargetPanel::do_click(QPoint const &pos)
 {
-       if(m_editor->link_mode()) do_click_in_link_mode(pos);
+       Editor *editor = parent_editor();
+       if(editor->link_mode()) do_click_in_link_mode(pos);
 }
 
 void TM::TargetPanel::do_click_in_link_mode(QPoint const &pos)
@@ -527,24 +683,15 @@ void TM::TargetPanel::do_click_in_link_mode(QPoint const &pos)
        setTextCursor(cursorForPosition(pos));
 }
 
-void TM::TargetPanel::do_focusin()
-{
-       //show();
-}
-
-void TM::TargetPanel::do_focusout()
-{
-       //hide();
-}
-
 void TM::TargetPanel::do_key_press_in_link_mode(QKeyEvent *ev)
 {
+       Editor *editor = parent_editor();
        int key = ev->key();
        switch(key)
        {
        case Qt::Key_Enter:
        case Qt::Key_Return:
-               m_editor->set_link_mode(false);
+               editor->set_link_mode(false);
                break;
        case Qt::Key_Left:
        case Qt::Key_Right:
@@ -561,6 +708,7 @@ TM::Editor::Editor(Settings *settings, Service *service, QWidget *parent)
        : TextWidget(parent)
        , m_settings(settings)
        , m_service(service)
+       , m_segment_id(-1)
        , m_edit_mode(false)
        , m_link_mode(false)
        , m_current_source_panel(nullptr)
@@ -574,9 +722,8 @@ TM::Editor::Editor(Settings *settings, Service *service, QWidget *parent)
  */
 void TM::Editor::clear()
 {
-       if(!m_sentences) return; // 文を保持していない場合、何もしない。
+       if(m_segment_id < 0) return; // 文を保持していない場合、何もしない。
 
-       m_sentences.reset(); // 保持している文のメモリーを開放する。
        // パネルの終了処理。
        if(m_current_source_panel) do_panel_leave(m_current_source_panel);
        m_current_source_panel = nullptr;
@@ -585,28 +732,29 @@ void TM::Editor::clear()
        ta->clear();
        // リンクモードの解除。
        set_link_mode(false);
-       parent_widget()->set_link_mode_disabled(true);
+       parent_editor_widget()->set_link_mode_disabled(true);
+
+       m_segment_id = -1;
 }
 
-/*!
- * \brief エディタに文を設定します。
- *
- * 文は複数の場合があります。
- */
-void TM::Editor::set_sentences(Text::pointer sentences)
+void TM::Editor::set_segment(TextSegment::pointer segment)
 {
        clear();
-       m_sentences = sentences;
+       m_segment_id = segment->segment_id();
        TextArea *ta = text_area();
-       for(Text::pointer s = sentences->begin(); s; s = s->next()) // s: sentence
+       int i = 0;
+       for(TextSentence::pointer p : *segment)
        {
                SourcePanel *sp = ta->append<SourcePanel>();
                TargetPanel *tp = ta->append<TargetPanel>();
+               sp->set_index(i++);
                sp->set_target_panel(tp);
-               sp->set_editor(this);
+               sp->set_parent_editor(this);
                tp->set_source_panel(sp);
-               tp->set_editor(this);
-               sp->set_sentence(s);
+               tp->set_parent_editor(this);
+
+               sp->set_text_sentence(p);
+
                sp->show();
        }
 }
@@ -623,7 +771,7 @@ void TM::Editor::set_link_mode(bool mode_)
        assert(!mode_ || can_link_mode()); // リンクモードに入るには、条件がある。
 
        m_link_mode = mode_;
-       parent_widget()->set_link_mode(mode_);
+       parent_editor_widget()->set_link_mode(mode_);
 
        if(mode_) do_link_mode_enter(m_current_source_panel);
        else if(m_current_source_panel) do_link_mode_leave(m_current_source_panel);
@@ -638,7 +786,7 @@ bool TM::Editor::can_link_mode() const
        return true;
 }
 
-TM::EditorWidget* TM::Editor::parent_widget()
+TM::EditorWidget* TM::Editor::parent_editor_widget()
 {
        EditorWidget* result = qobject_cast<EditorWidget*>(parentWidget());
        assert(result);
@@ -685,7 +833,10 @@ void TM::Editor::do_panel_enter(SourcePanel *panel)
        TargetPanel *tp = panel->target_panel();
        assert(tp);
        tp->show();
-       if(can_link_mode()) parent_widget()->set_link_mode_disabled(false);
+       if(can_link_mode()) parent_editor_widget()->set_link_mode_disabled(false);
+
+       // 編集ウィジェットに通知。
+       parent_editor_widget()->do_panel_entered(panel);
 }
 
 /*!
@@ -698,9 +849,24 @@ void TM::Editor::do_panel_leave(SourcePanel *panel)
        tp->hide();
 
        set_link_mode(false);
-       parent_widget()->set_link_mode_disabled(true);
+       parent_editor_widget()->set_link_mode_disabled(true);
 
-       qDebug() << "focus out";
+       TextSentence::pointer text_sentence = panel->text_sentence();
+       // チケット #35438 訳文を元に戻せない
+       bool need_clear_target = (!tp->is_text_saved()) && tp->is_text_dirty() && tp->is_empty();
+       if(need_clear_target)
+       {
+               parent_editor_widget()->remove_sentence(text_sentence);
+       }
+       else if(!tp->is_text_saved())
+       {
+               if(tp->is_text_dirty()) divide_target_sentence(panel);
+               if(!tp->is_empty())
+                       parent_editor_widget()->save_sentence(text_sentence);
+               tp->set_text_saved(true);
+       }
+       // 編集ウィジェットに通知。
+       parent_editor_widget()->do_panel_leaved(panel);
 }
 
 /*!
@@ -711,20 +877,8 @@ void TM::Editor::do_link_mode_enter(SourcePanel *panel)
        assert(panel);
        TargetPanel *tp = panel->target_panel();
        assert(tp);
-       int tcode = parent_widget()->target_language();
-
-       if(tp->is_text_dirty())
-       {
-               panel->linker()->clear();
-               QString string = tp->toPlainText();
-               Text::pointer sentences = m_service->divide_into_sentences(tcode, string);
-               if(sentences->size())
-               {
-                       Text::pointer words = m_service->divide_into_words(tcode, sentences->begin());
-                       tp->set_sentence(words);
-               }
-       }
 
+       if(tp->is_text_dirty()) divide_target_sentence(panel);
        panel->ensure_highlight();
 }
 
@@ -733,12 +887,29 @@ void TM::Editor::do_link_mode_enter(SourcePanel *panel)
  */
 void TM::Editor::do_link_mode_leave(SourcePanel *panel)
 {
-       qDebug() << m_current_source_panel->linker()->to_json_array();
        assert(panel);
        panel->commit_link();
-       panel->clear_highlight();
+       //panel->clear_highlight();
 }
 
+/*!
+ * \brief 訳文パネルの文を単語に分割し、表示に反映します。
+ */
+void TM::Editor::divide_target_sentence(SourcePanel *source_panel)
+{
+       int tcode = parent_editor_widget()->target_language();
+       TargetPanel *tp = source_panel->target_panel();
+       QString string = tp->toPlainText();
+
+       Text::pointer sentences = m_service->divide_into_sentences(tcode, string);
+       if(sentences->size())
+       {
+               source_panel->linker()->clear();
+               Text::pointer words = m_service->divide_into_words(tcode, sentences->begin());
+               source_panel->set_target_sentence(words);
+               tp->ensure_sentence(); // ココで、text_dirtyがfalseになる。
+       }
+}