// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "perforcesettings.h" #include "perforcechecker.h" #include "perforcetr.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Utils; namespace Perforce::Internal { static QString defaultCommand() { return QLatin1String("p4" QTC_HOST_EXE_SUFFIX); } PerforceSettings::PerforceSettings() { setSettingsGroup("Perforce"); setAutoApply(false); registerAspect(&p4BinaryPath); p4BinaryPath.setDisplayStyle(StringAspect::PathChooserDisplay); p4BinaryPath.setSettingsKey("Command"); p4BinaryPath.setDefaultValue( Environment::systemEnvironment().searchInPath(defaultCommand()).toString()); p4BinaryPath.setHistoryCompleter("Perforce.Command.History"); p4BinaryPath.setExpectedKind(PathChooser::Command); p4BinaryPath.setDisplayName(Tr::tr("Perforce Command")); p4BinaryPath.setLabelText(Tr::tr("P4 command:")); registerAspect(&p4Port); p4Port.setDisplayStyle(StringAspect::LineEditDisplay); p4Port.setSettingsKey("Port"); p4Port.setLabelText(Tr::tr("P4 port:")); registerAspect(&p4Client); p4Client.setDisplayStyle(StringAspect::LineEditDisplay); p4Client.setSettingsKey("Client"); p4Client.setLabelText(Tr::tr("P4 client:")); registerAspect(&p4User); p4User.setDisplayStyle(StringAspect::LineEditDisplay); p4User.setSettingsKey("User"); p4User.setLabelText(Tr::tr("P4 user:")); registerAspect(&logCount); logCount.setSettingsKey("LogCount"); logCount.setRange(1000, 10000); logCount.setDefaultValue(1000); logCount.setLabelText(Tr::tr("Log count:")); registerAspect(&customEnv); // The settings value has been stored with the opposite meaning for a while. // Avoid changing the stored value, but flip it on read/write: customEnv.setSettingsKey("Default"); const auto invertBoolVariant = [](const QVariant &v) { return QVariant(!v.toBool()); }; customEnv.setFromSettingsTransformation(invertBoolVariant); customEnv.setToSettingsTransformation(invertBoolVariant); registerAspect(&timeOutS); timeOutS.setSettingsKey("TimeOut"); timeOutS.setRange(1, 360); timeOutS.setDefaultValue(30); timeOutS.setLabelText(Tr::tr("Timeout:")); timeOutS.setSuffix(Tr::tr("s")); registerAspect(&autoOpen); autoOpen.setSettingsKey("PromptToOpen"); autoOpen.setDefaultValue(true); autoOpen.setLabelText(Tr::tr("Automatically open files when editing")); } // --------------------PerforceSettings PerforceSettings::~PerforceSettings() { delete m_topLevelDir; } QStringList PerforceSettings::commonP4Arguments() const { QStringList lst; if (customEnv.value()) { if (!p4Client.value().isEmpty()) lst << "-c" << p4Client.value(); if (!p4Port.value().isEmpty()) lst << "-p" << p4Port.value(); if (!p4User.value().isEmpty()) lst << "-u" << p4User.value(); } return lst; } QStringList PerforceSettings::commonP4Arguments_volatile() const { QStringList lst; if (customEnv.volatileValue().toBool()) { auto p4C = p4Client.volatileValue().toString(); if (!p4C.isEmpty()) lst << "-c" << p4C; auto p4P = p4Port.volatileValue().toString(); if (!p4P.isEmpty()) lst << "-p" << p4P; auto p4U = p4User.volatileValue().toString(); if (!p4U.isEmpty()) lst << "-u" << p4U; } return lst; } bool PerforceSettings::isValid() const { return !m_topLevel.isEmpty() && !p4BinaryPath.value().isEmpty(); } bool PerforceSettings::defaultEnv() const { return !customEnv.value(); // Note: negated } FilePath PerforceSettings::topLevel() const { return FilePath::fromString(m_topLevel); } FilePath PerforceSettings::topLevelSymLinkTarget() const { return FilePath::fromString(m_topLevelSymLinkTarget); } void PerforceSettings::setTopLevel(const QString &t) { if (m_topLevel == t) return; clearTopLevel(); if (!t.isEmpty()) { // Check/expand symlinks as creator always has expanded file paths QFileInfo fi(t); if (fi.isSymLink()) { m_topLevel = t; m_topLevelSymLinkTarget = QFileInfo(fi.symLinkTarget()).absoluteFilePath(); } else { m_topLevelSymLinkTarget = m_topLevel = t; } m_topLevelDir = new QDir(m_topLevelSymLinkTarget); } } void PerforceSettings::clearTopLevel() { delete m_topLevelDir; m_topLevelDir = nullptr; m_topLevel.clear(); } QString PerforceSettings::relativeToTopLevel(const QString &dir) const { QTC_ASSERT(m_topLevelDir, return QLatin1String("../") + dir); return m_topLevelDir->relativeFilePath(dir); } QString PerforceSettings::relativeToTopLevelArguments(const QString &dir) const { return relativeToTopLevel(dir); } // Map the root part of a path: // Calling "/home/john/foo" with old="/home", new="/user" // results in "/user/john/foo" static inline QString mapPathRoot(const QString &path, const QString &oldPrefix, const QString &newPrefix) { if (path.isEmpty() || oldPrefix.isEmpty() || newPrefix.isEmpty() || oldPrefix == newPrefix) return path; if (path == oldPrefix) return newPrefix; if (path.startsWith(oldPrefix)) return newPrefix + path.right(path.size() - oldPrefix.size()); return path; } QStringList PerforceSettings::commonP4Arguments(const QString &workingDir) const { QStringList rc; if (!workingDir.isEmpty()) { /* Determine the -d argument for the working directory for matching relative paths. * It is is below the toplevel, replace top level portion by exact specification. */ rc << QLatin1String("-d") << QDir::toNativeSeparators(mapPathRoot(workingDir, m_topLevelSymLinkTarget, m_topLevel)); } rc.append(commonP4Arguments()); return rc; } QString PerforceSettings::mapToFileSystem(const QString &perforceFilePath) const { return mapPathRoot(perforceFilePath, m_topLevel, m_topLevelSymLinkTarget); } // SettingsPage PerforceSettingsPage::PerforceSettingsPage(PerforceSettings *settings) { setId(VcsBase::Constants::VCS_ID_PERFORCE); setDisplayName(Tr::tr("Perforce")); setCategory(VcsBase::Constants::VCS_SETTINGS_CATEGORY); setSettings(settings); setLayouter([settings](QWidget *widget) { PerforceSettings &s = *settings; using namespace Layouting; auto errorLabel = new QLabel; auto testButton = new QPushButton(Tr::tr("Test")); QObject::connect(testButton, &QPushButton::clicked, widget, [settings, errorLabel, testButton] { testButton->setEnabled(false); auto checker = new PerforceChecker(errorLabel); checker->setUseOverideCursor(true); QObject::connect(checker, &PerforceChecker::failed, errorLabel, [errorLabel, testButton, checker](const QString &t) { errorLabel->setStyleSheet("background-color: red"); errorLabel->setText(t); testButton->setEnabled(true); checker->deleteLater(); }); QObject::connect(checker, &PerforceChecker::succeeded, errorLabel, [errorLabel, testButton, checker](const FilePath &repo) { errorLabel->setStyleSheet({}); errorLabel->setText(Tr::tr("Test succeeded (%1).") .arg(repo.toUserOutput())); testButton->setEnabled(true); checker->deleteLater(); }); errorLabel->setStyleSheet(QString()); errorLabel->setText(Tr::tr("Testing...")); const FilePath p4Bin = FilePath::fromUserInput( settings->p4BinaryPath.volatileValue().toString()); checker->start(p4Bin, {}, settings->commonP4Arguments_volatile(), 10000); }); Group config { title(Tr::tr("Configuration")), Row { s.p4BinaryPath } }; Group environment { title(Tr::tr("Environment Variables")), s.customEnv.groupChecker(), Row { s.p4Port, s.p4Client, s.p4User } }; Group misc { title(Tr::tr("Miscellaneous")), Column { Row { s.logCount, s.timeOutS, st }, s.autoOpen } }; Column { config, environment, misc, Row { errorLabel, st, testButton }, st }.attachTo(widget); }); } } // Perforce::Internal