2016-10-01 06:54:57 +00:00
|
|
|
/* interface_frame.cpp
|
|
|
|
* Display of interfaces, including their respective data, and the
|
|
|
|
* capability to filter interfaces by type
|
|
|
|
*
|
|
|
|
* Wireshark - Network traffic analyzer
|
|
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
|
|
* Copyright 1998 Gerald Combs
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU General Public License
|
|
|
|
* as published by the Free Software Foundation; either version 2
|
|
|
|
* of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <ui_interface_frame.h>
|
|
|
|
|
2017-04-25 20:43:32 +00:00
|
|
|
#include "caputils/capture_ifinfo.h"
|
2016-10-01 06:54:57 +00:00
|
|
|
#include "ui/qt/interface_frame.h"
|
2017-07-25 14:47:16 +00:00
|
|
|
#include <ui/qt/models/interface_tree_model.h>
|
2016-10-01 06:54:57 +00:00
|
|
|
|
2017-07-25 14:47:16 +00:00
|
|
|
#include <ui/qt/models/sparkline_delegate.h>
|
2016-10-01 06:54:57 +00:00
|
|
|
#include "wireshark_application.h"
|
|
|
|
|
|
|
|
#ifdef HAVE_EXTCAP
|
|
|
|
#include "extcap.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <QFrame>
|
|
|
|
#include <QPushButton>
|
|
|
|
#include <QHBoxLayout>
|
|
|
|
#include <QLabel>
|
|
|
|
#include <QItemSelection>
|
|
|
|
|
|
|
|
#define BTN_IFTYPE_PROPERTY "ifType"
|
|
|
|
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
|
|
const int stat_update_interval_ = 1000; // ms
|
|
|
|
#endif
|
|
|
|
|
|
|
|
InterfaceFrame::InterfaceFrame(QWidget * parent)
|
2017-11-23 12:34:19 +00:00
|
|
|
: QFrame(parent),
|
2016-10-01 06:54:57 +00:00
|
|
|
ui(new Ui::InterfaceFrame)
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
|
|
,stat_timer_(NULL)
|
|
|
|
#endif // HAVE_LIBPCAP
|
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
|
|
|
|
|
|
|
setStyleSheet(QString(
|
|
|
|
"QFrame {"
|
|
|
|
" border: 0;"
|
|
|
|
"}"
|
|
|
|
"QTreeView {"
|
|
|
|
" border: 0;"
|
|
|
|
"}"
|
|
|
|
));
|
|
|
|
|
|
|
|
#ifdef Q_OS_MAC
|
|
|
|
ui->interfaceTree->setAttribute(Qt::WA_MacShowFocusRect, false);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* TODO: There must be a better way to do this */
|
2016-10-17 09:57:00 +00:00
|
|
|
ifTypeDescription.insert(IF_WIRED, tr("Wired"));
|
2016-10-01 06:54:57 +00:00
|
|
|
ifTypeDescription.insert(IF_AIRPCAP, tr("AirPCAP"));
|
|
|
|
ifTypeDescription.insert(IF_PIPE, tr("Pipe"));
|
|
|
|
ifTypeDescription.insert(IF_STDIN, tr("STDIN"));
|
|
|
|
ifTypeDescription.insert(IF_BLUETOOTH, tr("Bluetooth"));
|
|
|
|
ifTypeDescription.insert(IF_WIRELESS, tr("Wireless"));
|
|
|
|
ifTypeDescription.insert(IF_DIALUP, tr("Dial-Up"));
|
|
|
|
ifTypeDescription.insert(IF_USB, tr("USB"));
|
|
|
|
#ifdef HAVE_EXTCAP
|
|
|
|
ifTypeDescription.insert(IF_EXTCAP, tr("External Capture"));
|
|
|
|
#endif
|
|
|
|
ifTypeDescription.insert(IF_VIRTUAL, tr ("Virtual"));
|
|
|
|
|
|
|
|
proxyModel = new InterfaceSortFilterModel(this);
|
|
|
|
sourceModel = new InterfaceTreeModel(this);
|
2016-10-03 14:31:36 +00:00
|
|
|
|
|
|
|
QList<InterfaceTreeColumns> columns;
|
|
|
|
#ifdef HAVE_EXTCAP
|
|
|
|
columns.append(IFTREE_COL_EXTCAP);
|
|
|
|
#endif
|
|
|
|
columns.append(IFTREE_COL_NAME);
|
|
|
|
columns.append(IFTREE_COL_STATS);
|
|
|
|
proxyModel->setColumns(columns);
|
2016-10-26 08:03:50 +00:00
|
|
|
proxyModel->setStoreOnChange(true);
|
2016-10-01 06:54:57 +00:00
|
|
|
proxyModel->setSourceModel(sourceModel);
|
|
|
|
ui->interfaceTree->setModel(proxyModel);
|
|
|
|
|
2016-10-03 14:31:36 +00:00
|
|
|
ui->interfaceTree->setItemDelegateForColumn(proxyModel->mapSourceToColumn(IFTREE_COL_STATS), new SparkLineDelegate(this));
|
2016-10-01 06:54:57 +00:00
|
|
|
|
|
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(interfaceListChanged()));
|
|
|
|
connect(wsApp, SIGNAL(localInterfaceListChanged()), this, SLOT(interfaceListChanged()));
|
|
|
|
|
|
|
|
connect(ui->interfaceTree->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
|
|
|
|
this, SLOT(interfaceTreeSelectionChanged(const QItemSelection &, const QItemSelection &)));
|
|
|
|
}
|
|
|
|
|
|
|
|
InterfaceFrame::~InterfaceFrame()
|
|
|
|
{
|
|
|
|
delete sourceModel;
|
|
|
|
delete proxyModel;
|
|
|
|
delete ui;
|
|
|
|
}
|
|
|
|
|
2016-10-17 09:57:00 +00:00
|
|
|
QMenu * InterfaceFrame::getSelectionMenu()
|
|
|
|
{
|
2017-04-19 21:11:43 +00:00
|
|
|
QMenu * contextMenu = new QMenu(this);
|
2016-10-17 09:57:00 +00:00
|
|
|
QList<int> typesDisplayed = proxyModel->typesDisplayed();
|
|
|
|
|
2016-10-18 20:28:28 +00:00
|
|
|
QMap<int, QString>::const_iterator it = ifTypeDescription.constBegin();
|
|
|
|
while(it != ifTypeDescription.constEnd())
|
2016-10-17 09:57:00 +00:00
|
|
|
{
|
2016-10-18 20:28:28 +00:00
|
|
|
int ifType = it.key();
|
|
|
|
|
|
|
|
if ( typesDisplayed.contains(ifType) )
|
|
|
|
{
|
|
|
|
QAction *endp_action = new QAction(it.value(), this);
|
|
|
|
endp_action->setData(qVariantFromValue(ifType));
|
|
|
|
endp_action->setCheckable(true);
|
|
|
|
endp_action->setChecked(proxyModel->isInterfaceTypeShown(ifType));
|
|
|
|
connect(endp_action, SIGNAL(triggered()), this, SLOT(triggeredIfTypeButton()));
|
|
|
|
contextMenu->addAction(endp_action);
|
|
|
|
}
|
|
|
|
++it;
|
2016-10-17 09:57:00 +00:00
|
|
|
}
|
|
|
|
|
2016-12-27 09:14:32 +00:00
|
|
|
#ifdef HAVE_PCAP_REMOTE
|
|
|
|
if ( proxyModel->remoteInterfacesExist() )
|
|
|
|
{
|
|
|
|
QAction * toggleRemoteAction = new QAction(tr("Remote interfaces"), this);
|
|
|
|
toggleRemoteAction->setCheckable(true);
|
|
|
|
toggleRemoteAction->setChecked(! proxyModel->remoteDisplay());
|
|
|
|
connect(toggleRemoteAction, SIGNAL(triggered()), this, SLOT(toggleRemoteInterfaces()));
|
|
|
|
contextMenu->addAction(toggleRemoteAction);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-03-13 10:10:08 +00:00
|
|
|
#if 0
|
|
|
|
// Disabled until bug 13354 is fixed
|
2016-10-20 07:34:49 +00:00
|
|
|
contextMenu->addSeparator();
|
|
|
|
QAction * toggleHideAction = new QAction(tr("Show hidden interfaces"), this);
|
|
|
|
toggleHideAction->setCheckable(true);
|
|
|
|
toggleHideAction->setChecked(! proxyModel->filterHidden());
|
|
|
|
connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHiddenInterfaces()));
|
|
|
|
contextMenu->addAction(toggleHideAction);
|
2017-03-13 10:10:08 +00:00
|
|
|
#endif
|
2016-10-20 07:34:49 +00:00
|
|
|
|
2016-10-17 09:57:00 +00:00
|
|
|
return contextMenu;
|
|
|
|
}
|
|
|
|
|
|
|
|
int InterfaceFrame::interfacesHidden()
|
|
|
|
{
|
|
|
|
return proxyModel->interfacesHidden();
|
|
|
|
}
|
|
|
|
|
|
|
|
int InterfaceFrame::interfacesPresent()
|
|
|
|
{
|
|
|
|
return sourceModel->rowCount() - proxyModel->interfacesHidden();
|
|
|
|
}
|
|
|
|
|
2017-07-16 21:26:51 +00:00
|
|
|
void InterfaceFrame::ensureSelectedInterface()
|
2017-07-14 21:38:59 +00:00
|
|
|
{
|
2017-07-16 21:26:51 +00:00
|
|
|
#ifdef HAVE_LIBPCAP
|
|
|
|
if (interfacesPresent() < 1) return;
|
|
|
|
|
|
|
|
if (sourceModel->selectedDevices().count() < 1) {
|
|
|
|
QModelIndex first_idx = proxyModel->index(0, 0);
|
|
|
|
ui->interfaceTree->setCurrentIndex(first_idx);
|
|
|
|
}
|
|
|
|
|
2017-07-14 21:38:59 +00:00
|
|
|
ui->interfaceTree->setFocus();
|
2017-07-16 21:26:51 +00:00
|
|
|
#endif
|
2017-07-14 21:38:59 +00:00
|
|
|
}
|
|
|
|
|
2016-10-01 06:54:57 +00:00
|
|
|
void InterfaceFrame::hideEvent(QHideEvent *) {
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
|
|
if (stat_timer_)
|
|
|
|
stat_timer_->stop();
|
|
|
|
sourceModel->stopStatistic();
|
|
|
|
#endif // HAVE_LIBPCAP
|
|
|
|
}
|
|
|
|
|
|
|
|
void InterfaceFrame::showEvent(QShowEvent *) {
|
2016-10-13 07:11:50 +00:00
|
|
|
|
2016-10-01 06:54:57 +00:00
|
|
|
#ifdef HAVE_LIBPCAP
|
|
|
|
if (stat_timer_)
|
|
|
|
stat_timer_->start(stat_update_interval_);
|
|
|
|
#endif // HAVE_LIBPCAP
|
|
|
|
}
|
|
|
|
|
|
|
|
void InterfaceFrame::actionButton_toggled(bool checked)
|
|
|
|
{
|
|
|
|
QVariant ifType = sender()->property(BTN_IFTYPE_PROPERTY);
|
|
|
|
if ( ifType.isValid() )
|
|
|
|
{
|
|
|
|
proxyModel->setInterfaceTypeVisible(ifType.toInt(), checked);
|
|
|
|
}
|
2016-10-13 07:11:50 +00:00
|
|
|
|
|
|
|
resetInterfaceTreeDisplay();
|
2016-10-01 06:54:57 +00:00
|
|
|
}
|
|
|
|
|
2016-10-17 09:57:00 +00:00
|
|
|
void InterfaceFrame::triggeredIfTypeButton()
|
2016-10-01 06:54:57 +00:00
|
|
|
{
|
2016-10-17 09:57:00 +00:00
|
|
|
QAction *sender = qobject_cast<QAction *>(QObject::sender());
|
|
|
|
if ( sender )
|
2016-10-01 06:54:57 +00:00
|
|
|
{
|
2016-10-17 09:57:00 +00:00
|
|
|
int ifType = sender->data().value<int>();
|
|
|
|
proxyModel->toggleTypeVisibility(ifType);
|
2016-10-01 06:54:57 +00:00
|
|
|
|
2016-10-17 09:57:00 +00:00
|
|
|
resetInterfaceTreeDisplay();
|
|
|
|
emit typeSelectionChanged();
|
|
|
|
}
|
2016-10-01 06:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InterfaceFrame::interfaceListChanged()
|
2016-10-13 07:11:50 +00:00
|
|
|
{
|
|
|
|
resetInterfaceTreeDisplay();
|
2017-03-10 13:37:18 +00:00
|
|
|
// Ensure that device selection is consistent with the displayed selection.
|
|
|
|
updateSelectedInterfaces();
|
2016-10-17 09:57:00 +00:00
|
|
|
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
|
|
if (!stat_timer_) {
|
|
|
|
updateStatistics();
|
|
|
|
stat_timer_ = new QTimer(this);
|
|
|
|
connect(stat_timer_, SIGNAL(timeout()), this, SLOT(updateStatistics()));
|
|
|
|
stat_timer_->start(stat_update_interval_);
|
|
|
|
}
|
|
|
|
#endif
|
2016-10-13 07:11:50 +00:00
|
|
|
}
|
|
|
|
|
2016-10-20 07:34:49 +00:00
|
|
|
void InterfaceFrame::toggleHiddenInterfaces()
|
|
|
|
{
|
|
|
|
proxyModel->toggleFilterHidden();
|
|
|
|
|
|
|
|
emit typeSelectionChanged();
|
|
|
|
}
|
|
|
|
|
2016-12-27 09:14:32 +00:00
|
|
|
#ifdef HAVE_PCAP_REMOTE
|
|
|
|
void InterfaceFrame::toggleRemoteInterfaces()
|
|
|
|
{
|
|
|
|
proxyModel->toggleRemoteDisplay();
|
|
|
|
emit typeSelectionChanged();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2016-10-13 07:11:50 +00:00
|
|
|
void InterfaceFrame::resetInterfaceTreeDisplay()
|
2016-10-01 06:54:57 +00:00
|
|
|
{
|
2016-10-03 14:31:36 +00:00
|
|
|
if ( proxyModel->rowCount() == 0 )
|
2016-10-01 06:54:57 +00:00
|
|
|
{
|
|
|
|
ui->interfaceTree->setHidden(true);
|
|
|
|
ui->lblNoInterfaces->setHidden(false);
|
|
|
|
|
2016-10-13 07:11:50 +00:00
|
|
|
ui->lblNoInterfaces->setText( proxyModel->interfaceError() );
|
2016-10-01 06:54:57 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ui->interfaceTree->setHidden(false);
|
|
|
|
ui->lblNoInterfaces->setHidden(true);
|
2016-10-13 07:11:50 +00:00
|
|
|
#ifdef HAVE_EXTCAP
|
|
|
|
ui->interfaceTree->resizeColumnToContents(proxyModel->mapSourceToColumn(IFTREE_COL_EXTCAP));
|
|
|
|
#endif
|
|
|
|
ui->interfaceTree->resizeColumnToContents(proxyModel->mapSourceToColumn(IFTREE_COL_NAME));
|
|
|
|
ui->interfaceTree->resizeColumnToContents(proxyModel->mapSourceToColumn(IFTREE_COL_STATS));
|
2016-10-01 06:54:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void InterfaceFrame::updateSelectedInterfaces()
|
|
|
|
{
|
2016-10-02 14:22:27 +00:00
|
|
|
if ( sourceModel->rowCount() == 0 )
|
2016-10-01 06:54:57 +00:00
|
|
|
return;
|
2016-10-02 14:47:04 +00:00
|
|
|
#ifdef HAVE_LIBPCAP
|
2016-10-03 14:31:36 +00:00
|
|
|
QItemSelection sourceSelection = sourceModel->selectedDevices();
|
|
|
|
QItemSelection mySelection = proxyModel->mapSelectionFromSource(sourceSelection);
|
2016-10-01 06:54:57 +00:00
|
|
|
|
|
|
|
ui->interfaceTree->selectionModel()->clearSelection();
|
|
|
|
ui->interfaceTree->selectionModel()->select(mySelection, QItemSelectionModel::SelectCurrent );
|
2016-10-02 14:47:04 +00:00
|
|
|
#endif
|
2016-10-01 06:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InterfaceFrame::interfaceTreeSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
|
|
|
|
{
|
|
|
|
if (selected.count() == 0 && deselected.count() == 0)
|
|
|
|
return;
|
2016-10-02 14:22:27 +00:00
|
|
|
if ( sourceModel->rowCount() == 0 )
|
2016-10-01 06:54:57 +00:00
|
|
|
return;
|
|
|
|
|
2016-10-03 14:31:36 +00:00
|
|
|
#ifdef HAVE_LIBPCAP
|
2016-10-01 06:54:57 +00:00
|
|
|
/* Take all selected interfaces, not just the newly ones */
|
|
|
|
QItemSelection allSelected = ui->interfaceTree->selectionModel()->selection();
|
|
|
|
QItemSelection sourceSelection = proxyModel->mapSelectionToSource(allSelected);
|
|
|
|
|
2016-10-03 14:31:36 +00:00
|
|
|
if ( sourceModel->updateSelectedDevices(sourceSelection) )
|
2016-10-01 06:54:57 +00:00
|
|
|
emit itemSelectionChanged();
|
2016-10-02 14:47:04 +00:00
|
|
|
#endif
|
2016-10-01 06:54:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void InterfaceFrame::on_interfaceTree_doubleClicked(const QModelIndex &index)
|
|
|
|
{
|
|
|
|
QModelIndex realIndex = proxyModel->mapToSource(index);
|
|
|
|
|
2016-10-02 14:22:27 +00:00
|
|
|
if ( ! realIndex.isValid() )
|
2016-10-01 06:54:57 +00:00
|
|
|
return;
|
|
|
|
|
2016-10-02 14:47:04 +00:00
|
|
|
#if defined(HAVE_EXTCAP) && defined(HAVE_LIBPCAP)
|
2016-10-01 06:54:57 +00:00
|
|
|
|
2016-10-03 14:31:36 +00:00
|
|
|
QString device_name = sourceModel->getColumnContent(realIndex.row(), IFTREE_COL_INTERFACE_NAME).toString();
|
|
|
|
QString extcap_string = sourceModel->getColumnContent(realIndex.row(), IFTREE_COL_EXTCAP_PATH).toString();
|
2016-10-01 06:54:57 +00:00
|
|
|
|
|
|
|
/* We trust the string here. If this interface is really extcap, the string is
|
|
|
|
* being checked immediatly before the dialog is being generated */
|
|
|
|
if ( extcap_string.length() > 0 )
|
|
|
|
{
|
|
|
|
/* this checks if configuration is required and not yet provided or saved via prefs */
|
|
|
|
if ( extcap_has_configuration((const char *)(device_name.toStdString().c_str()), TRUE) )
|
|
|
|
{
|
|
|
|
emit showExtcapOptions(device_name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
emit startCapture();
|
|
|
|
}
|
|
|
|
|
2016-10-02 14:47:04 +00:00
|
|
|
#if defined(HAVE_EXTCAP) && defined(HAVE_LIBPCAP)
|
2016-10-01 06:54:57 +00:00
|
|
|
void InterfaceFrame::on_interfaceTree_clicked(const QModelIndex &index)
|
|
|
|
{
|
|
|
|
if ( index.column() == 0 )
|
|
|
|
{
|
|
|
|
QModelIndex realIndex = proxyModel->mapToSource(index);
|
|
|
|
|
2016-10-02 14:22:27 +00:00
|
|
|
if ( ! realIndex.isValid() )
|
2016-10-01 06:54:57 +00:00
|
|
|
return;
|
|
|
|
|
2016-10-03 14:31:36 +00:00
|
|
|
QString device_name = sourceModel->getColumnContent(realIndex.row(), IFTREE_COL_INTERFACE_NAME).toString();
|
|
|
|
QString extcap_string = sourceModel->getColumnContent(realIndex.row(), IFTREE_COL_EXTCAP_PATH).toString();
|
2016-10-01 06:54:57 +00:00
|
|
|
|
|
|
|
/* We trust the string here. If this interface is really extcap, the string is
|
|
|
|
* being checked immediatly before the dialog is being generated */
|
|
|
|
if ( extcap_string.length() > 0 )
|
|
|
|
{
|
|
|
|
/* this checks if configuration is required and not yet provided or saved via prefs */
|
|
|
|
if ( extcap_has_configuration((const char *)(device_name.toStdString().c_str()), FALSE) )
|
|
|
|
{
|
|
|
|
emit showExtcapOptions(device_name);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void InterfaceFrame::updateStatistics(void)
|
|
|
|
{
|
2016-10-02 14:22:27 +00:00
|
|
|
if ( sourceModel->rowCount() == 0 )
|
2016-10-01 06:54:57 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
|
|
|
2016-10-02 14:22:27 +00:00
|
|
|
for( int idx = 0; idx < proxyModel->rowCount(); idx++ )
|
2016-10-01 06:54:57 +00:00
|
|
|
{
|
|
|
|
QModelIndex selectIndex = proxyModel->mapFromSource(sourceModel->index(idx, 0));
|
|
|
|
|
|
|
|
/* Proxy model has not masked out the interface */
|
|
|
|
if ( selectIndex.isValid() )
|
|
|
|
sourceModel->updateStatistic(idx);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Proxy Method so we do not need to expose the source model */
|
|
|
|
void InterfaceFrame::getPoints(int idx, PointList * pts)
|
|
|
|
{
|
|
|
|
sourceModel->getPoints(idx, pts);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Editor modelines
|
|
|
|
*
|
|
|
|
* Local Variables:
|
|
|
|
* c-basic-offset: 4
|
|
|
|
* tab-width: 8
|
|
|
|
* indent-tabs-mode: nil
|
|
|
|
* End:
|
|
|
|
*
|
|
|
|
* ex: set shiftwidth=4 tabstop=8 expandtab:
|
|
|
|
* :indentSize=4:tabSize=8:noTabs=true:
|
|
|
|
*/
|