1 // Copyright 2010-2018, Google Inc.
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 //     * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 //     * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 //     * Neither the name of Google Inc. nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 #include "gui/character_pad/character_palette.h"
31 
32 #include <QtGui/QtGui>
33 #include <QtWidgets/QMessageBox>
34 
35 #ifdef OS_WIN
36 #include <Windows.h>
37 #endif  // OS_WIN
38 
39 #include "base/logging.h"
40 #include "base/util.h"
41 #include "client/client.h"
42 #include "config/stats_config_util.h"
43 #include "gui/base/win_util.h"
44 #include "gui/character_pad/data/local_character_map.h"
45 #include "gui/character_pad/data/unicode_blocks.h"
46 #include "gui/character_pad/selection_handler.h"
47 #include "protocol/commands.pb.h"
48 
49 namespace mozc {
50 namespace gui {
51 namespace {
52 
53 const char32 kHexBase = 16;
54 
55 const char kUNICODEName[]  = "Unicode";
56 const char kCP932Name[]    = "Shift JIS";
57 const char kJISX0201Name[] = "JISX 0201";
58 const char kJISX0208Name[] = "JISX 0208";
59 const char kJISX0212Name[] = "JISX 0212";
60 
61 const CharacterPalette::UnicodeRange kUCS2Range = { 0, 0xffff };
62 
63 const CharacterPalette::CP932JumpTo kCP932JumpTo[] = {
64     {"半角英数字", 0x0020},
65     {"半角カタカナ", 0x00A1},
66     {"全角記号", 0x8141},
67     {"全角英数字", 0x8250},
68     {"ひらがな", 0x829F},
69     {"カタカナ", 0x8340},
70     {"丸数字", 0x8740},
71     {"ローマ数字", 0xFA40},
72     {"単位", 0x875F},
73     {"その他の記号", 0x8780},
74     {"ギリシャ文字", 0x839F},
75     {"キリル文字", 0x8440},
76     {"罫線", 0x849F},
77     {"第一水準漢字", 0x889F},
78     {"第二水準漢字", 0x989F},
79     {NULL, 0},
80 };
81 
82 // Adds a child QTreeWidgetItem with the text "name" to parent.
83 // Returns the added child item.
AddItem(QTreeWidgetItem * parent,const char * name)84 QTreeWidgetItem *AddItem(QTreeWidgetItem *parent, const char *name) {
85   QTreeWidgetItem *item = new QTreeWidgetItem(parent);
86   item->setText(0, QString::fromUtf8(name));
87   parent->addChild(item);
88   return item;
89 }
90 
91 }  // namespace
92 
CharacterPalette(QWidget * parent)93 CharacterPalette::CharacterPalette(QWidget *parent)
94     : QMainWindow(parent),
95       usage_stats_enabled_(mozc::config::StatsConfigUtil::IsEnabled()) {
96   // To reduce the disk IO of reading the stats config, we load it only when the
97   // class is initialized. There is no problem because the config dialog (on
98   // Mac) and the administrator dialog (on Windows) say that the usage stats
99   // setting changes will take effect after the re-login.
100 
101   if (usage_stats_enabled_) {
102     client_.reset(client::ClientFactory::NewClient());
103   }
104   setupUi(this);
105 
106   fontComboBox->setWritingSystem(
107       static_cast<QFontDatabase::WritingSystem>(QFontDatabase::Any));
108   fontComboBox->setEditable(false);
109   fontComboBox->setCurrentFont(tableWidget->font());
110 
111   QObject::connect(fontComboBox,
112                    SIGNAL(currentFontChanged(const QFont &)),
113                    this, SLOT(updateFont(const QFont &)));
114 
115   QObject::connect(sizeComboBox,
116                    SIGNAL(currentIndexChanged(int)),
117                    this, SLOT(updateFontSize(int)));
118 
119   sizeComboBox->setCurrentIndex(4);
120   fontComboBox->setCurrentFont(tableWidget->font());
121 
122   QObject::connect(tableWidget,
123                    SIGNAL(itemSelected(const QTableWidgetItem*)),
124                    this,
125                    SLOT(itemSelected(const QTableWidgetItem*)));
126 
127   // Category Tree
128   QObject::connect(categoryTreeWidget,
129                    SIGNAL(itemClicked(QTreeWidgetItem *, int)),
130                    SLOT(categorySelected(QTreeWidgetItem *, int)));
131 
132   QTreeWidgetItem *unicode_item = new QTreeWidgetItem;
133   unicode_item->setText(0, QString::fromUtf8(kUNICODEName));
134   QTreeWidgetItem *sjis_item = new QTreeWidgetItem;
135   sjis_item->setText(0, QString::fromUtf8(kCP932Name));
136   QTreeWidgetItem *jisx0201_item = new QTreeWidgetItem;
137   jisx0201_item->setText(0, QString::fromUtf8(kJISX0201Name));
138   QTreeWidgetItem *jisx0208_item = new QTreeWidgetItem;
139   jisx0208_item->setText(0, QString::fromUtf8(kJISX0208Name));
140   QTreeWidgetItem *jisx0212_item = new QTreeWidgetItem;
141   jisx0212_item->setText(0, QString::fromUtf8(kJISX0212Name));
142 
143   // Because almost all users use Shift-JIS table instead of
144   // Unicode table, Shift-JIS table is selected and child
145   // items of Shift-JIS table are expanded by default.
146   // In order to let user know the existence of the Unicode table,
147   // shows Unicode table at first, but don't expands the child items.
148   categoryTreeWidget->addTopLevelItem(unicode_item);
149   categoryTreeWidget->addTopLevelItem(sjis_item);
150   categoryTreeWidget->addTopLevelItem(jisx0201_item);
151   categoryTreeWidget->addTopLevelItem(jisx0208_item);
152   categoryTreeWidget->addTopLevelItem(jisx0212_item);
153 
154   for (size_t i = 0; kCP932JumpTo[i].name != NULL; ++i) {
155     AddItem(sjis_item, kCP932JumpTo[i].name);
156   }
157 
158   // Make Unicode block children and look-up table for each character range.
159   for (size_t i = 0; kUnicodeBlockTable[i].name != NULL; ++i) {
160     const UnicodeBlock &block = kUnicodeBlockTable[i];
161     const UnicodeRange &range = block.range;
162     QTreeWidgetItem *item = new QTreeWidgetItem(unicode_item);
163     const QString original_name(block.name);
164     const QString translated_name(QObject::tr(block.name));
165     unicode_block_map_[original_name] = range;
166     unicode_block_map_[translated_name] = range;
167     item->setText(0, translated_name);
168     unicode_item->addChild(item);
169   }
170 
171   // Adjust the splitter.
172   splitter->setSizes(QList<int>()
173                      << static_cast<int>(width() * 0.25)
174                      << static_cast<int>(width() * 0.75));
175 
176   // set default table
177   showLocalTable(kCP932Map, kCP932MapSize);
178   categoryTreeWidget->setCurrentItem(sjis_item);
179 
180   categoryTreeWidget->setItemExpanded(
181       categoryTreeWidget->topLevelItem(0)->parent(),
182       true);
183 
184   // Select "Shift-JIS" item as a default.
185   categoryTreeWidget->setCurrentItem(sjis_item);
186   sjis_item->setExpanded(true);
187 
188   tableWidget->setAutoScroll(false);
189 
190   if (usage_stats_enabled_) {
191     CHECK(client_.get());
192     // Sends the usage stats event (CHARACTER_PALETTE_OPEN_EVENT) to mozc
193     // converter.
194     commands::SessionCommand command;
195     command.set_type(commands::SessionCommand::USAGE_STATS_EVENT);
196     command.set_usage_stats_event(
197         commands::SessionCommand::CHARACTER_PALETTE_OPEN_EVENT);
198     commands::Output dummy_output;
199     client_->SendCommand(command, &dummy_output);
200   }
201 
202   repaint();
203   update();
204 }
205 
~CharacterPalette()206 CharacterPalette::~CharacterPalette() {}
207 
updateFont(const QFont & font)208 void CharacterPalette::updateFont(const QFont &font) {
209   QFont new_font = font;
210   new_font.setPointSize(tableWidget->font().pointSize());
211   tableWidget->setFont(new_font);
212   tableWidget->adjustSize();
213   updateTableSize();
214 }
215 
updateFontSize(int index)216 void CharacterPalette::updateFontSize(int index) {
217   int font_point = 24;
218   switch (index) {
219     case 0: font_point = 32; break;
220     case 1: font_point = 24; break;
221     case 2: font_point = 16; break;
222     case 3: font_point = 14; break;
223     case 4: font_point = 12; break;
224   }
225 
226   QFont font;
227   font.setPointSize(font_point);
228   tableWidget->setFont(font);
229 
230   updateTableSize();
231   tableWidget->adjustSize();
232 
233   QTableWidgetItem *item = tableWidget->currentItem();
234   if (item != NULL) {
235     tableWidget->scrollToItem(item,
236                               QAbstractItemView::PositionAtCenter);
237   }
238 }
239 
resizeEvent(QResizeEvent *)240 void CharacterPalette::resizeEvent(QResizeEvent *) {
241   updateTableSize();
242 }
243 
updateTableSize()244 void CharacterPalette::updateTableSize() {
245   // here we use "龍" to calc font size, as it looks almsot square
246   const char kHexBaseChar[]= "龍";
247   const QRect rect =
248       QFontMetrics(tableWidget->font()).boundingRect(trUtf8(kHexBaseChar));
249 
250 #ifdef OS_MACOSX
251   const int width = static_cast<int>(rect.width() * 2.2);
252   const int height = static_cast<int>(rect.height() * 2.0);
253 #else
254   const int width = static_cast<int>(rect.width() * 1.6);
255   const int height = static_cast<int>(rect.height() * 1.2);
256 #endif
257 
258   for (int j = 0; j < tableWidget->columnCount(); ++j) {
259     tableWidget->setColumnWidth(j, width);
260   }
261   for (int j = 0; j < tableWidget->rowCount(); ++j) {
262     tableWidget->setRowHeight(j, height);
263   }
264   tableWidget->setLookupResultItem(NULL);
265 }
266 
categorySelected(QTreeWidgetItem * item,int column)267 void CharacterPalette::categorySelected(QTreeWidgetItem *item,
268                                         int column) {
269   const QString &text = item->text(column);
270   const QTreeWidgetItem *parent = item->parent();
271 
272   item->setExpanded(!item->isExpanded());
273 
274   if (text == kUNICODEName) {     // TOP Unicode
275     // The number of characters within entire Unicode range is now too large to
276     // show in the table. So we show only UCS2 range when |kUNICODEName| is
277     // clicked.
278     showUnicodeTableByRange(kUCS2Range);
279   } else if (parent != NULL && parent->text(0) == kUNICODEName) {
280     showUnicodeTableByBlockName(text);
281   } else if (parent != NULL && parent->text(0) == kCP932Name) {
282     showSJISBlockTable(text);
283   } else if (text == kJISX0201Name) {
284     showLocalTable(kJISX0201Map, kJISX0201MapSize);
285   } else if (text == kJISX0208Name) {
286     showLocalTable(kJISX0208Map, kJISX0208MapSize);
287   } else if (text == kJISX0212Name) {
288     showLocalTable(kJISX0212Map, kJISX0212MapSize);
289   } else if (text == kCP932Name) {
290     showLocalTable(kCP932Map, kCP932MapSize);
291   }
292 }
293 
itemSelected(const QTableWidgetItem * item)294 void CharacterPalette::itemSelected(const QTableWidgetItem *item) {
295   if (!usage_stats_enabled_) {
296     return;
297   }
298   CHECK(client_.get());
299   // Sends the usage stats event (CHARACTER_PALETTE_COMMIT_EVENT) to mozc
300   // converter.
301   commands::SessionCommand command;
302   command.set_type(commands::SessionCommand::USAGE_STATS_EVENT);
303   command.set_usage_stats_event(
304       commands::SessionCommand::CHARACTER_PALETTE_COMMIT_EVENT);
305   commands::Output dummy_output;
306   client_->SendCommand(command, &dummy_output);
307 }
308 
309 // Unicode operations
showUnicodeTableByRange(const UnicodeRange & range)310 void CharacterPalette::showUnicodeTableByRange(const UnicodeRange &range) {
311   tableWidget->hide();
312   tableWidget->clear();
313 
314   QStringList column_header;
315   for (int col = 0; col < kHexBase; ++col) {
316     column_header << QString::number(col, kHexBase).toUpper();
317   }
318 
319   QStringList row_header;
320   for (char32 ucs4 = range.first; ucs4 <= range.last; ucs4 += kHexBase) {
321     QString str;
322     str.sprintf("U+%3.3X0", ucs4 / kHexBase);
323     row_header << str;
324   }
325 
326   tableWidget->setColumnCount(kHexBase);
327   tableWidget->setRowCount(row_header.size());
328 
329   tableWidget->setHorizontalHeaderLabels(column_header);
330   tableWidget->setVerticalHeaderLabels(row_header);
331 
332   const char32 offset = range.first / kHexBase;
333   for (char32 ucs4 = range.first; ucs4 <= range.last; ++ucs4) {
334     const char32 ucs4s[] = { ucs4 };
335     QTableWidgetItem *item =
336         new QTableWidgetItem(QString::fromUcs4(ucs4s, arraysize(ucs4s)));
337     item->setTextAlignment(Qt::AlignCenter);
338     tableWidget->setItem(ucs4 / kHexBase - offset, ucs4 % kHexBase, item);
339     tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
340   }
341 
342   tableWidget->scrollToItem(tableWidget->item(0, 0),
343                             QAbstractItemView::PositionAtTop);
344   tableWidget->setLookupResultItem(NULL);
345   tableWidget->show();
346 }
347 
showSJISBlockTable(const QString & name)348 void CharacterPalette::showSJISBlockTable(const QString &name) {
349   const CP932JumpTo *block = NULL;
350   for (int i = 0; kCP932JumpTo[i].name !=NULL; ++i) {
351     if (name == QString::fromUtf8(kCP932JumpTo[i].name)) {
352       block = &kCP932JumpTo[i];
353       break;
354     }
355   }
356 
357   if (block == NULL) {
358     return;
359   }
360 
361   showLocalTable(kCP932Map, kCP932MapSize);
362 
363   tableWidget->hide();
364   QTableWidgetItem *item = tableWidget->item(block->from / kHexBase -
365                                              kCP932Map[0].from / kHexBase,
366                                              block->from % kHexBase);
367 
368   if (item != NULL) {
369     tableWidget->scrollToItem(item, QAbstractItemView::PositionAtTop);
370     item->setSelected(true);
371   }
372 
373   tableWidget->setLookupResultItem(NULL);
374   tableWidget->show();
375 }
376 
showUnicodeTableByBlockName(const QString & block_name)377 void CharacterPalette::showUnicodeTableByBlockName(const QString &block_name) {
378   QMap<QString, UnicodeRange>::const_iterator
379       it = unicode_block_map_.find(block_name);
380   if (it == unicode_block_map_.end()) {
381     return;
382   }
383   showUnicodeTableByRange(it.value());
384 }
385 
386 // Local table
showLocalTable(const LocalCharacterMap * local_map,size_t local_map_size)387 void CharacterPalette::showLocalTable(const LocalCharacterMap *local_map,
388                                       size_t local_map_size) {
389   tableWidget->hide();
390   tableWidget->clear();
391 
392   QStringList column_header;
393   for (int col = 0; col < kHexBase; ++col) {
394     column_header << QString::number(col, kHexBase).toUpper();
395   }
396 
397   // find range
398   const int from_start = local_map[0].from;
399   const int from_end   = local_map[local_map_size - 1].from + kHexBase;
400 
401   QStringList row_header;
402   for (int i = from_start; i < from_end; i += kHexBase) {
403     QString str;
404     str.sprintf("0x%X0", i / kHexBase);
405     row_header << str;
406   }
407 
408   tableWidget->setColumnCount(kHexBase);
409   tableWidget->setRowCount(row_header.size());
410 
411   tableWidget->setHorizontalHeaderLabels(column_header);
412   tableWidget->setVerticalHeaderLabels(row_header);
413 
414   const int offset = from_start / kHexBase;
415   for (size_t i = 0; i < local_map_size; ++i) {
416     // We do not use QString(QChar(i)) but Util::UCS4ToUTF8 because
417     // QChar is only 16-bit.
418     string utf8;
419     Util::UCS4ToUTF8(local_map[i].ucs2, &utf8);
420     QTableWidgetItem *item = new QTableWidgetItem(
421         QString::fromUtf8(utf8.data(), utf8.size()));
422     item->setTextAlignment(Qt::AlignCenter);
423     tableWidget->setItem(local_map[i].from / kHexBase - offset,
424                          local_map[i].from % kHexBase, item);
425     tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
426   }
427 
428   tableWidget->scrollToItem(tableWidget->item(0, 0),
429                             QAbstractItemView::PositionAtCenter);
430 
431   tableWidget->setLookupResultItem(NULL);
432   tableWidget->show();
433 }
434 
435 }  // namespace gui
436 }  // namespace mozc
437