1 /*
2 
3   copyright (c) 2010-2013 uim Project https://github.com/uim/uim
4 
5   All rights reserved.
6 
7   Redistribution and use in source and binary forms, with or without
8   modification, are permitted provided that the following conditions
9   are met:
10 
11   1. Redistributions of source code must retain the above copyright
12      notice, this list of conditions and the following disclaimer.
13   2. Redistributions in binary form must reproduce the above copyright
14      notice, this list of conditions and the following disclaimer in the
15      documentation and/or other materials provided with the distribution.
16   3. Neither the name of authors nor the names of its contributors
17      may be used to endorse or promote products derived from this software
18      without specific prior written permission.
19 
20   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
21   ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE
24   FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26   OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29   OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30   SUCH DAMAGE.
31 
32 */
33 #include "candidatetablewindow.h"
34 
35 #include <cstdio>
36 
37 #include <QtGui/QFontMetrics>
38 #if QT_VERSION < 0x050000
39 # include <QtGui/QLabel>
40 # include <QtGui/QGridLayout>
41 # include <QtGui/QStyle>
42 # include <QtGui/QVBoxLayout>
43 #else
44 # include <QtWidgets/QLabel>
45 # include <QtWidgets/QGridLayout>
46 # include <QtWidgets/QStyle>
47 # include <QtWidgets/QVBoxLayout>
48 #endif
49 
50 #include <uim/uim-scm.h>
51 
52 static const int TABLE_NR_CELLS = TABLE_NR_COLUMNS * TABLE_NR_ROWS;
53 
54 static const int BLOCK_SPACING = 20;
55 static const int HOMEPOSITION_SPACING = 2;
56 
57 // labelchar_table consists of four blocks
58 //   blockLR  blockA
59 //   blockLRS blockAS
60 static const int L_WIDTH = 5;
61 static const int R_WIDTH = 5;
62 static const int A_WIDTH = 3;
63 
64 static const int A_HEIGHT = 4;
65 static const int AS_HEIGHT = 4;
66 
67 // 106 keyboard
68 static char DEFAULT_TABLE[TABLE_NR_CELLS] = {
69   '1','2','3','4','5', '6','7','8','9','0',   '-','^','\\',
70   'q','w','e','r','t', 'y','u','i','o','p',   '@','[','\0',
71   'a','s','d','f','g', 'h','j','k','l',';',   ':',']','\0',
72   'z','x','c','v','b', 'n','m',',','.','/',   '\0','\0',' ',
73   '!','"','#','$','%', '&','\'','(',')','\0', '=','~','|',
74   'Q','W','E','R','T', 'Y','U','I','O','P',   '`','{','\0',
75   'A','S','D','F','G', 'H','J','K','L','+',   '*','}','\0',
76   'Z','X','C','V','B', 'N','M','<','>','?',   '_','\0','\0',
77 };
78 
CandidateTableWindow(QWidget * parent)79 CandidateTableWindow::CandidateTableWindow(QWidget *parent)
80 : AbstractCandidateWindow(parent)
81 {
82     initTable();
83 
84     lLayout = createLayout(A_HEIGHT, L_WIDTH, 0, 0);
85     rLayout = createLayout(A_HEIGHT, R_WIDTH, 0, L_WIDTH);
86     aLayout = createLayout(A_HEIGHT, A_WIDTH, 0, L_WIDTH + R_WIDTH);
87     lsLayout = createLayout(AS_HEIGHT, L_WIDTH, A_HEIGHT, 0);
88     rsLayout = createLayout(AS_HEIGHT, R_WIDTH, A_HEIGHT, L_WIDTH);
89     asLayout = createLayout(AS_HEIGHT, A_WIDTH, A_HEIGHT, L_WIDTH + R_WIDTH);
90 
91     QGridLayout *buttonLayout = new QGridLayout;
92     buttonLayout->setSpacing(BLOCK_SPACING - 2 * HOMEPOSITION_SPACING);
93     buttonLayout->setMargin(0);
94     buttonLayout->addLayout(lLayout, 0, 0);
95     buttonLayout->addLayout(rLayout, 0, 1);
96     buttonLayout->addLayout(aLayout, 0, 2);
97     buttonLayout->addLayout(lsLayout, 1, 0);
98     buttonLayout->addLayout(rsLayout, 1, 1);
99     buttonLayout->addLayout(asLayout, 1, 2);
100 
101     QVBoxLayout *layout = new QVBoxLayout;
102     layout->setMargin(0);
103     layout->setSpacing(0);
104     layout->addLayout(buttonLayout);
105     layout->addWidget(numLabel);
106 
107     setLayout(layout);
108 }
109 
~CandidateTableWindow()110 CandidateTableWindow::~CandidateTableWindow()
111 {
112     if (table != DEFAULT_TABLE)
113         free(table);
114 }
115 
sizeHint() const116 QSize CandidateTableWindow::sizeHint() const
117 {
118     QRect lRect = lLayout->geometry();
119 
120     // height
121     // numLabel + lLayout
122     int height = numLabel->height() + lRect.height();
123     if (lsLayout->isEnabled()) {
124         // lsLayout
125         height += lsLayout->geometry().height()
126             + BLOCK_SPACING - 2 * HOMEPOSITION_SPACING;
127     }
128 
129     // width
130     // lLayout + rLayout
131     int width = lRect.width() + rLayout->geometry().width()
132         + BLOCK_SPACING - 2 * HOMEPOSITION_SPACING;
133     if (aLayout->isEnabled()) {
134         // aLayout
135         width += aLayout->geometry().width()
136             + BLOCK_SPACING - 2 * HOMEPOSITION_SPACING;
137     }
138 
139     return QSize(width, height);
140 }
141 
slotCandidateClicked(int index)142 void CandidateTableWindow::slotCandidateClicked(int index)
143 {
144     fprintf(stdout, "set_candidate_index\f%d\f\f", index);
145     fflush(stdout);
146     fprintf(stdout, "update_label\f\f");
147     fflush(stdout);
148 }
149 
initTableInternal()150 static char *initTableInternal()
151 {
152     uim_lisp list = uim_scm_symbol_value("uim-candwin-prog-layout");
153     if (!list || !uim_scm_listp(list))
154         return DEFAULT_TABLE;
155     size_t len = 0;
156     void **array = uim_scm_list2array(list, &len,
157         reinterpret_cast<void *(*)(uim_lisp)>(uim_scm_c_str));
158     if (!array || len <= 0) {
159         free(array);
160         return DEFAULT_TABLE;
161     }
162     char *table = static_cast<char *>(malloc(TABLE_NR_CELLS * sizeof(char)));
163     if (!table) {
164         free(array);
165         return DEFAULT_TABLE;
166     }
167     for (int i = 0; i < TABLE_NR_CELLS; i++) {
168         if (i >= static_cast<int>(len)) {
169             table[i] = '\0';
170             continue;
171         }
172         char *str = static_cast<char *>(array[i]);
173         // XXX: only use first char
174         table[i] = str[0];
175     }
176     free(array);
177     return table;
178 }
179 
initTable()180 void CandidateTableWindow::initTable()
181 {
182     uim_gc_gate_func_ptr func_body
183         = reinterpret_cast<uim_gc_gate_func_ptr>(initTableInternal);
184     void *ret = uim_scm_call_with_gc_ready_stack(func_body, 0);
185     table = static_cast<char *>(ret);
186 }
187 
createLayout(int row,int column,int rowOffset,int columnOffset)188 QGridLayout *CandidateTableWindow::createLayout(int row, int column,
189         int rowOffset, int columnOffset)
190 {
191     QGridLayout *layout = new QGridLayout;
192     layout->setSpacing(HOMEPOSITION_SPACING);
193     layout->setMargin(0);
194     for (int i = 0; i < row; i++) {
195         for (int j = 0; j < column; j++) {
196             KeyButton *button = new KeyButton;
197             connect(button, SIGNAL(candidateClicked(int)),
198                 this, SLOT(slotCandidateClicked(int)));
199             int r = i + rowOffset;
200             int c = j + columnOffset;
201             buttonArray[r][c] = button;
202             if (table[r * TABLE_NR_COLUMNS + c] == '\0') {
203                 // Hide this button because some styles such as Oxygen
204                 // ignore the flat property.
205                 button->hide();
206                 button->setFlat(true);
207             }
208             layout->addWidget(button, i, j);
209         }
210     }
211     layout->addItem(new QSpacerItem(0, 0,
212         QSizePolicy::Expanding, QSizePolicy::Expanding), row, column);
213     return layout;
214 }
215 
isEmptyBlock(QGridLayout * layout)216 static bool isEmptyBlock(QGridLayout *layout)
217 {
218     for (int i = 0; i < layout->count(); i++) {
219         QWidget *widget = layout->itemAt(i)->widget();
220         if (widget && widget->isEnabled())
221             return false;
222     }
223     return true;
224 }
225 
setBlockVisible(QLayout * layout,bool visible)226 void CandidateTableWindow::setBlockVisible(QLayout *layout, bool visible)
227 {
228     if (visible == layout->isEnabled())
229         return;
230     layout->setEnabled(visible);
231     for (int i = 0; i < layout->count(); i++) {
232         QPushButton *widget
233             = qobject_cast<QPushButton*>(layout->itemAt(i)->widget());
234         // Flat buttons shouldn't be shown.
235         if (widget && !(visible && widget->isFlat()))
236             widget->setVisible(visible);
237     }
238 }
239 
updateView(int ncandidates,const QList<CandData> & stores)240 void CandidateTableWindow::updateView(int ncandidates,
241     const QList<CandData> &stores)
242 {
243     for (int i = 0; i < TABLE_NR_ROWS; i++) {
244         for (int j = 0; j < TABLE_NR_COLUMNS; j++) {
245             KeyButton *button = buttonArray[i][j];
246             button->setIndex(-1);
247             button->setEnabled(false);
248             button->setText("");
249         }
250     }
251     int index = 0;
252     int delta = 0;
253     for (int i = 0; i < TABLE_NR_ROWS; i++) {
254         for (int j = 0; j < TABLE_NR_COLUMNS; j++) {
255             if (table[index] == '\0') {
256                 delta++;
257                 index++;
258                 continue;
259             }
260             if (index - delta >= ncandidates)
261                 continue;
262             int candidateIndex = index - delta;
263             CandData cand = stores[candidateIndex];
264             QString candString = cand.str;
265             if (!candString.isEmpty()) {
266                 int row = i;
267                 int column = j;
268                 QString headString = cand.headingLabel;
269                 getButtonPosition(row, column, headString);
270                 KeyButton *b = buttonArray[row][column];
271                 // '&' shouldn't be used as the shortcut key
272                 b->setText(candString.replace('&', "&&"));
273                 b->setIndex(candidateIndex);
274                 b->setEnabled(true);
275             }
276             index++;
277         }
278     }
279 }
280 
updateSize()281 void CandidateTableWindow::updateSize()
282 {
283     // hide empty blocks.
284     // pattern0 (full table)
285     //   blockLR  blockA
286     //   blockLRS blockAS (for shift key)
287     // pattern1 (minimal blocks)
288     //   blockLR
289     // pattern2 (without shift blocks)
290     //   blockLR  blockA
291     // pattern3 (without symbol blocks)
292     //   blockLR
293     //   blockLRS
294     bool hasBlockA = !isEmptyBlock(aLayout);
295     bool hasBlockAs = !isEmptyBlock(asLayout);
296     bool hasBlockLrs = !(isEmptyBlock(lsLayout) && isEmptyBlock(rsLayout));
297 
298     setBlockVisible(aLayout, hasBlockA || hasBlockAs);
299     setBlockVisible(asLayout, hasBlockAs || (hasBlockA && hasBlockLrs));
300     setBlockVisible(lsLayout, hasBlockLrs || hasBlockAs);
301     setBlockVisible(rsLayout, hasBlockLrs || hasBlockAs);
302 
303     setMaximumSize(sizeHint());
304     setMinimumSize(QSize(0, 0));
305 }
306 
setIndex(int totalindex,int displayLimit,int candidateIndex)307 void CandidateTableWindow::setIndex(int totalindex, int displayLimit,
308     int candidateIndex)
309 {
310     Q_UNUSED(totalindex)
311     Q_UNUSED(displayLimit)
312     Q_UNUSED(candidateIndex)
313     fprintf(stdout, "update_label\f\f");
314     fflush(stdout);
315 }
316 
getButtonPosition(int & row,int & column,const QString & headString)317 void CandidateTableWindow::getButtonPosition(int &row, int &column,
318     const QString &headString)
319 {
320     char *ch = table;
321     for (int i = 0; i < TABLE_NR_ROWS; i++) {
322         for (int j = 0; j < TABLE_NR_COLUMNS; j++) {
323             if (*ch == '\0') {
324                 ch++;
325                 continue;
326             }
327             const char str[] = {*ch, '\0'};
328             if (headString == QLatin1String(str)) {
329                 row = i;
330                 column = j;
331                 return;
332             }
333             ch++;
334         }
335     }
336 }
337 
KeyButton()338 KeyButton::KeyButton() : m_index(-1)
339 {
340     connect(this, SIGNAL(clicked()), this, SLOT(slotClicked()));
341 }
342 
sizeHint() const343 QSize KeyButton::sizeHint() const
344 {
345     QSize size = QPushButton::sizeHint();
346     int margin = style()->pixelMetric(QStyle::PM_ButtonMargin);
347     int width = qMax(size.height(),
348         QFontMetrics(QFont()).boundingRect(text()).width() + margin * 2);
349     return QSize(width, size.height());
350 }
351 
setIndex(int index)352 void KeyButton::setIndex(int index)
353 {
354     m_index = index;
355 }
356 
index() const357 int KeyButton::index() const
358 {
359     return m_index;
360 }
361 
slotClicked()362 void KeyButton::slotClicked()
363 {
364     if (m_index >= 0)
365         emit candidateClicked(m_index);
366 }
367