1 // This file is part of the SpeedCrunch project
2 // Copyright (C) 2005-2006 Johan Thelin <e8johan@gmail.com>
3 // Copyright (C) 2007-2009, 2014 @heldercorreia
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License
7 // as published by the Free Software Foundation; either version 2
8 // of the License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; see the file COPYING.  If not, write to
17 // the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 // Boston, MA 02110-1301, USA.
19 
20 #include "gui/keypad.h"
21 
22 #include "core/settings.h"
23 
24 #include <QLocale>
25 #include <QHash>
26 #include <QSignalMapper>
27 #include <QApplication>
28 #include <QGridLayout>
29 #include <QPushButton>
30 #include <QStyle>
31 #include <QStyleOptionButton>
32 
33 #if QT_VERSION >= 0x040400 && defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC)
34 #include <QMacStyle>
35 #endif
36 
37 const Keypad::KeyDescription Keypad::keyDescriptions[] = {
38     {QString::fromLatin1("0"), Key0, true, 3, 0},
39     {QString::fromLatin1("1"), Key1, true, 2, 0},
40     {QString::fromLatin1("2"), Key2, true, 2, 1},
41     {QString::fromLatin1("3"), Key3, true, 2, 2},
42     {QString::fromLatin1("4"), Key4, true, 1, 0},
43     {QString::fromLatin1("5"), Key5, true, 1, 1},
44     {QString::fromLatin1("6"), Key6, true, 1, 2},
45     {QString::fromLatin1("7"), Key7, true, 0, 0},
46     {QString::fromLatin1("8"), Key8, true, 0, 1},
47     {QString::fromLatin1("9"), Key9, true, 0, 2},
48     {QString::fromLatin1(","), KeyRadixChar, true, 3, 1},
49     {QString::fromUtf8("="), KeyEquals, true, 3, 2},
50     {QString::fromUtf8("÷"), KeyDivide, true, 0, 3},
51     {QString::fromUtf8("×"), KeyTimes, true, 1, 3},
52     {QString::fromUtf8("−"), KeyMinus, true, 2, 3},
53     {QString::fromUtf8("+"), KeyPlus, true, 3, 3},
54     {QString::fromLatin1("arccos"), KeyAcos, false, 2, 8},
55     {QString::fromLatin1("ans"), KeyAns, false, 1, 6},
56     {QString::fromLatin1("arcsin"), KeyAsin, false, 1, 8},
57     {QString::fromLatin1("arctan"), KeyAtan, false, 3, 8},
58     {QString::fromLatin1("C"), KeyClear, false, 0, 4},
59     {QString::fromLatin1("cos"), KeyCos, false, 2, 7},
60     {QString::fromLatin1("E"), KeyEE, false, 1, 4},
61     {QString::fromLatin1("exp"), KeyExp, false, 0, 7},
62     {QString::fromLatin1("!"), KeyFactorial, false, 3, 5},
63     {QString::fromLatin1("ln"), KeyLn, false, 0, 8},
64     {QString::fromLatin1("("), KeyLeftPar, false, 2, 4},
65     {QString::fromLatin1("%"), KeyPercent, false, 3, 4},
66     {QString::fromLatin1("^"), KeyRaise, false, 1, 5},
67     {QString::fromLatin1(")"), KeyRightPar, false, 2, 5},
68     {QString::fromLatin1("sin"), KeySin, false, 1, 7},
69     {QString::fromLatin1("tan"), KeyTan, false, 3, 7},
70     {QString::fromLatin1("x="), KeyXEquals, false, 3, 6},
71     {QString::fromLatin1("x"), KeyX, false, 2, 6},
72     {QString::fromUtf8("π"), KeyPi, false, 0, 6},
73     {QString::fromUtf8("√"), KeySqrt, false, 0, 5}
74 };
75 
Keypad(QWidget * parent)76 Keypad::Keypad(QWidget* parent)
77     : QWidget(parent)
78 {
79     createButtons();
80     sizeButtons();
81     layoutButtons();
82     setButtonTooltips();
83     disableButtonFocus();
84     setLayoutDirection(Qt::LeftToRight);
85     connect(&mapper, SIGNAL(mapped(int)), SLOT(emitButtonPressed(int)));
86 }
87 
handleRadixCharacterChange()88 void Keypad::handleRadixCharacterChange()
89 {
90     key(KeyRadixChar)->setText(QString(QChar(Settings::instance()->radixCharacter())));
91 }
92 
retranslateText()93 void Keypad::retranslateText()
94 {
95     setButtonTooltips();
96     handleRadixCharacterChange();
97 }
98 
emitButtonPressed(int button) const99 void Keypad::emitButtonPressed(int button) const
100 {
101     emit buttonPressed(Button(button));
102 }
103 
key(Button button) const104 QPushButton* Keypad::key(Button button) const
105 {
106     Q_ASSERT(keys.contains(button));
107     return keys.value(button).first;
108 }
109 
createButtons()110 void Keypad::createButtons()
111 {
112     keys.clear();
113 
114     QFont boldFont;
115     boldFont.setBold(true);
116     boldFont.setPointSize(boldFont.pointSize() + 3);
117 
118     static const int keyDescriptionsCount = int(sizeof keyDescriptions / sizeof keyDescriptions[0]);
119     for (int i = 0; i < keyDescriptionsCount; ++i) {
120         const KeyDescription* description = keyDescriptions + i;
121         QPushButton* key = new QPushButton(description->label);
122         if (description->boldFont)
123             key->setFont(boldFont);
124         const QPair<QPushButton*, const KeyDescription*> hashValue(key, description);
125         keys.insert(description->button, hashValue);
126 
127         QObject::connect(key, SIGNAL(clicked()), &mapper, SLOT(map()));
128         mapper.setMapping(key, description->button);
129     }
130 
131     handleRadixCharacterChange();
132 }
133 
disableButtonFocus()134 void Keypad::disableButtonFocus()
135 {
136     QHashIterator<Button, QPair<QPushButton*, const KeyDescription*> > i(keys);
137     while (i.hasNext()) {
138         i.next();
139         i.value().first->setFocusPolicy(Qt::NoFocus);
140     }
141 }
142 
layoutButtons()143 void Keypad::layoutButtons()
144 {
145     int layoutSpacing = 3;
146 
147 #if QT_VERSION >= 0x040400 && defined(Q_WS_MAC) && !defined(QT_NO_STYLE_MAC)
148     // Workaround for a layouting bug in QMacStyle, Qt 4.4.0. Buttons would overlap.
149     if (qobject_cast<QMacStyle *>(p->style()))
150         layoutSpacing = -1;
151 #endif
152 
153     QGridLayout* layout = new QGridLayout(this);
154     layout->setMargin(3);
155     layout->setSpacing(layoutSpacing);
156 
157     QHashIterator<Button, QPair<QPushButton*, const KeyDescription*> > i(keys);
158     while (i.hasNext()) {
159         i.next();
160         QWidget* widget = i.value().first;
161         const int row = i.value().second->gridRow;
162         const int column = i.value().second->gridColumn;
163         layout->addWidget(widget, row, column);
164     }
165 }
166 
setButtonTooltips()167 void Keypad::setButtonTooltips()
168 {
169     key(KeyAcos)->setToolTip(Keypad::tr("Inverse cosine"));
170     key(KeyAns)->setToolTip(Keypad::tr("The last result"));
171     key(KeyAsin)->setToolTip(Keypad::tr("Inverse sine"));
172     key(KeyAtan)->setToolTip(Keypad::tr("Inverse tangent"));
173     key(KeyClear)->setToolTip(Keypad::tr("Clear expression"));
174     key(KeyCos)->setToolTip(Keypad::tr("Cosine"));
175     key(KeyEE)->setToolTip(Keypad::tr("Scientific notation"));
176     key(KeyExp)->setToolTip(Keypad::tr("Exponential"));
177     key(KeyLn)->setToolTip(Keypad::tr("Natural logarithm"));
178     key(KeySin)->setToolTip(Keypad::tr("Sine"));
179     key(KeySqrt)->setToolTip(Keypad::tr("Square root"));
180     key(KeyTan)->setToolTip(Keypad::tr("Tangent"));
181     key(KeyXEquals)->setToolTip(Keypad::tr("Assign variable x"));
182     key(KeyX)->setToolTip(Keypad::tr("The variable x"));
183 }
184 
sizeButtons()185 void Keypad::sizeButtons()
186 {
187     // The same font in all buttons, so just pick one.
188     QFontMetrics fm = key(Key0)->fontMetrics();
189 
190     int maxWidth = fm.width(key(KeyAcos)->text());
191     const int textHeight = qMax(fm.lineSpacing(), 14);
192 
193     QStyle::ContentsType type = QStyle::CT_ToolButton;
194     QStyleOptionButton option;
195     const QWidget* exampleWidget = key(KeyAcos);
196     option.initFrom(exampleWidget);
197     QSize minSize = QSize(maxWidth, textHeight).expandedTo(QApplication::globalStrut());
198     QSize size = exampleWidget->style()->sizeFromContents(type, &option, minSize, exampleWidget);
199 
200 #ifdef Q_WS_X11
201     // We would like to use the button size as indicated by the widget style, but in some cases,
202     // e.g. KDE's Plastik or Oxygen, another few pixels (typically 5) are added as the content
203     // margin, thereby making the button incredibly wider than necessary. Workaround: take only
204     // the hinted height, adjust the width ourselves (with our own margin).
205     maxWidth += 6;
206     int hh = size.height();
207     int ww = hh * 162 / 100; // Use golden ratio.
208     size = QSize(qMax(ww, maxWidth), hh);
209 #endif
210 
211     // limit the size of the buttons
212     QHashIterator<Button, QPair<QPushButton*, const KeyDescription*> > i(keys);
213     while (i.hasNext()) {
214         i.next();
215         i.value().first->setFixedSize(size);
216     }
217 }
218 
changeEvent(QEvent * event)219 void Keypad::changeEvent(QEvent* event)
220 {
221     if (event->type() == QEvent::LanguageChange)
222         retranslateText();
223     else
224         QWidget::changeEvent(event);
225 }
226