VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/widgets/UIAddDiskEncryptionPasswordDialog.cpp@ 104158

Last change on this file since 104158 was 103988, checked in by vboxsync, 9 months ago

FE/Qt. bugref:10624. Adding missing override keywords gcc has found.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 22.4 KB
Line 
1/* $Id: UIAddDiskEncryptionPasswordDialog.cpp 103988 2024-03-21 13:49:47Z vboxsync $ */
2/** @file
3 * VBox Qt GUI - UIAddDiskEncryptionPasswordDialog class implementation.
4 */
5
6/*
7 * Copyright (C) 2006-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28/* Qt includes: */
29#include <QAbstractTableModel>
30#include <QHeaderView>
31#include <QItemEditorFactory>
32#include <QLabel>
33#include <QLineEdit>
34#include <QPushButton>
35#include <QStandardItemEditorCreator>
36#include <QTableView>
37#include <QVBoxLayout>
38
39/* GUI includes: */
40#include "QIDialogButtonBox.h"
41#include "QIStyledItemDelegate.h"
42#include "QIWithRetranslateUI.h"
43#include "UICommon.h"
44#include "UIAddDiskEncryptionPasswordDialog.h"
45#include "UIIconPool.h"
46#include "UIMedium.h"
47#include "UINotificationCenter.h"
48
49/* Other VBox includes: */
50#include <iprt/assert.h>
51
52
53/** UIEncryptionDataTable field indexes. */
54enum UIEncryptionDataTableSection
55{
56 UIEncryptionDataTableSection_Id,
57 UIEncryptionDataTableSection_Password,
58 UIEncryptionDataTableSection_Max
59};
60
61template<class T>
62static QStringList toStringList(const QList<T> &list)
63{
64 QStringList l;
65 foreach(const T &t, list)
66 l << t.toString();
67 return l;
68}
69
70/** QLineEdit extension used as
71 * the embedded password editor for the UIEncryptionDataTable. */
72class UIPasswordEditor : public QLineEdit
73{
74 Q_OBJECT;
75
76 /** Holds the current password of the editor. */
77 Q_PROPERTY(QString password READ password WRITE setPassword USER true);
78
79signals:
80
81 /** Notifies listeners about data should be committed. */
82 void sigCommitData(QWidget *pThis);
83
84 /** Notifies listeners about Enter/Return key triggering. */
85 void sigEnterKeyTriggered();
86
87public:
88
89 /** Constructs password editor passing @a pParent to the base-class. */
90 UIPasswordEditor(QWidget *pParent);
91
92protected:
93
94 /** Handles key-press @a pEvent. */
95 virtual void keyPressEvent(QKeyEvent *pEvent) RT_OVERRIDE;
96
97private slots:
98
99 /** Commits data to the listeners. */
100 void sltCommitData() { emit sigCommitData(this); }
101
102private:
103
104 /** Prepares all. */
105 void prepare();
106
107 /** Property: Returns the current password of the editor. */
108 QString password() const { return QLineEdit::text(); }
109 /** Property: Defines the current @a strPassword of the editor. */
110 void setPassword(const QString &strPassword) { QLineEdit::setText(strPassword); }
111};
112
113
114/** QAbstractTableModel extension used as
115 * the data representation model for the UIEncryptionDataTable. */
116class UIEncryptionDataModel : public QAbstractTableModel
117{
118 Q_OBJECT;
119
120public:
121
122 /** Constructs model passing @a pParent to the base-class.
123 * @param encryptedMedia Brings the lists of medium ids (values) encrypted with passwords with ids (keys). */
124 UIEncryptionDataModel(QObject *pParent, const EncryptedMediumMap &encryptedMedia);
125
126 /** Returns the shallow copy of the encryption password map instance. */
127 EncryptionPasswordMap encryptionPasswords() const { return m_encryptionPasswords; }
128
129 /** Returns the row count, taking optional @a parent instead of root if necessary. */
130 virtual int rowCount(const QModelIndex &parent = QModelIndex()) const RT_OVERRIDE RT_FINAL;
131 /** Returns the column count, taking optional @a parent instead of root if necessary. */
132 virtual int columnCount(const QModelIndex &parent = QModelIndex()) const RT_OVERRIDE RT_FINAL;
133
134 /** Returns the @a index flags. */
135 virtual Qt::ItemFlags flags(const QModelIndex &index) const RT_OVERRIDE RT_FINAL;
136
137 /** Returns the header data for the @a iSection, @a orientation and @a iRole. */
138 virtual QVariant headerData(int iSection, Qt::Orientation orientation, int iRole) const RT_OVERRIDE RT_FINAL;
139
140 /** Returns the @a index data for the @a iRole. */
141 virtual QVariant data(const QModelIndex &index, int iRole = Qt::DisplayRole) const RT_OVERRIDE RT_FINAL;
142 /** Defines the @a index data for the @a iRole as @a value. */
143 virtual bool setData(const QModelIndex &index, const QVariant &value, int iRole = Qt::EditRole) RT_OVERRIDE RT_FINAL;
144
145private:
146
147 /** Prepares all. */
148 void prepare();
149
150 /** Holds the encrypted medium map reference. */
151 const EncryptedMediumMap &m_encryptedMedia;
152
153 /** Holds the encryption password map instance. */
154 EncryptionPasswordMap m_encryptionPasswords;
155};
156
157
158/** QTableView extension used to
159 * allow the UIAddDiskEncryptionPasswordDialog to enter
160 * disk encryption passwords for particular password ids. */
161class UIEncryptionDataTable : public QTableView
162{
163 Q_OBJECT;
164
165signals:
166
167 /** Notifies listeners about editor's Enter/Return key triggering. */
168 void sigEditorEnterKeyTriggered();
169
170public:
171
172 /** Constructs table.
173 * @param encryptedMedia Brings the lists of medium ids (values) encrypted with passwords with ids (keys). */
174 UIEncryptionDataTable(const EncryptedMediumMap &encryptedMedia);
175 /** Destructs table. */
176 virtual ~UIEncryptionDataTable() RT_OVERRIDE;
177
178 /** Returns the shallow copy of the encryption password map
179 * acquired from the UIEncryptionDataModel instance. */
180 EncryptionPasswordMap encryptionPasswords() const;
181
182 /** Initiates the editor for the first index available. */
183 void editFirstIndex();
184
185private:
186
187 /** Prepares all. */
188 void prepare();
189 /** Cleanups all. */
190 void cleanup();
191
192 /** Holds the encrypted medium map reference. */
193 const EncryptedMediumMap &m_encryptedMedia;
194
195 /** Holds the encryption-data model instance. */
196 UIEncryptionDataModel *m_pModelEncryptionData;
197
198 /** Holds the item editor factory instance. */
199 QItemEditorFactory *m_pItemEditorFactory;
200};
201
202
203/*********************************************************************************************************************************
204* Class UIPasswordEditor implementation. *
205*********************************************************************************************************************************/
206
207UIPasswordEditor::UIPasswordEditor(QWidget *pParent)
208 : QLineEdit(pParent)
209{
210 /* Prepare: */
211 prepare();
212}
213
214void UIPasswordEditor::keyPressEvent(QKeyEvent *pEvent)
215{
216 /* Call to base-class: */
217 QLineEdit::keyPressEvent(pEvent);
218
219 /* Broadcast Enter/Return key press: */
220 switch (pEvent->key())
221 {
222 case Qt::Key_Enter:
223 case Qt::Key_Return:
224 emit sigEnterKeyTriggered();
225 pEvent->accept();
226 break;
227 default:
228 break;
229 }
230}
231
232void UIPasswordEditor::prepare()
233{
234 /* Make sure QIStyledDelegate aware of us: */
235 setProperty("has_sigCommitData", true);
236 setProperty("has_sigEnterKeyTriggered", true);
237 /* Set echo mode: */
238 setEchoMode(QLineEdit::Password);
239 /* Listen for the text changes: */
240 connect(this, &UIPasswordEditor::textChanged,
241 this, &UIPasswordEditor::sltCommitData);
242}
243
244
245/*********************************************************************************************************************************
246* Class UIEncryptionDataModel implementation. *
247*********************************************************************************************************************************/
248
249UIEncryptionDataModel::UIEncryptionDataModel(QObject *pParent, const EncryptedMediumMap &encryptedMedia)
250 : QAbstractTableModel(pParent)
251 , m_encryptedMedia(encryptedMedia)
252{
253 /* Prepare: */
254 prepare();
255}
256
257int UIEncryptionDataModel::rowCount(const QModelIndex &parent /* = QModelIndex() */) const
258{
259 Q_UNUSED(parent);
260 return m_encryptionPasswords.size();
261}
262
263int UIEncryptionDataModel::columnCount(const QModelIndex &parent /* = QModelIndex() */) const
264{
265 Q_UNUSED(parent);
266 return UIEncryptionDataTableSection_Max;
267}
268
269Qt::ItemFlags UIEncryptionDataModel::flags(const QModelIndex &index) const
270{
271 /* Check index validness: */
272 if (!index.isValid())
273 return Qt::NoItemFlags;
274 /* Depending on column index: */
275 switch (index.column())
276 {
277 case UIEncryptionDataTableSection_Id: return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
278 case UIEncryptionDataTableSection_Password: return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
279 default: break;
280 }
281 /* No flags by default: */
282 return Qt::NoItemFlags;
283}
284
285QVariant UIEncryptionDataModel::headerData(int iSection, Qt::Orientation orientation, int iRole) const
286{
287 /* Check argument validness: */
288 if (iRole != Qt::DisplayRole || orientation != Qt::Horizontal)
289 return QVariant();
290 /* Depending on column index: */
291 switch (iSection)
292 {
293 case UIEncryptionDataTableSection_Id: return UIAddDiskEncryptionPasswordDialog::tr("ID", "password table field");
294 case UIEncryptionDataTableSection_Password: return UIAddDiskEncryptionPasswordDialog::tr("Password", "password table field");
295 default: break;
296 }
297 /* Null value by default: */
298 return QVariant();
299}
300
301QVariant UIEncryptionDataModel::data(const QModelIndex &index, int iRole /* = Qt::DisplayRole */) const
302{
303 /* Check argument validness: */
304 if (!index.isValid())
305 return QVariant();
306 /* Depending on role: */
307 switch (iRole)
308 {
309 case Qt::DisplayRole:
310 {
311 /* Depending on column index: */
312 switch (index.column())
313 {
314 case UIEncryptionDataTableSection_Id:
315 return m_encryptionPasswords.keys().at(index.row());
316 case UIEncryptionDataTableSection_Password:
317 return QString().fill('*', m_encryptionPasswords.value(m_encryptionPasswords.keys().at(index.row())).size());
318 default:
319 return QVariant();
320 }
321 break;
322 }
323 case Qt::EditRole:
324 {
325 /* Depending on column index: */
326 switch (index.column())
327 {
328 case UIEncryptionDataTableSection_Password:
329 return m_encryptionPasswords.value(m_encryptionPasswords.keys().at(index.row()));
330 default:
331 return QVariant();
332 }
333 break;
334 }
335 case Qt::ToolTipRole:
336 {
337 /* We are generating tool-tip here and not in retranslateUi() because of the tricky plural form handling,
338 * but be quiet, it's safe enough because the tool-tip being re-acquired every time on mouse-hovering. */
339 const QList<QUuid> encryptedMedia = m_encryptedMedia.values(m_encryptionPasswords.keys().at(index.row()));
340 return UIAddDiskEncryptionPasswordDialog::tr("<nobr>Used by the following %n hard disk(s):</nobr><br>%1",
341 "This text is never used with n == 0. "
342 "Feel free to drop the %n where possible, "
343 "we only included it because of problems with Qt Linguist "
344 "(but the user can see how many hard drives are in the tool-tip "
345 "and doesn't need to be told).",
346 encryptedMedia.size())
347 .arg(toStringList(encryptedMedia).join("<br>"));
348 }
349 default:
350 break;
351 }
352 /* Null value by default: */
353 return QVariant();
354}
355
356bool UIEncryptionDataModel::setData(const QModelIndex &index, const QVariant &value, int iRole /* = Qt::EditRole */)
357{
358 /* Check argument validness: */
359 if (!index.isValid() || iRole != Qt::EditRole)
360 return false;
361 /* Depending on column index: */
362 switch (index.column())
363 {
364 case UIEncryptionDataTableSection_Password:
365 {
366 /* Update password: */
367 const int iRow = index.row();
368 const QString strPassword = value.toString();
369 const QString strKey = m_encryptionPasswords.keys().at(iRow);
370 m_encryptionPasswords[strKey] = strPassword;
371 break;
372 }
373 default:
374 break;
375 }
376 /* Nothing to set by default: */
377 return false;
378}
379
380void UIEncryptionDataModel::prepare()
381{
382 /* Populate the map of passwords and statuses: */
383 foreach (const QString &strPasswordId, m_encryptedMedia.keys())
384 m_encryptionPasswords.insert(strPasswordId, QString());
385}
386
387
388/*********************************************************************************************************************************
389* Class UIEncryptionDataTable implementation. *
390*********************************************************************************************************************************/
391
392UIEncryptionDataTable::UIEncryptionDataTable(const EncryptedMediumMap &encryptedMedia)
393 : m_encryptedMedia(encryptedMedia)
394 , m_pModelEncryptionData(0)
395 , m_pItemEditorFactory(0)
396{
397 prepare();
398}
399
400UIEncryptionDataTable::~UIEncryptionDataTable()
401{
402 cleanup();
403}
404
405EncryptionPasswordMap UIEncryptionDataTable::encryptionPasswords() const
406{
407 AssertPtrReturn(m_pModelEncryptionData, EncryptionPasswordMap());
408 return m_pModelEncryptionData->encryptionPasswords();
409}
410
411void UIEncryptionDataTable::editFirstIndex()
412{
413 AssertPtrReturnVoid(m_pModelEncryptionData);
414 /* Compose the password field index of the first available table record: */
415 const QModelIndex index = m_pModelEncryptionData->index(0, UIEncryptionDataTableSection_Password);
416 /* Navigate table to the corresponding index: */
417 setCurrentIndex(index);
418 /* Compose the fake mouse-event which will trigger the embedded editor: */
419 QMouseEvent event(QEvent::MouseButtonPress, QPoint(), Qt::LeftButton, Qt::NoButton, Qt::NoModifier);
420 /* Initiate the embedded editor for the corresponding index: */
421 edit(index, QAbstractItemView::SelectedClicked, &event);
422}
423
424void UIEncryptionDataTable::prepare()
425{
426 /* Create encryption-data model: */
427 m_pModelEncryptionData = new UIEncryptionDataModel(this, m_encryptedMedia);
428 if (m_pModelEncryptionData)
429 {
430 /* Assign configured model to table: */
431 setModel(m_pModelEncryptionData);
432 }
433
434 /* Create item delegate: */
435 QIStyledItemDelegate *pStyledItemDelegate = new QIStyledItemDelegate(this);
436 if (pStyledItemDelegate)
437 {
438 /* Create new item editor factory: */
439 m_pItemEditorFactory = new QItemEditorFactory;
440 if (m_pItemEditorFactory)
441 {
442 /* Register UIPasswordEditor as the QString editor: */
443 QStandardItemEditorCreator<UIPasswordEditor> *pQStringItemEditorCreator = new QStandardItemEditorCreator<UIPasswordEditor>();
444 if (pQStringItemEditorCreator)
445 m_pItemEditorFactory->registerEditor(QMetaType::QString, pQStringItemEditorCreator);
446
447 /* Assign configured item editor factory to table delegate: */
448 pStyledItemDelegate->setItemEditorFactory(m_pItemEditorFactory);
449 }
450
451 /* Assign configured item delegate to table: */
452 delete itemDelegate();
453 setItemDelegate(pStyledItemDelegate);
454
455 /* Configure item delegate: */
456 pStyledItemDelegate->setWatchForEditorDataCommits(true);
457 pStyledItemDelegate->setWatchForEditorEnterKeyTriggering(true);
458 connect(pStyledItemDelegate, &QIStyledItemDelegate::sigEditorEnterKeyTriggered,
459 this, &UIEncryptionDataTable::sigEditorEnterKeyTriggered);
460 }
461
462 /* Configure table: */
463 setTabKeyNavigation(false);
464 setContextMenuPolicy(Qt::CustomContextMenu);
465 setSelectionBehavior(QAbstractItemView::SelectRows);
466 setSelectionMode(QAbstractItemView::SingleSelection);
467 setEditTriggers(QAbstractItemView::CurrentChanged | QAbstractItemView::SelectedClicked);
468
469 /* Configure headers: */
470 verticalHeader()->hide();
471 verticalHeader()->setDefaultSectionSize((int)(verticalHeader()->minimumSectionSize() * 1.33));
472 horizontalHeader()->setStretchLastSection(false);
473 horizontalHeader()->setSectionResizeMode(UIEncryptionDataTableSection_Id, QHeaderView::Interactive);
474 horizontalHeader()->setSectionResizeMode(UIEncryptionDataTableSection_Password, QHeaderView::Stretch);
475}
476
477void UIEncryptionDataTable::cleanup()
478{
479 /* Cleanup item editor factory: */
480 delete m_pItemEditorFactory;
481 m_pItemEditorFactory = 0;
482}
483
484
485/*********************************************************************************************************************************
486* Class UIAddDiskEncryptionPasswordDialog implementation. *
487*********************************************************************************************************************************/
488
489UIAddDiskEncryptionPasswordDialog::UIAddDiskEncryptionPasswordDialog(QWidget *pParent,
490 const QString &strMachineName,
491 const EncryptedMediumMap &encryptedMedia)
492 : QIWithRetranslateUI<QDialog>(pParent)
493 , m_strMachineName(strMachineName)
494 , m_encryptedMedia(encryptedMedia)
495 , m_pLabelDescription(0)
496 , m_pTableEncryptionData(0)
497 , m_pButtonBox(0)
498{
499 /* Prepare: */
500 prepare();
501 /* Apply language settings: */
502 retranslateUi();
503}
504
505EncryptionPasswordMap UIAddDiskEncryptionPasswordDialog::encryptionPasswords() const
506{
507 AssertPtrReturn(m_pTableEncryptionData, EncryptionPasswordMap());
508 return m_pTableEncryptionData->encryptionPasswords();
509}
510
511void UIAddDiskEncryptionPasswordDialog::retranslateUi()
512{
513 /* Translate the dialog title: */
514 setWindowTitle(tr("%1 - Disk Encryption").arg(m_strMachineName));
515
516 /* Translate the description label: */
517 AssertPtrReturnVoid(m_pLabelDescription);
518 m_pLabelDescription->setText(tr("This virtual machine is password protected. "
519 "Please enter the %n encryption password(s) below.",
520 "This text is never used with n == 0. "
521 "Feel free to drop the %n where possible, "
522 "we only included it because of problems with Qt Linguist "
523 "(but the user can see how many passwords are in the list "
524 "and doesn't need to be told).",
525 m_encryptedMedia.uniqueKeys().size()));
526}
527
528void UIAddDiskEncryptionPasswordDialog::accept()
529{
530 /* Validate passwords status: */
531 foreach (const QString &strPasswordId, m_encryptedMedia.uniqueKeys())
532 {
533 const QUuid uMediumId = m_encryptedMedia.values(strPasswordId).first();
534 const QString strPassword = m_pTableEncryptionData->encryptionPasswords().value(strPasswordId);
535 if (!isPasswordValid(uMediumId, strPassword))
536 {
537 UINotificationMessage::warnAboutInvalidEncryptionPassword(strPasswordId);
538 AssertPtrReturnVoid(m_pTableEncryptionData);
539 m_pTableEncryptionData->setFocus();
540 m_pTableEncryptionData->editFirstIndex();
541 return;
542 }
543 }
544 /* Call to base-class: */
545 QIWithRetranslateUI<QDialog>::accept();
546}
547
548void UIAddDiskEncryptionPasswordDialog::prepare()
549{
550 /* Configure self: */
551 setWindowModality(Qt::WindowModal);
552
553 /* Create main-layout: */
554 QVBoxLayout *pMainLayout = new QVBoxLayout(this);
555 if (pMainLayout)
556 {
557 /* Create input-layout: */
558 QVBoxLayout *pInputLayout = new QVBoxLayout;
559 if (pInputLayout)
560 {
561 /* Create description label: */
562 m_pLabelDescription = new QLabel;
563 if (m_pLabelDescription)
564 {
565 /* Add label into layout: */
566 pInputLayout->addWidget(m_pLabelDescription);
567 }
568
569 /* Create encryption-data table: */
570 m_pTableEncryptionData = new UIEncryptionDataTable(m_encryptedMedia);
571 if (m_pTableEncryptionData)
572 {
573 /* Configure encryption-data table: */
574 connect(m_pTableEncryptionData, &UIEncryptionDataTable::sigEditorEnterKeyTriggered,
575 this, &UIAddDiskEncryptionPasswordDialog::sltEditorEnterKeyTriggered);
576 m_pTableEncryptionData->setFocus();
577 m_pTableEncryptionData->editFirstIndex();
578 /* Add label into layout: */
579 pInputLayout->addWidget(m_pTableEncryptionData);
580 }
581
582 /* Add layout into parent: */
583 pMainLayout->addLayout(pInputLayout);
584 }
585
586 /* Create button-box: */
587 m_pButtonBox = new QIDialogButtonBox;
588 if (m_pButtonBox)
589 {
590 /* Configure button-box: */
591 m_pButtonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
592 connect(m_pButtonBox, &QIDialogButtonBox::accepted, this, &UIAddDiskEncryptionPasswordDialog::accept);
593 connect(m_pButtonBox, &QIDialogButtonBox::rejected, this, &UIAddDiskEncryptionPasswordDialog::reject);
594
595 /* Add button-box into layout: */
596 pMainLayout->addWidget(m_pButtonBox);
597 }
598 }
599}
600
601/* static */
602bool UIAddDiskEncryptionPasswordDialog::isPasswordValid(const QUuid &uMediumId, const QString strPassword)
603{
604 /* Look for the medium with passed ID: */
605 const UIMedium uimedium = uiCommon().medium(uMediumId);
606 if (!uimedium.isNull())
607 {
608 /* Check wrapped medium for validity: */
609 const CMedium medium = uimedium.medium();
610 if (!medium.isNull())
611 {
612 /* Check whether the password is suitable for that medium: */
613 medium.CheckEncryptionPassword(strPassword);
614 return medium.isOk();
615 }
616 }
617 /* False by default: */
618 return false;
619}
620
621
622#include "UIAddDiskEncryptionPasswordDialog.moc"
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette