#include "tmsocket.h"
#include "tmeditorwidget.h"
+#include "tmservice.h"
#include "settings.h"
#include "html.h"
+#include "tmtext.h"
#include <QMutex>
#include <QMutexLocker>
int TM::HtmlData::type() const { return Type; }
-TM::HtmlData::pointer TM::HtmlData::create(HtmlNode node_, int begin_, int tail_)
-{
- return pointer(new HtmlData(node_, begin_, tail_));
-}
-
-// TextConverter --------------------------------------------------------------
-
-/*!
- * \brief 引数として与えられたHtmlの範囲から構造化テキストを作成します。
- */
-Text::pointer TM::TextConverter::to_text(HtmlRange range)
-{
- m_state = 0;
- Text::pointer result = Text::create();
- int begin = 0;
-
- for(HtmlNode const &node : range)
- {
- if(node.type() != HtmlNode::Text) continue;
- Text::pointer p = stuff_text(result, node.value());
- if(!p) continue;
- int num = p->string().size();
- HtmlData::pointer hd = HtmlData::create(node, begin, begin + num - 1);
- p->set_data(hd);
- begin += num;
- }
- return result;
-}
+HtmlNode TM::HtmlData::node() { return m_node; }
-/*!
- * \brief 引数として与えられたノードから構造化テキストを作成します。
- *
- * 連続する空白文字を一つにまとめます。
- * 無効なノードが与えられた場合、空の構造化テキストを返します。
- */
-Text::pointer TM::TextConverter::stuff_text(Text::pointer parent, QString const &string)
+QString TM::HtmlData::debug_dump() const
{
- QString outstring;
- for(QChar const &ch : string)
- {
- if(is_ignorable_white_space(ch)) continue;
- if(is_white_space(ch)) outstring.append(' ');
- else outstring.append(ch);
- }
- if(outstring.isEmpty()) return Text::pointer();
-
- Text::pointer result = Text::create(parent, outstring);
- parent->append(result);
+ QString result;
+ result += "[HtmlData:";
+ result += m_node.name() + ",";
+ result += QString::number(begin()) + ",";
+ result += QString::number(tail()) + "]";
return result;
}
-/*!
- * \brief 連続する空白文字を検出します。
- *
- * 最初の空白文字、あるいは空白文字以外は、falseを返します。
- * 連続する空白文字の二番目以降に対してのみtrueを返します。
- */
-bool TM::TextConverter::is_ignorable_white_space(const QChar &ch)
-{
- if(!is_white_space(ch))
- {
- m_state = 0;
- return false;
- }
- if(m_state == 0)
- {
- m_state = 1;
- return false;
- }
- return true;
-}
-
-/*!
- * \brief 引数として与えられたchが空白文字の場合true、それ以外の場合falseを返します。
- */
-bool TM::TextConverter::is_white_space(QChar const &ch)
+TM::HtmlData::pointer TM::HtmlData::create(HtmlNode node_, int begin_, int tail_)
{
- switch(ch.unicode())
- {
- case 0x9:
- case 0xA:
- case 0xC:
- case 0xD:
- case 0x20:
- case 0x200B:
- return true;
- }
- return false;
+ return pointer(new HtmlData(node_, begin_, tail_));
}
// SocketConnection -----------------------------------------------------------
-TM::SocketConnection::SocketConnection(EditorWidget *editor_widget, QWebSocket *socket)
+TM::SocketConnection::SocketConnection(Settings *settings, Service *service,
+ EditorWidget *editor_widget, QWebSocket *socket)
: QObject(socket)
- , m_editor_widget(editor_widget)
+ , m_settings(settings)
+ , m_service(service)
, m_mutex(QMutex::Recursive)
+ , m_editor_widget(editor_widget)
+ , m_site_id(0)
, m_edit_mode(false)
+ , m_scode(0)
+ , m_target_language_code(0)
{
+ qRegisterMetaType<pointer>();
+
connect(socket, SIGNAL(textMessageReceived(QString const&)),
this, SLOT(onTextMessageReceived(QString const&)));
connect(socket, SIGNAL(binaryMessageReceived(QByteArray const&)),
QWebSocket* TM::SocketConnection::socket()
{
QWebSocket * result = qobject_cast<QWebSocket*>(parent());
- assert(result);
return result;
}
void TM::SocketConnection::send_message(QString const &message)
{
- socket()->sendTextMessage(message);
+ QWebSocket *ws = socket();
+ if(ws) ws->sendTextMessage(message);
}
void TM::SocketConnection::send_message(QJsonObject const &json)
send_message(doc.toJson().data());
}
+/*!
+ * \brief データベースへセンテンスの登録とブラウザへの反映を行います。
+ * \param segment_id セグメントのID。
+ * \param index センテンスのインデックス。
+ *
+ * エディタから呼び出されます。
+ */
+void TM::SocketConnection::save_sentence(int segment_id, int index)
+{
+ assert(m_site_id);
+
+ // セグメントの検索。
+ segment_map_iterator it = m_segments.find(segment_id);
+ assert(it != m_segments.end());
+ TextSegment::pointer segment = it.value();
+
+ // センテンスの検索。
+ TextSentence::pointer sentence = segment->at(index);
+ assert(sentence->source_sentence());
+ if(!sentence->target_sentence()) return;
+
+ // 言語コード。
+ int scode = m_editor_widget->source_language();
+ int tcode = m_editor_widget->target_language();
+
+ // データベースへ登録。
+ m_service->insert_sentence(segment_id, index,
+ m_url.host(), scode, tcode, sentence, pointer(this));
+
+ // ブラウザへ反映。
+ set_segment(segment_id, segment->to_html());
+}
+
+/*!
+ * \brief データベースへセンテンスの消去とブラウザへの反映を行います。
+ * \param segment_id セグメントのID。
+ * \param index センテンスのインデックス。
+ *
+ * エディタから呼び出されます。
+ * 呼び出されたとき、訳文はありません。
+ * そこで、clear系がサービスに依頼する時は、原文を引数として渡します。
+ */
+void TM::SocketConnection::remove_sentence(int segment_id, int index)
+{
+ assert(m_site_id);
+
+ // セグメントの検索。
+ segment_map_iterator it = m_segments.find(segment_id);
+ assert(it != m_segments.end());
+ TextSegment::pointer segment = it.value();
+
+ // センテンスの検索。
+ TextSentence::pointer sentence = segment->at(index);
+ assert(sentence->source_sentence());
+ // 訳文の消去。
+ sentence->set_target_sentence(Text::pointer());
+
+ // 言語コード。
+ int scode = m_editor_widget->source_language();
+ int tcode = m_editor_widget->target_language();
+
+ // データベースとサーバへ反映。
+ m_service->remove_sentence(m_url.host(), scode, tcode, sentence);
+
+ // ブラウザへ反映。
+ set_segment(segment_id, segment->to_html());
+}
+
+/*!
+ * \brief TM::SocketConnection::sentence_found
+ * \param segment_id
+ * \param index
+ * \param result データベースが返す訳文。
+ */
+void TM::SocketConnection::sentence_found(
+ int segment_id, int index, sentence_data_type::pointer result)
+{
+ if(!result) return; // 検索がヒットしなかった場合、何もしない。
+
+ segment_map_iterator it = m_segments.find(segment_id);
+ assert(it != m_segments.end());
+ TextSegment::pointer segment = it.value();
+
+ segment->at(index)->set_source_id(result->source_id);
+ if(100 <= result->quality) // 品質が100以上の場合、セグメントとブラウザを更新する。
+ {
+ Text::pointer sentences = m_service->divide_into_sentences(
+ m_target_language_code, result->sentence);
+ Text::pointer words = m_service->divide_into_words(
+ m_target_language_code, sentences->begin());
+ segment->at(index)->set_target_sentence(words);
+ segment->at(index)->set_json(result->json);
+
+ set_segment(segment_id, segment->to_html()); // ブラウザに送信
+ }
+ else // 品質が100未満の場合、候補として登録する。
+ {
+ segment->at(index)->append(result);
+ }
+}
+
+void TM::SocketConnection::sentence_inserted(int segment_id, int index,
+ quint32 source_id, quint32 target_id)
+{
+ segment_map_iterator it = m_segments.find(segment_id);
+ assert(it != m_segments.end());
+ TextSegment::pointer segment = it.value();
+
+ assert(source_id);
+ TextSentence::pointer sentence = segment->at(index);
+ sentence->set_source_id(source_id);
+ sentence->set_target_id(target_id);
+}
+
void TM::SocketConnection::changeEditMode(bool edit_mode)
{
set_edit_mode(edit_mode);
}
+void TM::SocketConnection::changeLanguage()
+{
+ QJsonObject json;
+ json["cmd"] = "reload";
+ send_message(json);
+}
+
+/*!
+ * \brief ブラウザに編集モードを反映します。
+ *
+ * ブラウザは、編集モードにある場合、クリックでedit_segmentを発行します。
+ * 編集モードではリンクのクリックは無効です。
+ */
void TM::SocketConnection::set_edit_mode(bool edit_mode)
{
if(edit_mode == m_edit_mode) return;
json["cmd"] = "set_edit_mode";
json["edit_mode"] = edit_mode;
send_message(json);
+}
+/*!
+ * \brief ウェブブラウザへセグメントの訳文をセットします。
+ * \param segment_id セグメントのID。
+ * \param html セグメントに対応するHTML文字列。
+ */
+void TM::SocketConnection::set_segment(int segment_id, QString html)
+{
+ QJsonObject json;
+ json["cmd"] = "set_segment";
+ json["segment_id"] = segment_id;
+ json["html"] = html;
+ send_message(json);
}
/*!
- * \brief ウェブブラウザ上でクリックされ、editコマンドが発行されたときに呼び出されます。
+ * \brief ウェブブラウザ上でクリックされ、edit_segmentコマンドが発行されたときに
+ * 呼び出されます。
*/
-void TM::SocketConnection::do_edit(QJsonObject const &json)
+void TM::SocketConnection::do_edit_segment(QJsonObject const &json)
{
- m_document.set_content(json["source"].toString().toUtf8());
- HtmlNode body = m_document.first("html").first("body");
-
- TextConverter tc;
- m_text = tc.to_text(HtmlRange(body, body));
- m_editor_widget->set_string(m_text->to_string(), json["target"].toString());
- //emit editCmd(json["id"].toInt(), m_document.to_string());
- /*
- QJsonObject reply;
- reply["cmd"] = "doedit";
- reply["id"] = json["id"].toString();
- reply["html"] = "test2";
- QJsonDocument doc;
- doc.setObject(reply);
- send_message(doc.toJson().data());
- */
+ assert(json.contains("segment_id"));
+ if(!json.contains("segment_id")) return;
+ int segment_id = json["segment_id"].toString().toInt();
+
+ segment_map_iterator it = m_segments.find(segment_id);
+ assert(it != m_segments.end());
+ TextSegment::pointer segment = it.value();
+
+ if(segment != m_current_segment)
+ {
+ m_current_segment = segment;
+ m_editor_widget->set_segment(m_current_segment);
+ }
}
/*!
* \brief ウェブブラウザ上でドキュメントがフォーカスを取得し、
* focusコマンドが発行されたときに呼び出されます。
*/
-void TM::SocketConnection::do_focus(QJsonObject const &json)
+void TM::SocketConnection::do_focus(QJsonObject const &)
{
m_editor_widget->attach(this);
- bool edit_mode = json["edit_mode"].toBool();
- m_edit_mode = edit_mode;
- m_editor_widget->set_edit_mode(edit_mode);
- if(m_text) m_editor_widget->set_string(m_text->to_string(), "");
+ m_editor_widget->set_edit_mode(m_edit_mode);
+ if(m_current_segment) m_editor_widget->set_segment(m_current_segment);
}
void TM::SocketConnection::do_blur(QJsonObject const &)
{
- //m_widget->detach(this);
+ //m_editor_widget->detach(this);
}
-void TM::SocketConnection::do_load(const QJsonObject &json)
+void TM::SocketConnection::do_load(QJsonObject const &json)
{
m_editor_widget->attach(this);
- bool edit_mode = json["edit_mode"].toBool();
- m_edit_mode = edit_mode;
- m_editor_widget->set_edit_mode(edit_mode);
- //set_edit_mode(m_widget->edit_mode());
+ set_edit_mode(m_editor_widget->edit_mode());
+
+ m_scode = m_editor_widget->source_language();
+ m_target_language_code = m_editor_widget->target_language();
+ m_url = QUrl(json["url"].toString());
+ m_site_id = m_service->find_site_id(m_url.host());
+}
+
+/*!
+ * \brief ブラウザがセグメントを読み込んだ結果呼び出されます。
+ *
+ * この処理の中で、セグメントの設定、完全一致訳文の検索を行います。
+ * セグメント全体の検索が終わった時点でブラウザへsegment_loaded応答を返します。
+ * ブラウザは、segment_loadedを受けて、そのセグメントをクリック可能にします。
+ */
+void TM::SocketConnection::do_load_segment(QJsonObject const &json)
+{
+ assert(json.contains("segment_id"));
+ int segment_id = json["segment_id"].toString().toInt();
+ assert(!m_segments.contains(segment_id));
+ assert(json.contains("html"));
+ QString html = json["html"].toString();
+
+ // セグメントの挿入。
+ TextSegment::pointer segment = TextSegment::create(
+ m_service, m_scode, segment_id, html);
+ m_segments.insert(segment_id, segment);
+
+ // センテンス完全一致訳文の検索。
+ for(TextSentence::pointer sentence : *segment)
+ {
+ m_service->find_sentence(
+ segment_id, sentence->index(),
+ m_url.host(), m_scode, m_target_language_code,
+ sentence, pointer(this));
+ }
}
void TM::SocketConnection::onTextMessageReceived(QString const &message)
{
QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object();
QString cmd = json["cmd"].toString();
- if(cmd == "edit") do_edit(json);
+ if(cmd == "edit_segment") do_edit_segment(json);
else if(cmd == "focus") do_focus(json);
else if(cmd == "blur") do_blur(json);
else if(cmd == "load") do_load(json);
+ else if(cmd == "load_segment") do_load_segment(json);
}
void TM::SocketConnection::onBinaryMessageReceived(QByteArray const &message)
// SocketServer ---------------------------------------------------------------
-TM::SocketServer::SocketServer(Settings *settings, EditorWidget *editor_widget, QObject *parent)
+TM::SocketServer::SocketServer(Settings *settings, Service* service,
+ EditorWidget *editor_widget, QObject *parent)
: QObject(parent)
, m_settings(settings)
+ , m_service(service)
, m_server(new QWebSocketServer(QStringLiteral("wordring websocket"),
QWebSocketServer::NonSecureMode, this))
, m_editor_widget(editor_widget)
quint16 port = m_settings->value("SocketServer/port").toUInt();
bool result = m_server->listen(QHostAddress::LocalHost, port);
if(!result) result = m_server->listen(QHostAddress::LocalHost, 0);
+ assert(result);
if(!result) qFatal("An error occured in SocketServer().");
}
{
QWebSocket *socket = m_server->nextPendingConnection();
connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
- new SocketConnection(m_editor_widget, socket);
+ new SocketConnection(m_settings, m_service, m_editor_widget, socket);
m_sockets.push_back(socket);
}