| // Copyright 2010-2015, Google Inc. |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are |
| // met: |
| // |
| // * Redistributions of source code must retain the above copyright |
| // notice, this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above |
| // copyright notice, this list of conditions and the following disclaimer |
| // in the documentation and/or other materials provided with the |
| // distribution. |
| // * Neither the name of Google Inc. nor the names of its |
| // contributors may be used to endorse or promote products derived from |
| // this software without specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "gui/word_register_dialog/word_register_dialog.h" |
| |
| #ifdef OS_WIN |
| # include <windows.h> |
| # include <imm.h> |
| #endif // OS_WIN |
| |
| #include <QtGui/QtGui> |
| #include <cstdlib> |
| #ifdef OS_WIN |
| #include <memory> // for std::unique_ptr |
| #endif // OS_WIN |
| #include <string> |
| #include <vector> |
| |
| #include "base/const.h" |
| #include "base/logging.h" |
| #include "base/util.h" |
| #include "client/client.h" |
| #include "data_manager/user_pos_manager.h" |
| #include "dictionary/user_dictionary_session.h" |
| #include "dictionary/user_dictionary_storage.h" |
| #include "dictionary/user_dictionary_storage.pb.h" |
| #include "dictionary/user_dictionary_util.h" |
| #include "dictionary/user_pos.h" |
| |
| namespace mozc { |
| namespace gui { |
| |
| using mozc::user_dictionary::UserDictionary; |
| using mozc::user_dictionary::UserDictionaryCommandStatus; |
| using mozc::user_dictionary::UserDictionarySession; |
| using mozc::user_dictionary::UserDictionaryStorage; |
| #ifdef OS_WIN |
| using std::unique_ptr; |
| #endif // OS_WIN |
| |
| namespace { |
| const int kSessionTimeout = 100000; |
| const int kMaxEditLength = 100; |
| const int kMaxReverseConversionLength = 30; |
| |
| QString GetEnv(const char *envname) { |
| #if defined(OS_WIN) |
| wstring wenvname; |
| mozc::Util::UTF8ToWide(envname, &wenvname); |
| const DWORD buffer_size = |
| ::GetEnvironmentVariable(wenvname.c_str(), NULL, 0); |
| if (buffer_size == 0) { |
| return ""; |
| } |
| unique_ptr<wchar_t[]> buffer(new wchar_t[buffer_size]); |
| const DWORD num_copied = |
| ::GetEnvironmentVariable(wenvname.c_str(), buffer.get(), buffer_size); |
| if (num_copied > 0) { |
| return QString::fromWCharArray(buffer.get()); |
| } |
| return ""; |
| #endif // OS_WIN |
| #if defined(OS_MACOSX) || defined(OS_LINUX) |
| return ::getenv(envname); |
| #endif // OS_MACOSX or OS_LINUX |
| // TODO(team): Support other platforms. |
| return ""; |
| } |
| } // anonymous namespace |
| |
| WordRegisterDialog::WordRegisterDialog() |
| : is_available_(true), |
| session_(new UserDictionarySession( |
| UserDictionaryUtil::GetUserDictionaryFileName())), |
| client_(client::ClientFactory::NewClient()), |
| window_title_(tr("Mozc")), |
| user_pos_(new dictionary::UserPOS( |
| UserPosManager::GetUserPosManager()->GetUserPOSData())) { |
| setupUi(this); |
| setWindowFlags(Qt::WindowSystemMenuHint | Qt::WindowStaysOnTopHint); |
| setWindowModality(Qt::NonModal); |
| |
| ReadinglineEdit->setMaxLength(kMaxEditLength); |
| WordlineEdit->setMaxLength(kMaxEditLength); |
| |
| if (!SetDefaultEntryFromEnvironmentVariable()) { |
| #ifdef OS_WIN |
| // On Windows, try to use clipboard as a fallback. |
| SetDefaultEntryFromClipboard(); |
| #endif // OS_WIN |
| } |
| |
| client_->set_timeout(kSessionTimeout); |
| |
| if (session_->Load() != |
| UserDictionaryCommandStatus::USER_DICTIONARY_COMMAND_SUCCESS) { |
| LOG(WARNING) << "UserDictionarySession::Load() failed"; |
| } |
| |
| if (!session_->mutable_storage()->Lock()) { |
| QMessageBox::information( |
| this, window_title_, |
| tr("Close dictionary tool before using word register dialog.")); |
| is_available_ = false; |
| return; |
| } |
| |
| #ifndef ENABLE_CLOUD_SYNC |
| if (session_->mutable_storage() |
| ->ConvertSyncDictionariesToNormalDictionaries()) { |
| LOG(INFO) << "Syncable dictionaries are converted to normal dictionaries"; |
| session_->mutable_storage()->Save(); |
| } |
| #endif // !ENABLE_CLOUD_SYNC |
| |
| // Initialize ComboBox |
| vector<string> pos_set; |
| user_pos_->GetPOSList(&pos_set); |
| CHECK(!pos_set.empty()); |
| |
| for (size_t i = 0; i < pos_set.size(); ++i) { |
| CHECK(!pos_set[i].empty()); |
| PartOfSpeechcomboBox->addItem(pos_set[i].c_str()); |
| } |
| |
| // Create new dictionary if empty |
| if (!session_->mutable_storage()->Exists() || |
| session_->storage().dictionaries_size() == 0) { |
| const QString name = tr("User Dictionary 1"); |
| uint64 dic_id = 0; |
| if (!session_->mutable_storage()->CreateDictionary( |
| name.toStdString(), &dic_id)) { |
| LOG(ERROR) << "Failed to create a new dictionary."; |
| is_available_ = false; |
| return; |
| } |
| } |
| |
| // Load Dictionary List |
| { |
| const UserDictionaryStorage &storage = session_->storage(); |
| CHECK_GT(storage.dictionaries_size(), 0); |
| for (size_t i = 0; i < storage.dictionaries_size(); ++i) { |
| DictionarycomboBox->addItem(storage.dictionaries(i).name().c_str()); |
| } |
| } |
| |
| connect(WordlineEdit, SIGNAL(textChanged(const QString &)), |
| this, SLOT(LineEditChanged(const QString &))); |
| connect(ReadinglineEdit, SIGNAL(textChanged(const QString &)), |
| this, SLOT(LineEditChanged(const QString &))); |
| connect(WordlineEdit, SIGNAL(editingFinished()), |
| this, SLOT(CompleteReading())); |
| connect(WordRegisterDialogbuttonBox, |
| SIGNAL(clicked(QAbstractButton *)), |
| this, |
| SLOT(Clicked(QAbstractButton *))); |
| connect(LaunchDictionaryToolpushButton, SIGNAL(clicked()), |
| this, SLOT(LaunchDictionaryTool())); |
| |
| if (!WordlineEdit->text().isEmpty()) { |
| ReadinglineEdit->setFocus(Qt::OtherFocusReason); |
| if (!ReadinglineEdit->text().isEmpty()) { |
| ReadinglineEdit->selectAll(); |
| } |
| } |
| |
| UpdateUIStatus(); |
| |
| // Turn on IME |
| EnableIME(); |
| } |
| |
| WordRegisterDialog::~WordRegisterDialog() {} |
| |
| bool WordRegisterDialog::IsAvailable() const { |
| return is_available_; |
| } |
| |
| void WordRegisterDialog::LineEditChanged(const QString &str) { |
| UpdateUIStatus(); |
| } |
| |
| void WordRegisterDialog::CompleteReading() { |
| if (ReadinglineEdit->text().isEmpty()) { |
| ReadinglineEdit->setText(GetReading(WordlineEdit->text())); |
| ReadinglineEdit->selectAll(); |
| } |
| UpdateUIStatus(); |
| } |
| |
| void WordRegisterDialog::UpdateUIStatus() { |
| const bool enabled = |
| !ReadinglineEdit->text().isEmpty() && |
| !WordlineEdit->text().isEmpty(); |
| |
| QAbstractButton *button = |
| WordRegisterDialogbuttonBox->button(QDialogButtonBox::Ok); |
| if (button != NULL) { |
| button->setEnabled(enabled); |
| } |
| } |
| |
| void WordRegisterDialog::Clicked(QAbstractButton *button) { |
| switch (WordRegisterDialogbuttonBox->buttonRole(button)) { |
| case QDialogButtonBox::AcceptRole: |
| switch (SaveEntry()) { |
| case EMPTY_KEY: |
| case EMPTY_VALUE: |
| LOG(FATAL) << "key/value is empty. This case will never occur."; |
| return; |
| case INVALID_KEY: |
| QMessageBox::warning( |
| this, window_title_, |
| tr("Reading part contains invalid characters.")); |
| return; |
| case INVALID_VALUE: |
| QMessageBox::warning( |
| this, window_title_, |
| tr("Word part contains invalid characters.")); |
| return; |
| case FATAL_ERROR: |
| QMessageBox::warning( |
| this, window_title_, tr("Unexpected error occurs.")); |
| break; |
| case SAVE_FAILURE: |
| QMessageBox::warning( |
| this, window_title_, tr("Failed to update user dictionary.")); |
| break; |
| case SAVE_SUCCESS: |
| break; |
| default: |
| return; |
| } |
| QDialog::accept(); |
| break; |
| default: |
| QDialog::reject(); |
| break; |
| } |
| } |
| |
| WordRegisterDialog::ErrorCode WordRegisterDialog::SaveEntry() { |
| const string key = ReadinglineEdit->text().toStdString(); |
| const string value = WordlineEdit->text().toStdString(); |
| UserDictionary::PosType pos = UserDictionaryUtil::ToPosType( |
| PartOfSpeechcomboBox->currentText().toStdString().c_str()); |
| |
| if (key.empty()) { |
| return EMPTY_KEY; |
| } |
| |
| if (value.empty()) { |
| return EMPTY_VALUE; |
| } |
| |
| if (!UserDictionaryUtil::IsValidReading(key)) { |
| return INVALID_KEY; |
| } |
| |
| if (!UserDictionary::PosType_IsValid(pos)) { |
| LOG(ERROR) << "POS is invalid"; |
| return FATAL_ERROR; |
| } |
| |
| const int index = DictionarycomboBox->currentIndex(); |
| if (index < 0 || index >= session_->storage().dictionaries_size()) { |
| LOG(ERROR) << "index is out of range"; |
| return FATAL_ERROR; |
| } |
| |
| UserDictionary *dic = |
| session_->mutable_storage()->mutable_dictionaries(index); |
| CHECK(dic); |
| |
| if (dic->name() != DictionarycomboBox->currentText().toStdString()) { |
| LOG(ERROR) << "Inconsitent dictionary name"; |
| return FATAL_ERROR; |
| } |
| |
| UserDictionary::Entry *entry = dic->add_entries(); |
| CHECK(entry); |
| entry->set_key(key); |
| entry->set_value(value); |
| entry->set_pos(pos); |
| |
| if (!session_->mutable_storage()->Save() && |
| session_->mutable_storage()->GetLastError() == |
| mozc::UserDictionaryStorage::SYNC_FAILURE) { |
| LOG(ERROR) << "Cannot save dictionary"; |
| return SAVE_FAILURE; |
| } |
| |
| if (!client_->PingServer()) { |
| LOG(WARNING) << "Server is not running. Do nothing"; |
| return SAVE_SUCCESS; |
| } |
| |
| #ifndef OS_MACOSX |
| // Update server version if need be. |
| if (!client_->CheckVersionOrRestartServer()) { |
| LOG(ERROR) << "CheckVersionOrRestartServer failed"; |
| return SAVE_SUCCESS; |
| } |
| #endif // OS_MACOSX |
| |
| if (!client_->Reload()) { |
| LOG(ERROR) << "Reload command failed"; |
| return SAVE_SUCCESS; |
| } |
| |
| return SAVE_SUCCESS; |
| } |
| |
| void WordRegisterDialog::LaunchDictionaryTool() { |
| session_->mutable_storage()->UnLock(); |
| client_->LaunchTool("dictionary_tool", ""); |
| QWidget::close(); |
| } |
| |
| const QString WordRegisterDialog::GetReading(const QString &str) { |
| if (str.isEmpty()) { |
| LOG(ERROR) << "given string is empty"; |
| return ""; |
| } |
| |
| if (str.count() >= kMaxReverseConversionLength) { |
| LOG(ERROR) << "too long input"; |
| return ""; |
| } |
| |
| commands::Output output; |
| { |
| commands::KeyEvent key; |
| key.set_special_key(commands::KeyEvent::ON); |
| if (!client_->SendKey(key, &output)) { |
| LOG(ERROR) << "SendKey failed"; |
| return ""; |
| } |
| |
| commands::SessionCommand command; |
| command.set_type(commands::SessionCommand::CONVERT_REVERSE); |
| command.set_text(str.toStdString()); |
| |
| if (!client_->SendCommand(command, &output)) { |
| LOG(ERROR) << "SendCommand failed"; |
| return ""; |
| } |
| |
| commands::Output dummy_output; |
| command.set_type(commands::SessionCommand::REVERT); |
| client_->SendCommand(command, &dummy_output); |
| } |
| |
| if (!output.has_preedit()) { |
| LOG(ERROR) << "No preedit"; |
| return ""; |
| } |
| |
| string key; |
| for (size_t segment_index = 0; |
| segment_index < output.preedit().segment_size(); |
| ++segment_index) { |
| const commands::Preedit::Segment &segment = |
| output.preedit().segment(segment_index); |
| if (!segment.has_key()) { |
| LOG(ERROR) << "No segment"; |
| return ""; |
| } |
| key.append(segment.key()); |
| } |
| |
| if (key.empty() || |
| !UserDictionaryUtil::IsValidReading(key)) { |
| LOG(WARNING) << "containing invalid characters"; |
| return ""; |
| } |
| |
| return QString(key.c_str()); |
| } |
| |
| // Get default value from Clipboard |
| void WordRegisterDialog::SetDefaultEntryFromClipboard() { |
| if (QApplication::clipboard() == NULL) { |
| return; |
| } |
| CopyCurrentSelectionToClipboard(); |
| const QString value = TrimValue(QApplication::clipboard()->text()); |
| WordlineEdit->setText(value); |
| ReadinglineEdit->setText(GetReading(value)); |
| } |
| |
| void WordRegisterDialog::CopyCurrentSelectionToClipboard() { |
| #ifdef OS_WIN |
| const HWND foreground_window = ::GetForegroundWindow(); |
| if (foreground_window == NULL) { |
| LOG(ERROR) << "GetForegroundWindow() failed: " << ::GetLastError(); |
| return; |
| } |
| |
| const DWORD thread_id = |
| ::GetWindowThreadProcessId(foreground_window, NULL); |
| |
| if (!::AttachThreadInput(::GetCurrentThreadId(), thread_id, TRUE)) { |
| LOG(ERROR) << "AttachThreadInput failed: " << ::GetLastError(); |
| return; |
| } |
| |
| const HWND focus_window = ::GetFocus(); |
| |
| ::AttachThreadInput(::GetCurrentThreadId(), thread_id, FALSE); |
| |
| if (focus_window == NULL || !::IsWindow(focus_window)) { |
| LOG(WARNING) << "No focus window"; |
| return; |
| } |
| |
| DWORD message_response = 0; |
| const DWORD kSendMessageTimeout = 10 * 1000; // 10sec. |
| const LRESULT send_result = |
| ::SendMessageTimeout(focus_window, WM_COPY, 0, 0, SMTO_NORMAL, |
| kSendMessageTimeout, &message_response); |
| if (send_result == 0) { |
| LOG(ERROR) << "SendMessageTimeout() failed: " << ::GetLastError(); |
| } |
| #endif // OS_WIN |
| |
| return; |
| } |
| |
| bool WordRegisterDialog::SetDefaultEntryFromEnvironmentVariable() { |
| const QString entry = TrimValue(GetEnv(mozc::kWordRegisterEnvironmentName)); |
| if (entry.isEmpty()) { |
| return false; |
| } |
| WordlineEdit->setText(entry); |
| |
| QString reading_string = |
| TrimValue(GetEnv(mozc::kWordRegisterEnvironmentReadingName)); |
| if (reading_string.isEmpty()) { |
| reading_string = GetReading(entry); |
| } |
| ReadinglineEdit->setText(reading_string); |
| |
| return true; |
| } |
| |
| const QString WordRegisterDialog::TrimValue(const QString &str) const { |
| return str.trimmed().replace('\r', "").replace('\n', ""); |
| } |
| |
| void WordRegisterDialog::EnableIME() { |
| #ifdef OS_WIN |
| // TODO(taku): implement it for other platform. |
| HIMC himc = ::ImmGetContext(winId()); |
| if (himc != NULL) { |
| ::ImmSetOpenStatus(himc, TRUE); |
| } |
| #endif // OS_WIN |
| } |
| } // namespace gui |
| } // namespace mozc |