andre@25: /* Copyright (C) 2015 by ETH Zürich andre@25: * Software engineering by Intevation GmbH andre@9: * andre@9: * This file is Free Software under the GNU GPL (v>=2) andre@9: * and comes with ABSOLUTELY NO WARRANTY! andre@9: * See LICENSE.txt for details. andre@9: */ andre@9: #include "folderselectdialog.h" andre@9: #include "constants.h" andre@9: andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@9: #include andre@45: #include andre@9: andre@84: static ulong ipStringToLong(const QString str) { andre@84: QStringList octets = str.split("."); andre@84: if (!octets.size() > 3) { andre@84: qWarning() << "invalid call to str to long"; andre@84: return 0; andre@84: } andre@84: ulong s1 = octets.at(0).toLong(); andre@84: ulong s2 = octets.at(1).toLong(); andre@84: ulong s3 = octets.at(2).toLong(); andre@84: ulong s4 = octets.at(3).toLong(); andre@84: andre@84: return (s1 << 24) | (s2 << 16) | (s3 << 8) | s4; andre@84: } andre@84: andre@84: class IPAwareSortModel : public QSortFilterProxyModel andre@84: { andre@84: public: andre@84: bool lessThan(const QModelIndex &left, andre@84: const QModelIndex &right) const andre@84: { andre@84: QString leftString = sourceModel()->data(left).toString(); andre@84: QString rightString = sourceModel()->data(right).toString(); andre@84: andre@84: static QRegExp ipPattern("\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b"); andre@84: if (ipPattern.indexIn(leftString) != -1 && ipPattern.indexIn(rightString) != -1) { andre@84: return ipStringToLong(leftString) > ipStringToLong(rightString); andre@84: } else { andre@84: return QSortFilterProxyModel::lessThan(left, right); andre@84: } andre@84: } andre@84: }; andre@84: andre@9: FolderSelectDialog::FolderSelectDialog(const QString& startFolder, andre@9: const QString& folderPattern, andre@9: const QString& pathLabel, andre@9: QWidget * parent, andre@9: Qt::WindowFlags f) : andre@9: QDialog(parent, f), andre@9: mCurFolder(startFolder), andre@9: mFolderPattern(folderPattern), andre@9: mPathLabelString(pathLabel), andre@9: mPathLabel(NULL), andre@9: mPathLineEdit(NULL) andre@9: { andre@84: mSortModel = new IPAwareSortModel(); andre@9: mModel = new QStandardItemModel(); andre@9: mShowPathSelection = !pathLabel.isEmpty(); andre@9: setupGUI(); andre@9: if (mShowPathSelection) { andre@9: mPathLabel->setText("" + pathLabel + ": "); andre@9: } andre@9: setFolder(mCurFolder); andre@50: #ifndef Q_OS_WIN andre@50: QIcon windowIcon = QIcon(":/icon-64.png"); andre@50: setWindowIcon(windowIcon); andre@50: #endif andre@9: } andre@9: andre@9: void FolderSelectDialog::setupGUI() { andre@9: QVBoxLayout *base = new QVBoxLayout; andre@9: QHBoxLayout *folderChangeArea = new QHBoxLayout; andre@9: andre@9: if (mShowPathSelection) { andre@9: mPathLabel = new QLabel; andre@9: folderChangeArea->addWidget(mPathLabel); andre@9: base->addLayout(folderChangeArea); andre@9: andre@9: QPushButton *folderSelect = new QPushButton(); andre@9: folderSelect->setToolTip(tr("Select folder")); andre@9: folderSelect->setIcon(QApplication::style()->standardIcon(QStyle::SP_DirIcon)); andre@9: connect(folderSelect, &QPushButton::clicked, this, andre@9: &FolderSelectDialog::changeFolderClicked); andre@9: folderChangeArea->addWidget(folderSelect); andre@9: andre@9: mPathLineEdit = new QLineEdit; andre@9: mPathLineEdit->setClearButtonEnabled(true); andre@9: connect(mPathLineEdit, &QLineEdit::textChanged, this, andre@9: &FolderSelectDialog::pathLineChanged); andre@9: folderChangeArea->addWidget(mPathLineEdit); andre@9: andre@9: mGoButton = new QPushButton(tr("Go")); andre@9: mGoButton->setEnabled(false); andre@90: andre@90: QPushButton *backBtn = new QPushButton; andre@90: backBtn->setIcon(QApplication::style()->standardIcon(QStyle::SP_ArrowBack)); andre@90: backBtn->setToolTip(tr("Back to exam selection.")); andre@90: andre@9: folderChangeArea->addWidget(mGoButton); andre@9: connect(mGoButton, &QPushButton::clicked, this, andre@9: &FolderSelectDialog::goClicked); andre@79: connect(mPathLineEdit, &QLineEdit::returnPressed, this, andre@79: &FolderSelectDialog::goClicked); andre@9: } andre@9: andre@88: mFilterWidget = new FilterWidget(mSortModel, false); andre@9: base->addWidget(mFilterWidget); andre@88: connect(mSortModel, &QSortFilterProxyModel::sourceModelChanged, andre@88: mFilterWidget, &FilterWidget::headersChanged); andre@9: andre@9: mView = new QTableView; andre@9: mView->setModel(mSortModel); andre@9: andre@9: mView->setSelectionBehavior(QAbstractItemView::SelectRows); andre@9: mView->setSelectionMode(QAbstractItemView::SingleSelection); andre@9: mView->setSortingEnabled(true); andre@9: mView->setEditTriggers(QAbstractItemView::NoEditTriggers); andre@9: connect(mView->selectionModel(), &QItemSelectionModel::selectionChanged, andre@9: this, &FolderSelectDialog::selectionChanged); andre@9: connect(mView, &QTableView::doubleClicked, this, andre@9: &FolderSelectDialog::wantToAccept); andre@9: andre@9: base->addWidget(mView); andre@9: andre@9: QHBoxLayout * bottomButtons = new QHBoxLayout; andre@9: base->addLayout(bottomButtons); andre@9: andre@9: mOkButton = new QPushButton(tr("Ok")); andre@9: mOkButton->setEnabled(false); andre@9: connect(mOkButton, &QPushButton::clicked, this, andre@9: &FolderSelectDialog::wantToAccept); andre@9: bottomButtons->addStretch(-1); andre@90: andre@90: QPushButton *backBtn = NULL; andre@90: if (!mShowPathSelection) { andre@90: backBtn = new QPushButton(tr("Back")); andre@90: backBtn->setToolTip(tr("Back to exam selection.")); andre@90: connect(backBtn, &QPushButton::clicked, this, andre@90: &FolderSelectDialog::backClicked); andre@90: bottomButtons->addWidget(backBtn); andre@90: } andre@90: andre@9: bottomButtons->addWidget(mOkButton); andre@9: setLayout(base); andre@9: } andre@9: andre@9: void FolderSelectDialog::selectionChanged (const QItemSelection& selected, andre@9: const QItemSelection& deselected) { andre@9: mOkButton->setEnabled(!selected.indexes().isEmpty()); andre@90: if (mOkButton->isEnabled()) { andre@90: mOkButton->setFocus(); andre@90: } andre@9: } andre@9: andre@9: void FolderSelectDialog::pathLineChanged() { andre@9: const QString path = mPathLineEdit->text(); andre@9: if (path.isEmpty()) { andre@9: mGoButton->setEnabled(false); andre@9: return; andre@9: } andre@79: mGoButton->setEnabled(true); andre@9: } andre@9: andre@9: andre@9: void FolderSelectDialog::goClicked() { andre@79: const QString path = mPathLineEdit->text(); andre@79: QDir dir(path); andre@79: if (!dir.exists()) { andre@79: QMessageBox::warning(this, tr("Error!"), tr("Failed to access directory: '%1'").arg(path)); andre@79: mGoButton->setEnabled(false); andre@79: return; andre@79: } andre@9: setFolder(mPathLineEdit->text()); andre@9: QSettings settings; andre@9: /* assuming go is only available in root folder mode */ andre@9: settings.setValue(ROOT_FOLDER_KEY, mPathLineEdit->text()); andre@9: } andre@9: andre@9: void FolderSelectDialog::setFolder(const QString& folder) { andre@61: if (folder.isEmpty()) { andre@61: return; andre@61: } andre@9: mCurFolder = folder; andre@9: if (mPathLineEdit) { andre@9: mPathLineEdit->setText(mCurFolder); andre@9: } andre@9: andre@9: QStringList columns = mFolderPattern.split(PATTERN_SEPERATOR); andre@9: mModel->clear(); andre@9: mModel->setHorizontalHeaderLabels(columns); andre@9: QDir dir(folder); andre@9: qDebug() << "Folder set to: " << folder; andre@45: QStringList errors; andre@9: foreach (const QString & subfolder, dir.entryList(QDir::Dirs | andre@9: QDir::Readable | andre@9: QDir::NoDotAndDotDot)) { andre@9: qDebug() << "Looking at: " << subfolder; andre@9: QStringList itemData = subfolder.split(PATTERN_SEPERATOR); andre@9: if (itemData.size() != columns.size()) { andre@45: errors << subfolder; andre@9: continue; andre@9: } andre@9: QList items; andre@9: foreach (const QString& part, itemData) { andre@9: QStandardItem * item = new QStandardItem(part); andre@9: item->setData(dir.absoluteFilePath(subfolder)); andre@9: items.append(item); andre@9: } andre@9: mModel->appendRow(items); andre@9: } andre@9: mSortModel->setSourceModel(mModel); andre@45: if (!errors.isEmpty()) { andre@45: QString errMsg = tr("The following folders did not match the pattern: %1").arg(mFolderPattern); andre@45: errMsg += "\n" + errors.join("\n"); andre@45: QMessageBox::warning(this, tr("Failed to parse some folders."), errMsg); andre@45: } andre@86: mView->resizeColumnsToContents(); andre@9: } andre@9: andre@9: void FolderSelectDialog::wantToAccept() { andre@9: QItemSelectionModel *selection = mView->selectionModel(); andre@9: QModelIndexList selected = selection->selectedIndexes(); andre@9: if (selected.isEmpty()) { /* Ok should not be enabled in that case */ andre@9: return; andre@9: } andre@9: const QString folder = selected[0].data(Qt::UserRole + 1).toString(); andre@9: andre@9: emit folderSelected(folder); andre@9: accept(); andre@9: } andre@9: andre@9: void FolderSelectDialog::changeFolderClicked() andre@9: { andre@9: const QString oldFolder = mCurFolder; andre@9: const QString startLoc = mCurFolder.isEmpty() ? andre@9: QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) : andre@9: mCurFolder; andre@9: andre@9: QString outFolder = QFileDialog::getExistingDirectory( andre@12: this, tr("Select %1").arg(mPathLabelString), andre@9: startLoc); andre@9: if (outFolder.isEmpty()) { andre@9: /* User aborted */ andre@9: return; andre@9: } andre@9: setFolder(outFolder); andre@9: }