3a6dacc015d62b62722ddfe51089b8ebec5a87f7
[pinentry.git] / qt4 / pinentrydialog.cpp
1 /*
2    pinentrydialog.cpp - A (not yet) secure Qt 4 dialog for PIN entry.
3
4    Copyright (C) 2002, 2008 Klarälvdalens Datakonsult AB (KDAB)
5    Copyright 2007 Ingo Klöcker
6
7    Written by Steffen Hansen <steffen@klaralvdalens-datakonsult.se>.
8
9    This program is free software; you can redistribute it and/or
10    modify it under the terms of the GNU General Public License as
11    published by the Free Software Foundation; either version 2 of the
12    License, or (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful, but
15    WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22 */
23
24 #include "pinentrydialog.h"
25 #include <QGridLayout>
26
27 #include "qsecurelineedit.h"
28
29 #include <QProgressBar>
30 #include <QApplication>
31 #include <QStyle>
32 #include <QPainter>
33 #include <QPushButton>
34 #include <QDialogButtonBox>
35 #include <QKeyEvent>
36 #include <QLabel>
37 #include <QPalette>
38
39 #ifdef Q_WS_WIN
40 #include <windows.h>
41 #endif
42
43 /* I [wk] have no idea for what this code was supposed to do.
44    Foregrounding a window is heavily restricted by modern Windows
45    versions.  This is the reason why gpg-agent employs its
46    AllowSetForegroundWindow callback machinery to ask the supposed to
47    be be calling process to allow a pinentry to go into the
48    foreground.
49
50    [ah] This is a Hack to workaround the fact that Foregrounding
51    a Window is so restricted that it AllowSetForegroundWindow
52    does not always work (e.g. when the ForegroundWindow timeout
53    has not expired.
54    */
55 #ifdef Q_WS_WIN
56 WINBOOL SetForegroundWindowEx( HWND hWnd )
57 {
58    //Attach foreground window thread to our thread
59    const DWORD ForeGroundID = GetWindowThreadProcessId(::GetForegroundWindow(),NULL);
60    const DWORD CurrentID   = GetCurrentThreadId();
61    WINBOOL retval;
62
63    AttachThreadInput ( ForeGroundID, CurrentID, TRUE );
64    //Do our stuff here
65    HWND hLastActivePopupWnd = GetLastActivePopup( hWnd );
66    retval = SetForegroundWindow( hLastActivePopupWnd );
67
68    //Detach the attached thread
69    AttachThreadInput ( ForeGroundID, CurrentID, FALSE );
70    return retval;
71 }// End SetForegroundWindowEx
72 #endif
73
74 void raiseWindow( QWidget* w )
75 {
76     /* Maybe Qt will become agressive enough one day that
77      * this is enough on windows too*/
78     w->raise();
79     w->activateWindow();
80 #ifdef Q_WS_WIN
81     /* In the meantime we do our own attention grabbing */
82     if (!SetForegroundWindow (w->winId()) &&
83             !SetForegroundWindowEx (w->winId()))  {
84         OutputDebugString("SetForegroundWindow (ex) failed");
85         /* Yet another fallback which will not work on some
86          * versions and is not recommended by msdn */
87         if (!ShowWindow (w->winId(), SW_SHOWNORMAL)) {
88             OutputDebugString ("ShowWindow failed.");
89         }
90     }
91 #endif
92 }
93
94 QPixmap icon( QStyle::StandardPixmap which )
95 {
96     QPixmap pm = qApp->windowIcon().pixmap( 48, 48 );
97    
98     if ( which != QStyle::SP_CustomBase ) {
99         const QIcon ic = qApp->style()->standardIcon( which );
100         QPainter painter( &pm );
101         const int emblemSize = 22;
102         painter.drawPixmap( pm.width()-emblemSize, 0,
103                             ic.pixmap( emblemSize, emblemSize ) );
104     }
105
106     return pm;
107 }
108
109 void PinEntryDialog::slotTimeout()
110 {
111     reject();
112 }
113
114 PinEntryDialog::PinEntryDialog( QWidget* parent, const char* name,
115         int timeout, bool modal, bool enable_quality_bar )
116   : QDialog( parent, Qt::WindowStaysOnTopHint ), _grabbed( false )
117 {
118   setWindowFlags( windowFlags() & ~Qt::WindowContextHelpButtonHint );
119
120   if ( modal ) {
121     setWindowModality( Qt::ApplicationModal );
122   }
123
124   _icon = new QLabel( this );
125   _icon->setPixmap( icon() );
126
127   _error = new QLabel( this );
128   _error->setWordWrap(true);
129   QPalette pal;
130   pal.setColor( QPalette::WindowText, Qt::red );
131   _error->setPalette( pal );
132   _error->hide();
133
134   _desc = new QLabel( this );
135   _desc->setWordWrap(true);
136   _desc->hide();
137
138   _prompt = new QLabel( this );
139   _prompt->hide();
140
141   _edit = new QSecureLineEdit( this );
142   _edit->setMaxLength( 256 );
143
144   _prompt->setBuddy( _edit );
145
146   if (enable_quality_bar)
147   {
148     _quality_bar_label = new QLabel( this );
149     _quality_bar_label->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
150     _quality_bar = new QProgressBar( this );
151     _quality_bar->setAlignment( Qt::AlignCenter );
152     _have_quality_bar = true;
153   }
154   else
155     _have_quality_bar = false;
156
157   QDialogButtonBox* const buttons = new QDialogButtonBox( this );
158   buttons->setStandardButtons( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
159   _ok = buttons->button( QDialogButtonBox::Ok );
160   _cancel = buttons->button( QDialogButtonBox::Cancel );
161
162   _ok->setDefault(true);
163
164   if ( style()->styleHint( QStyle::SH_DialogButtonBox_ButtonsHaveIcons ) )
165     {
166       _ok->setIcon( style()->standardIcon( QStyle::SP_DialogOkButton ) );
167       _cancel->setIcon( style()->standardIcon( QStyle::SP_DialogCancelButton ) );
168     }
169
170   if (timeout > 0) {
171       _timer = new QTimer(this);
172       connect(_timer, SIGNAL(timeout()), this, SLOT(slotTimeout()));
173       _timer->start(timeout*1000);
174   }
175   else
176     _timer = NULL;
177
178   connect( buttons, SIGNAL(accepted()), this, SLOT(accept()) );
179   connect( buttons, SIGNAL(rejected()), this, SLOT(reject()) );
180   connect( _edit, SIGNAL( textChanged(secqstring) ),
181            this, SLOT( updateQuality(secqstring) ) );
182
183   _edit->setFocus();
184
185   QGridLayout* const grid = new QGridLayout( this );
186   grid->addWidget( _icon, 0, 0, 5, 1, Qt::AlignTop|Qt::AlignLeft );
187   grid->addWidget( _error, 1, 1, 1, 2 );
188   grid->addWidget( _desc,  2, 1, 1, 2 );
189   //grid->addItem( new QSpacerItem( 0, _edit->height() / 10, QSizePolicy::Minimum, QSizePolicy::Fixed ), 1, 1 );
190   grid->addWidget( _prompt, 3, 1 );
191   grid->addWidget( _edit, 3, 2 );
192   if( enable_quality_bar )
193   {
194     grid->addWidget( _quality_bar_label, 4, 1 );
195     grid->addWidget( _quality_bar, 4, 2 );
196   }
197   grid->addWidget( buttons, 5, 0, 1, 3 );
198
199   grid->setSizeConstraint( QLayout::SetFixedSize );
200 }
201
202 void PinEntryDialog::hideEvent( QHideEvent* ev )
203 {
204   if ( !_pinentry_info || _pinentry_info->grab )
205     _edit->releaseKeyboard();
206   _grabbed = false;
207   QDialog::hideEvent( ev );
208 }
209
210 void PinEntryDialog::showEvent( QShowEvent* event )
211 {
212     QDialog::showEvent( event );
213     raiseWindow( this );
214 }
215
216 void PinEntryDialog::setDescription( const QString& txt )
217 {
218   _desc->setVisible( !txt.isEmpty() );
219   _desc->setText( txt );
220   _desc->setAccessibleDescription ( txt );
221   _icon->setPixmap( icon() );
222   setError( QString::null );
223 }
224
225 QString PinEntryDialog::description() const
226 {
227   return _desc->text();
228 }
229
230 void PinEntryDialog::setError( const QString& txt )
231 {
232   if( !txt.isNull() )_icon->setPixmap( icon( QStyle::SP_MessageBoxCritical ) );
233   _error->setText( txt );
234   _error->setAccessibleDescription ( txt );
235   _error->setVisible( !txt.isEmpty() );
236 }
237
238 QString PinEntryDialog::error() const
239 {
240   return _error->text();
241 }
242
243 void PinEntryDialog::setPin( const secqstring & txt )
244 {
245     _edit->setText( txt );
246 }
247
248 secqstring PinEntryDialog::pin() const
249 {
250     return _edit->text();
251 }
252
253 void PinEntryDialog::setPrompt( const QString& txt )
254 {
255   _prompt->setText( txt );
256   _prompt->setVisible( !txt.isEmpty() );
257 }
258
259 QString PinEntryDialog::prompt() const
260 {
261   return _prompt->text();
262 }
263
264 void PinEntryDialog::setOkText( const QString& txt )
265 {
266   _ok->setText( txt );
267   _ok->setAccessibleDescription ( txt );
268   _ok->setVisible( !txt.isEmpty() );
269 }
270
271 void PinEntryDialog::setCancelText( const QString& txt )
272 {
273   _cancel->setText( txt );
274   _cancel->setAccessibleDescription ( txt );
275   _cancel->setVisible( !txt.isEmpty() );
276 }
277
278 void PinEntryDialog::setQualityBar( const QString& txt )
279 {
280   if (_have_quality_bar) {
281     _quality_bar_label->setText( txt );
282     _quality_bar_label->setAccessibleDescription ( txt );
283   }
284 }
285
286 void PinEntryDialog::setQualityBarTT( const QString& txt )
287 {
288   if (_have_quality_bar)
289     _quality_bar->setToolTip( txt );
290 }
291
292 void PinEntryDialog::updateQuality(const secqstring & txt )
293 {
294   int length;
295   int percent;
296   QPalette pal;
297
298   if (_timer)
299     _timer->stop();
300
301   if (!_have_quality_bar || !_pinentry_info)
302     return;
303   secstring pinStr = toUtf8(txt);
304   const char* pin = pinStr.c_str();
305   // The Qt3 version called ::secmem_free (pin) here, but from other usage of secstring,
306   // it seems like this is not needed anymore - 16 Mar. 2009 13:15 -- Jesper K. Pedersen
307   length = strlen (pin);
308   percent = length? pinentry_inq_quality (_pinentry_info, pin, length) : 0;
309   if (!length)
310     {
311       _quality_bar->reset ();
312     }
313   else
314     {
315       pal = _quality_bar->palette ();
316       if (percent < 0)
317         {
318           pal.setColor (QPalette::Highlight, QColor("red"));
319           percent = -percent;
320         }
321       else
322         {
323           pal.setColor (QPalette::Highlight, QColor("green"));
324         }
325       _quality_bar->setPalette (pal);
326       _quality_bar->setValue (percent);
327     }
328 }
329
330 void PinEntryDialog::setPinentryInfo(pinentry_t peinfo)
331 {
332     _pinentry_info = peinfo;
333 }
334
335 void PinEntryDialog::paintEvent( QPaintEvent* event )
336 {
337   // Grab keyboard. It might be a little weird to do it here, but it works!
338   // Previously this code was in showEvent, but that did not work in Qt4.
339   QDialog::paintEvent( event );
340   if ( !_grabbed && ( !_pinentry_info || _pinentry_info->grab ) ) {
341     _edit->grabKeyboard();
342     _grabbed = true;
343   }
344
345 }
346
347 #include "pinentrydialog.moc"