VirtualBox

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

Last change on this file was 104358, checked in by vboxsync, 4 weeks ago

FE/Qt. bugref:10622. More refactoring around the retranslation functionality.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use