pinentry-qt4: make the accessibility part optional
[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 #ifndef QT_NO_ACCESSIBILITY
221   _desc->setAccessibleDescription ( txt );
222 #endif
223   _icon->setPixmap( icon() );
224   setError( QString::null );
225 }
226
227 QString PinEntryDialog::description() const
228 {
229   return _desc->text();
230 }
231
232 void PinEntryDialog::setError( const QString& txt )
233 {
234   if( !txt.isNull() )_icon->setPixmap( icon( QStyle::SP_MessageBoxCritical ) );
235   _error->setText( txt );
236 #ifndef QT_NO_ACCESSIBILITY
237   _error->setAccessibleDescription ( txt );
238 #endif
239   _error->setVisible( !txt.isEmpty() );
240 }
241
242 QString PinEntryDialog::error() const
243 {
244   return _error->text();
245 }
246
247 void PinEntryDialog::setPin( const secqstring & txt )
248 {
249     _edit->setText( txt );
250 }
251
252 secqstring PinEntryDialog::pin() const
253 {
254     return _edit->text();
255 }
256
257 void PinEntryDialog::setPrompt( const QString& txt )
258 {
259   _prompt->setText( txt );
260   _prompt->setVisible( !txt.isEmpty() );
261 }
262
263 QString PinEntryDialog::prompt() const
264 {
265   return _prompt->text();
266 }
267
268 void PinEntryDialog::setOkText( const QString& txt )
269 {
270   _ok->setText( txt );
271 #ifndef QT_NO_ACCESSIBILITY
272   _ok->setAccessibleDescription ( txt );
273 #endif
274   _ok->setVisible( !txt.isEmpty() );
275 }
276
277 void PinEntryDialog::setCancelText( const QString& txt )
278 {
279   _cancel->setText( txt );
280 #ifndef QT_NO_ACCESSIBILITY
281   _cancel->setAccessibleDescription ( txt );
282 #endif
283   _cancel->setVisible( !txt.isEmpty() );
284 }
285
286 void PinEntryDialog::setQualityBar( const QString& txt )
287 {
288   if (_have_quality_bar) {
289     _quality_bar_label->setText( txt );
290 #ifndef QT_NO_ACCESSIBILITY
291     _quality_bar_label->setAccessibleDescription ( txt );
292 #endif
293   }
294 }
295
296 void PinEntryDialog::setQualityBarTT( const QString& txt )
297 {
298   if (_have_quality_bar)
299     _quality_bar->setToolTip( txt );
300 }
301
302 void PinEntryDialog::updateQuality(const secqstring & txt )
303 {
304   int length;
305   int percent;
306   QPalette pal;
307
308   if (_timer)
309     _timer->stop();
310
311   if (!_have_quality_bar || !_pinentry_info)
312     return;
313   secstring pinStr = toUtf8(txt);
314   const char* pin = pinStr.c_str();
315   // The Qt3 version called ::secmem_free (pin) here, but from other usage of secstring,
316   // it seems like this is not needed anymore - 16 Mar. 2009 13:15 -- Jesper K. Pedersen
317   length = strlen (pin);
318   percent = length? pinentry_inq_quality (_pinentry_info, pin, length) : 0;
319   if (!length)
320     {
321       _quality_bar->reset ();
322     }
323   else
324     {
325       pal = _quality_bar->palette ();
326       if (percent < 0)
327         {
328           pal.setColor (QPalette::Highlight, QColor("red"));
329           percent = -percent;
330         }
331       else
332         {
333           pal.setColor (QPalette::Highlight, QColor("green"));
334         }
335       _quality_bar->setPalette (pal);
336       _quality_bar->setValue (percent);
337     }
338 }
339
340 void PinEntryDialog::setPinentryInfo(pinentry_t peinfo)
341 {
342     _pinentry_info = peinfo;
343 }
344
345 void PinEntryDialog::paintEvent( QPaintEvent* event )
346 {
347   // Grab keyboard. It might be a little weird to do it here, but it works!
348   // Previously this code was in showEvent, but that did not work in Qt4.
349   QDialog::paintEvent( event );
350   if ( !_grabbed && ( !_pinentry_info || _pinentry_info->grab ) ) {
351     _edit->grabKeyboard();
352     _grabbed = true;
353   }
354
355 }
356
357 #include "pinentrydialog.moc"