1
/* $Id: UIMachineSettingsPortForwardingDlg.cpp 33882 2010-11-09 09:32:27Z vboxsync $ */
4
* VBox frontends: Qt4 GUI ("VirtualBox"):
5
* UIMachineSettingsPortForwardingDlg class implementation
9
* Copyright (C) 2010 Oracle Corporation
11
* This file is part of VirtualBox Open Source Edition (OSE), as
12
* available from http://www.virtualbox.org. This file is free software;
13
* you can redistribute it and/or modify it under the terms of the GNU
14
* General Public License (GPL) as published by the Free Software
15
* Foundation, in version 2 as it comes in the "COPYING" file of the
16
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
22
#include <QHeaderView>
23
#include <QPushButton>
24
#include <QStyledItemDelegate>
25
#include <QItemEditorFactory>
32
#include "UIMachineSettingsPortForwardingDlg.h"
33
#include "VBoxGlobal.h"
34
#include "VBoxProblemReporter.h"
35
#include "UIToolBar.h"
36
#include "QITableView.h"
37
#include "QIDialogButtonBox.h"
38
#include "UIIconPool.h"
39
#include <iprt/cidr.h>
43
class IPValidator : public QValidator
49
IPValidator(QObject *pParent) : QValidator(pParent) {}
52
QValidator::State validate(QString &strInput, int & /* iPos */) const
54
QString strStringToValidate(strInput);
55
strStringToValidate.remove(' ');
56
QString strDot("\\.");
57
QString strDigits("(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]?|0)");
58
QRegExp intRegExp(QString("^(%1?(%2(%1?(%2(%1?(%2%1?)?)?)?)?)?)?$").arg(strDigits).arg(strDot));
60
if (strStringToValidate == "..." || RTCidrStrToIPv4(strStringToValidate.toLatin1().constData(), &uNetwork, &uMask) == VINF_SUCCESS)
61
return QValidator::Acceptable;
62
else if (intRegExp.indexIn(strStringToValidate) != -1)
63
return QValidator::Intermediate;
65
return QValidator::Invalid;
70
class NameEditor : public QLineEdit
73
Q_PROPERTY(NameData name READ name WRITE setName USER true);
77
NameEditor(QWidget *pParent = 0) : QLineEdit(pParent)
80
setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
81
setValidator(new QRegExpValidator(QRegExp("[^,]*"), this));
86
void setName(NameData name)
97
/* Protocol editor: */
98
class ProtocolEditor : public QComboBox
101
Q_PROPERTY(KNATProtocol protocol READ protocol WRITE setProtocol USER true);
105
ProtocolEditor(QWidget *pParent = 0) : QComboBox(pParent)
107
addItem(vboxGlobal().toString(KNATProtocol_UDP), QVariant::fromValue(KNATProtocol_UDP));
108
addItem(vboxGlobal().toString(KNATProtocol_TCP), QVariant::fromValue(KNATProtocol_TCP));
113
void setProtocol(KNATProtocol p)
115
for (int i = 0; i < count(); ++i)
117
if (itemData(i).value<KNATProtocol>() == p)
125
KNATProtocol protocol() const
127
return itemData(currentIndex()).value<KNATProtocol>();
132
class IPEditor : public QLineEdit
135
Q_PROPERTY(IpData ip READ ip WRITE setIp USER true);
139
IPEditor(QWidget *pParent = 0) : QLineEdit(pParent)
142
setAlignment(Qt::AlignCenter);
143
setValidator(new IPValidator(this));
144
setInputMask("000.000.000.000");
149
void setIp(IpData ip)
156
return text() == "..." ? QString() : text();
161
class PortEditor : public QSpinBox
164
Q_PROPERTY(PortData port READ port WRITE setPort USER true);
168
PortEditor(QWidget *pParent = 0) : QSpinBox(pParent)
171
setRange(0, (1 << (8 * sizeof(ushort))) - 1);
176
void setPort(PortData port)
178
setValue(port.value());
181
PortData port() const
187
/* Port forwarding data model: */
188
class UIPortForwardingModel : public QAbstractTableModel
195
enum UIPortForwardingDataType
197
UIPortForwardingDataType_Name,
198
UIPortForwardingDataType_Protocol,
199
UIPortForwardingDataType_HostIp,
200
UIPortForwardingDataType_HostPort,
201
UIPortForwardingDataType_GuestIp,
202
UIPortForwardingDataType_GuestPort,
203
UIPortForwardingDataType_Max
206
/* Port forwarding model constructor: */
207
UIPortForwardingModel(QObject *pParent = 0, const UIPortForwardingDataList &rules = UIPortForwardingDataList())
208
: QAbstractTableModel(pParent), m_dataList(rules) {}
209
/* Port forwarding model destructor: */
210
~UIPortForwardingModel() {}
212
/* The list of chosen rules: */
213
const UIPortForwardingDataList& rules() const
218
/* Flags for model indexes: */
219
Qt::ItemFlags flags(const QModelIndex &index) const
221
/* Check index validness: */
222
if (!index.isValid())
223
return Qt::NoItemFlags;
224
/* All columns have similar flags: */
225
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
228
/* Row count getter: */
229
int rowCount(const QModelIndex & parent = QModelIndex()) const
232
return m_dataList.size();
235
/* Column count getter: */
236
int columnCount(const QModelIndex & parent = QModelIndex()) const
239
return UIPortForwardingDataType_Max;
242
/* Get header data: */
243
QVariant headerData(int iSection, Qt::Orientation orientation, int iRole) const
245
/* Display role for horizontal header: */
246
if (iRole == Qt::DisplayRole && orientation == Qt::Horizontal)
248
/* Switch for different columns: */
251
case UIPortForwardingDataType_Name: return tr("Name");
252
case UIPortForwardingDataType_Protocol: return tr("Protocol");
253
case UIPortForwardingDataType_HostIp: return tr("Host IP");
254
case UIPortForwardingDataType_HostPort: return tr("Host Port");
255
case UIPortForwardingDataType_GuestIp: return tr("Guest IP");
256
case UIPortForwardingDataType_GuestPort: return tr("Guest Port");
260
/* Return wrong value: */
264
/* Get index data: */
265
QVariant data(const QModelIndex &index, int iRole) const
267
/* Check index validness: */
268
if (!index.isValid())
270
/* Switch for different roles: */
274
case Qt::DisplayRole:
276
/* Switch for different columns: */
277
switch (index.column())
279
case UIPortForwardingDataType_Name: return m_dataList[index.row()].name;
280
case UIPortForwardingDataType_Protocol: return vboxGlobal().toString(m_dataList[index.row()].protocol);
281
case UIPortForwardingDataType_HostIp: return m_dataList[index.row()].hostIp;
282
case UIPortForwardingDataType_HostPort: return m_dataList[index.row()].hostPort.value();
283
case UIPortForwardingDataType_GuestIp: return m_dataList[index.row()].guestIp;
284
case UIPortForwardingDataType_GuestPort: return m_dataList[index.row()].guestPort.value();
285
default: return QVariant();
291
/* Switch for different columns: */
292
switch (index.column())
294
case UIPortForwardingDataType_Name: return QVariant::fromValue(m_dataList[index.row()].name);
295
case UIPortForwardingDataType_Protocol: return QVariant::fromValue(m_dataList[index.row()].protocol);
296
case UIPortForwardingDataType_HostIp: return QVariant::fromValue(m_dataList[index.row()].hostIp);
297
case UIPortForwardingDataType_HostPort: return QVariant::fromValue(m_dataList[index.row()].hostPort);
298
case UIPortForwardingDataType_GuestIp: return QVariant::fromValue(m_dataList[index.row()].guestIp);
299
case UIPortForwardingDataType_GuestPort: return QVariant::fromValue(m_dataList[index.row()].guestPort);
300
default: return QVariant();
303
/* Alignment role: */
304
case Qt::TextAlignmentRole:
306
/* Switch for different columns: */
307
switch (index.column())
309
case UIPortForwardingDataType_Name:
310
case UIPortForwardingDataType_Protocol:
311
case UIPortForwardingDataType_HostPort:
312
case UIPortForwardingDataType_GuestPort:
313
return (int)(Qt::AlignLeft | Qt::AlignVCenter);
314
case UIPortForwardingDataType_HostIp:
315
case UIPortForwardingDataType_GuestIp:
316
return Qt::AlignCenter;
317
default: return QVariant();
320
case Qt::SizeHintRole:
322
/* Switch for different columns: */
323
switch (index.column())
325
case UIPortForwardingDataType_HostIp:
326
case UIPortForwardingDataType_GuestIp:
327
return QSize(QApplication::fontMetrics().width(" 888.888.888.888 "), QApplication::fontMetrics().height());
328
default: return QVariant();
333
/* Return wrong value: */
337
/* Set index data: */
338
bool setData(const QModelIndex &index, const QVariant &value, int iRole = Qt::EditRole)
340
/* Check index validness: */
341
if (!index.isValid() || iRole != Qt::EditRole)
343
/* Switch for different columns: */
344
switch (index.column())
346
case UIPortForwardingDataType_Name:
347
m_dataList[index.row()].name = value.value<NameData>();
348
emit dataChanged(index, index);
350
case UIPortForwardingDataType_Protocol:
351
m_dataList[index.row()].protocol = value.value<KNATProtocol>();
352
emit dataChanged(index, index);
354
case UIPortForwardingDataType_HostIp:
355
m_dataList[index.row()].hostIp = value.value<IpData>();
356
emit dataChanged(index, index);
358
case UIPortForwardingDataType_HostPort:
359
m_dataList[index.row()].hostPort = value.value<PortData>();
360
emit dataChanged(index, index);
362
case UIPortForwardingDataType_GuestIp:
363
m_dataList[index.row()].guestIp = value.value<IpData>();
364
emit dataChanged(index, index);
366
case UIPortForwardingDataType_GuestPort:
367
m_dataList[index.row()].guestPort = value.value<PortData>();
368
emit dataChanged(index, index);
370
default: return false;
372
/* Return false value: */
377
void addRule(const QModelIndex &index)
379
beginInsertRows(QModelIndex(), m_dataList.size(), m_dataList.size());
380
/* Search for existing "Rule [NUMBER]" record: */
382
QString strTemplate("Rule %1");
383
QRegExp regExp(strTemplate.arg("(\\d+)"));
384
for (int i = 0; i < m_dataList.size(); ++i)
385
if (regExp.indexIn(m_dataList[i].name) > -1)
386
uMaxIndex = regExp.cap(1).toUInt() > uMaxIndex ? regExp.cap(1).toUInt() : uMaxIndex;
387
/* If index is valid => copy data: */
389
m_dataList << UIPortForwardingData(strTemplate.arg(++uMaxIndex), m_dataList[index.row()].protocol,
390
m_dataList[index.row()].hostIp, m_dataList[index.row()].hostPort,
391
m_dataList[index.row()].guestIp, m_dataList[index.row()].guestPort);
392
/* If index is NOT valid => use default values: */
394
m_dataList << UIPortForwardingData(strTemplate.arg(++uMaxIndex), KNATProtocol_TCP,
395
QString(""), 0, QString(""), 0);
400
void delRule(const QModelIndex &index)
402
if (!index.isValid())
404
beginRemoveRows(QModelIndex(), index.row(), index.row());
405
m_dataList.removeAt(index.row());
411
/* Data container: */
412
UIPortForwardingDataList m_dataList;
415
/* Port forwarding dialog constructor: */
416
UIMachineSettingsPortForwardingDlg::UIMachineSettingsPortForwardingDlg(QWidget *pParent,
417
const UIPortForwardingDataList &rules)
418
: QIWithRetranslateUI<QIDialog>(pParent)
419
, fIsTableDataChanged(false)
429
setWindowFlags(Qt::Sheet);
430
#endif /* Q_WS_MAC */
432
/* Set dialog icon: */
433
setWindowIcon(UIIconPool::iconSetFull(QSize(32, 32), QSize(16, 16), ":/nw_32px.png", ":/nw_16px.png"));
436
m_pTableView = new QITableView(this);
437
m_pTableView->setTabKeyNavigation(false);
438
m_pTableView->verticalHeader()->hide();
439
m_pTableView->verticalHeader()->setDefaultSectionSize((int)m_pTableView->verticalHeader()->minimumSectionSize() * 1.33);
440
m_pTableView->setSelectionMode(QAbstractItemView::SingleSelection);
441
m_pTableView->setContextMenuPolicy(Qt::CustomContextMenu);
442
m_pTableView->installEventFilter(this);
443
connect(m_pTableView, SIGNAL(sigCurrentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(sltCurrentChanged()));
444
connect(m_pTableView, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(sltShowTableContexMenu(const QPoint &)));
446
/* Create actions: */
447
m_pAddAction = new QAction(this);
448
m_pCopyAction = new QAction(this);
449
m_pDelAction = new QAction(this);
450
m_pAddAction->setShortcut(QKeySequence("Ins"));
451
m_pDelAction->setShortcut(QKeySequence("Del"));
452
m_pAddAction->setIcon(UIIconPool::iconSet(":/controller_add_16px.png", ":/controller_add_disabled_16px.png"));
453
m_pCopyAction->setIcon(UIIconPool::iconSet(":/controller_add_16px.png", ":/controller_add_disabled_16px.png"));
454
m_pDelAction->setIcon(UIIconPool::iconSet(":/controller_remove_16px.png", ":/controller_remove_disabled_16px.png"));
455
connect(m_pAddAction, SIGNAL(triggered(bool)), this, SLOT(sltAddRule()));
456
connect(m_pCopyAction, SIGNAL(triggered(bool)), this, SLOT(sltCopyRule()));
457
connect(m_pDelAction, SIGNAL(triggered(bool)), this, SLOT(sltDelRule()));
459
/* Create toolbar: */
460
m_pToolBar = new UIToolBar(this);
461
m_pToolBar->setIconSize(QSize(16, 16));
462
m_pToolBar->setOrientation(Qt::Vertical);
463
m_pToolBar->addAction(m_pAddAction);
464
m_pToolBar->addAction(m_pDelAction);
466
/* Create table & toolbar layout: */
467
QHBoxLayout *pTableAndToolbarLayout = new QHBoxLayout;
468
pTableAndToolbarLayout->setSpacing(1);
469
pTableAndToolbarLayout->addWidget(m_pTableView);
470
pTableAndToolbarLayout->addWidget(m_pToolBar);
472
/* Create buttonbox: */
473
m_pButtonBox = new QIDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
474
connect(m_pButtonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(accept()));
475
connect(m_pButtonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject()));
477
/* Create main layout: */
478
QVBoxLayout *pMainLayout = new QVBoxLayout;
479
this->setLayout(pMainLayout);
480
pMainLayout->addLayout(pTableAndToolbarLayout);
481
pMainLayout->addWidget(m_pButtonBox);
484
m_pModel = new UIPortForwardingModel(this, rules);
485
connect(m_pModel, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(sltTableDataChanged()));
486
connect(m_pModel, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(sltTableDataChanged()));
487
connect(m_pModel, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(sltTableDataChanged()));
488
m_pTableView->setModel(m_pModel);
490
/* Register delegates editors: */
491
if (QAbstractItemDelegate *pAbstractItemDelegate = m_pTableView->itemDelegate())
493
if (QStyledItemDelegate *pStyledItemDelegate = qobject_cast<QStyledItemDelegate*>(pAbstractItemDelegate))
495
/* Create new item editor factory: */
496
QItemEditorFactory *pNewItemEditorFactory = new QItemEditorFactory;
498
/* Register name type: */
499
int iNameId = qRegisterMetaType<NameData>();
500
/* Register name editor: */
501
QStandardItemEditorCreator<NameEditor> *pNameEditorItemCreator = new QStandardItemEditorCreator<NameEditor>();
502
/* Link name type & editor: */
503
pNewItemEditorFactory->registerEditor((QVariant::Type)iNameId, pNameEditorItemCreator);
505
/* Register protocol type: */
506
int iProtocolId = qRegisterMetaType<KNATProtocol>();
507
/* Register protocol editor: */
508
QStandardItemEditorCreator<ProtocolEditor> *pProtocolEditorItemCreator = new QStandardItemEditorCreator<ProtocolEditor>();
509
/* Link protocol type & editor: */
510
pNewItemEditorFactory->registerEditor((QVariant::Type)iProtocolId, pProtocolEditorItemCreator);
512
/* Register ip type: */
513
int iIpId = qRegisterMetaType<IpData>();
514
/* Register ip editor: */
515
QStandardItemEditorCreator<IPEditor> *pIpEditorItemCreator = new QStandardItemEditorCreator<IPEditor>();
516
/* Link ip type & editor: */
517
pNewItemEditorFactory->registerEditor((QVariant::Type)iIpId, pIpEditorItemCreator);
519
/* Register port type: */
520
int iPortId = qRegisterMetaType<PortData>();
521
/* Register port editor: */
522
QStandardItemEditorCreator<PortEditor> *pPortEditorItemCreator = new QStandardItemEditorCreator<PortEditor>();
523
/* Link port type & editor: */
524
pNewItemEditorFactory->registerEditor((QVariant::Type)iPortId, pPortEditorItemCreator);
526
/* Set newly created item editor factory for table delegate: */
527
pStyledItemDelegate->setItemEditorFactory(pNewItemEditorFactory);
531
/* Retranslate dialog: */
535
setMinimumSize(600, 300);
538
/* Port forwarding dialog destructor: */
539
UIMachineSettingsPortForwardingDlg::~UIMachineSettingsPortForwardingDlg()
543
const UIPortForwardingDataList& UIMachineSettingsPortForwardingDlg::rules() const
545
return m_pModel->rules();
549
void UIMachineSettingsPortForwardingDlg::sltAddRule()
551
m_pModel->addRule(QModelIndex());
552
m_pTableView->setFocus();
553
m_pTableView->setCurrentIndex(m_pModel->index(m_pModel->rowCount() - 1, 0));
558
/* Copy rule slot: */
559
void UIMachineSettingsPortForwardingDlg::sltCopyRule()
561
m_pModel->addRule(m_pTableView->currentIndex());
562
m_pTableView->setFocus();
563
m_pTableView->setCurrentIndex(m_pModel->index(m_pModel->rowCount() - 1, 0));
569
void UIMachineSettingsPortForwardingDlg::sltDelRule()
571
m_pModel->delRule(m_pTableView->currentIndex());
572
m_pTableView->setFocus();
577
/* Table data change handler: */
578
void UIMachineSettingsPortForwardingDlg::sltTableDataChanged()
580
fIsTableDataChanged = true;
583
/* Table index-change handler: */
584
void UIMachineSettingsPortForwardingDlg::sltCurrentChanged()
586
bool fTableFocused = m_pTableView->hasFocus();
587
bool fTableChildrenFocused = m_pTableView->findChildren<QWidget*>().contains(QApplication::focusWidget());
588
bool fTableOrChildrenFocused = fTableFocused || fTableChildrenFocused;
589
m_pCopyAction->setEnabled(m_pTableView->currentIndex().isValid() && fTableOrChildrenFocused);
590
m_pDelAction->setEnabled(m_pTableView->currentIndex().isValid() && fTableOrChildrenFocused);
593
/* Table context-menu handler: */
594
void UIMachineSettingsPortForwardingDlg::sltShowTableContexMenu(const QPoint &pos)
596
/* Prepare context menu: */
597
QMenu menu(m_pTableView);
598
/* If some index is currently selected: */
599
if (m_pTableView->indexAt(pos).isValid())
601
menu.addAction(m_pCopyAction);
602
menu.addAction(m_pDelAction);
604
/* If no valid index selected: */
607
menu.addAction(m_pAddAction);
609
menu.exec(m_pTableView->viewport()->mapToGlobal(pos));
612
/* Adjusts table column's sizes: */
613
void UIMachineSettingsPortForwardingDlg::sltAdjustTable()
615
m_pTableView->horizontalHeader()->setStretchLastSection(false);
616
/* If table is NOT empty: */
617
if (m_pModel->rowCount())
619
/* Resize table to contents size-hint and emit a spare place for first column: */
620
m_pTableView->resizeColumnsToContents();
621
uint uFullWidth = m_pTableView->viewport()->width();
622
for (uint u = 1; u < UIPortForwardingModel::UIPortForwardingDataType_Max; ++u)
623
uFullWidth -= m_pTableView->horizontalHeader()->sectionSize(u);
624
m_pTableView->horizontalHeader()->resizeSection(UIPortForwardingModel::UIPortForwardingDataType_Name, uFullWidth);
626
/* If table is empty: */
629
/* Resize table columns to be equal in size: */
630
uint uFullWidth = m_pTableView->viewport()->width();
631
for (uint u = 0; u < UIPortForwardingModel::UIPortForwardingDataType_Max; ++u)
632
m_pTableView->horizontalHeader()->resizeSection(u, uFullWidth / UIPortForwardingModel::UIPortForwardingDataType_Max);
634
m_pTableView->horizontalHeader()->setStretchLastSection(true);
637
void UIMachineSettingsPortForwardingDlg::accept()
639
/* Validate table: */
640
for (int i = 0; i < m_pModel->rowCount(); ++i)
642
if (m_pModel->data(m_pModel->index(i, UIPortForwardingModel::UIPortForwardingDataType_HostPort), Qt::EditRole).value<PortData>().value() == 0 ||
643
m_pModel->data(m_pModel->index(i, UIPortForwardingModel::UIPortForwardingDataType_GuestPort), Qt::EditRole).value<PortData>().value() == 0)
645
vboxProblem().warnAboutIncorrectPort(this);
649
/* Base class accept() slot: */
650
QIWithRetranslateUI<QIDialog>::accept();
653
void UIMachineSettingsPortForwardingDlg::reject()
655
/* Check if table data was changed: */
656
if (fIsTableDataChanged && !vboxProblem().confirmCancelingPortForwardingDialog(this))
658
/* Base class reject() slot: */
659
QIWithRetranslateUI<QIDialog>::reject();
662
/* UI Retranslation slot: */
663
void UIMachineSettingsPortForwardingDlg::retranslateUi()
665
/* Set window title: */
666
setWindowTitle(tr("Port Forwarding Rules"));
668
/* Table translations: */
669
m_pTableView->setWhatsThis(tr("This table contains a list of port forwarding rules."));
671
/* Set action's text: */
672
m_pAddAction->setText(tr("Insert new rule"));
673
m_pCopyAction->setText(tr("Copy selected rule"));
674
m_pDelAction->setText(tr("Delete selected rule"));
675
m_pAddAction->setWhatsThis(tr("This button adds new port forwarding rule."));
676
m_pDelAction->setWhatsThis(tr("This button deletes selected port forwarding rule."));
677
m_pAddAction->setToolTip(QString("%1 (%2)").arg(m_pAddAction->text()).arg(m_pAddAction->shortcut().toString()));
678
m_pDelAction->setToolTip(QString("%1 (%2)").arg(m_pDelAction->text()).arg(m_pDelAction->shortcut().toString()));
681
/* Extended event-handler: */
682
bool UIMachineSettingsPortForwardingDlg::eventFilter(QObject *pObj, QEvent *pEvent)
685
if (pObj == m_pTableView)
687
/* Switch for different event-types: */
688
switch (pEvent->type())
690
case QEvent::FocusIn:
691
case QEvent::FocusOut:
692
/* Update actions: */
698
/* Instant table adjusting: */
700
/* Delayed table adjusting: */
701
QTimer::singleShot(0, this, SLOT(sltAdjustTable()));
708
/* Continue with base-class processing: */
709
return QIWithRetranslateUI<QIDialog>::eventFilter(pObj, pEvent);
712
#include "UIMachineSettingsPortForwardingDlg.moc"