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@99: 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@101: resize(700, 600); 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@101: QString stripQuotes (const QString &in) andre@101: { andre@101: QString out = in; andre@101: if (in.startsWith("\"")) { andre@101: out = out.right(out.size()-1); andre@101: } andre@101: if (in.endsWith("\"")) { andre@101: out = out.left(out.size()-1); andre@101: } andre@101: return out; andre@101: } andre@101: andre@101: QMap readExtraColumns(const QString &path) andre@101: { andre@101: QMap ret; andre@101: andre@101: QFile file(path); andre@101: if (!file.open(QIODevice::ReadOnly)) { andre@101: qDebug () << "Failed to open: " << path; andre@101: return ret; andre@101: } andre@101: QTextStream in(&file); andre@101: andre@101: while (!in.atEnd()) { andre@101: QString line = in.readLine(); andre@101: int equalsign = line.indexOf("="); andre@101: if (equalsign != -1) { andre@101: QString name = stripQuotes(line.left(equalsign).replace (QStringLiteral("export "), "")); andre@101: QString value = stripQuotes(line.right(line.size() - equalsign - 1)); andre@101: ret.insert (name, value); andre@101: } andre@101: } andre@101: return ret; andre@101: } andre@101: 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@101: int patternSize = columns.size(); andre@101: QDir dir(folder); andre@9: mModel->clear(); andre@9: qDebug() << "Folder set to: " << folder; 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@101: if (itemData.size() != patternSize) { andre@100: qDebug() << "Folder does not match pattern: " << 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@101: QFileInfo fi (dir.absoluteFilePath(subfolder + "/.exam-setup-user")); andre@9: items.append(item); andre@101: if (fi.isReadable()) { andre@101: const QMap map = readExtraColumns (fi.filePath()); andre@101: andre@101: Q_FOREACH (const QString &name, map.keys()) andre@101: { andre@101: qDebug() << "Adding extra column: " << name; andre@101: if (!columns.contains(name)) { andre@101: columns << name; andre@101: } andre@101: QStandardItem * subitem = new QStandardItem(map[name]); andre@101: items.append(subitem); andre@101: } andre@101: } andre@9: } andre@9: mModel->appendRow(items); andre@9: } andre@101: mModel->setHorizontalHeaderLabels(columns); andre@9: mSortModel->setSourceModel(mModel); 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: }