aheinecke@404: /* Copyright (C) 2014 by Bundesamt für Sicherheit in der Informationstechnik aheinecke@404: * Software engineering by Intevation GmbH aheinecke@404: * aheinecke@404: * This file is Free Software under the GNU GPL (v>=2) aheinecke@404: * and comes with ABSOLUTELY NO WARRANTY! aheinecke@404: * See LICENSE.txt for details. aheinecke@404: */ rrenkert@361: #include "createinstallerdialog.h" andre@761: #include "sslhelp.h" andre@761: rrenkert@361: #include aheinecke@526: #include rrenkert@361: #include rrenkert@361: #include rrenkert@361: #include rrenkert@361: #include rrenkert@361: #include rrenkert@361: #include rrenkert@361: #include andre@761: #include aheinecke@515: #include aheinecke@526: #include aheinecke@526: #include aheinecke@515: #include aheinecke@571: #include aheinecke@571: andre@761: #include andre@761: aheinecke@571: /* Static information used in codesigning */ aheinecke@571: #ifndef SIGN_HASH aheinecke@571: #define SIGN_HASH "sha256" aheinecke@571: #endif aheinecke@571: #ifndef SIGN_URL andre@852: #define SIGN_URL "http://wald.intevation.org/projects/trustbridge/" aheinecke@571: #endif aheinecke@571: #ifndef SIGN_PUBLISHER aheinecke@571: #define SIGN_PUBLISHER "TrustBridge Test with ümlaut" aheinecke@571: #endif aheinecke@571: rrenkert@361: CreateInstallerDialog::CreateInstallerDialog(QMainWindow *parent) : aheinecke@516: QDialog(parent), aheinecke@571: mProgress(this), aheinecke@571: mInstallerPath(), aheinecke@571: mCurrentWorkingDir(NULL) rrenkert@361: { aheinecke@515: QSettings settings; rrenkert@414: setWindowTitle(tr("Create binary installer")); rrenkert@361: setupGUI(); rrenkert@414: resize(500, 250); aheinecke@515: mCertFile->setText(settings.value("CodeSignCert", QString()).toString()); aheinecke@515: mBinaryFolder->setText(settings.value("LastBinaryFolder", QString()).toString()); aheinecke@515: mSaveFile->setText(settings.value("LastBinOutputFolder", QString()).toString()); aheinecke@516: aheinecke@516: connect(&mNSISProc, SIGNAL(finished(int, QProcess::ExitStatus)), aheinecke@516: this, SLOT(processFinished(int, QProcess::ExitStatus))); aheinecke@516: connect(&mNSISProc, SIGNAL(error(QProcess::ProcessError)), aheinecke@516: this, SLOT(processError(QProcess::ProcessError))); rrenkert@361: } rrenkert@361: rrenkert@361: void CreateInstallerDialog::setupGUI() rrenkert@361: { rrenkert@361: /* Top level layout / widgets */ rrenkert@361: QVBoxLayout *topLayout = new QVBoxLayout; rrenkert@414: QVBoxLayout *headerLayout = new QVBoxLayout; rrenkert@414: QHBoxLayout *headerSubLayout = new QHBoxLayout; rrenkert@428: QHBoxLayout *centerLayout = new QHBoxLayout; rrenkert@361: QHBoxLayout *bottomLayout = new QHBoxLayout; rrenkert@428: QVBoxLayout *labelLayout = new QVBoxLayout; rrenkert@428: QVBoxLayout *fieldLayout = new QVBoxLayout; rrenkert@428: QVBoxLayout *buttonLayout = new QVBoxLayout; rrenkert@361: rrenkert@414: QLabel *header = new QLabel("

" + tr("Create binary installer") + "

"); rrenkert@414: QLabel *description = new QLabel( aheinecke@515: tr("Create and sign a TrustBridge binary installer.")); rrenkert@414: headerSubLayout->insertSpacing(0, 40); rrenkert@414: headerSubLayout->addWidget(description); rrenkert@414: QFrame *headerSeparator = new QFrame(); rrenkert@414: headerSeparator->setFrameShape(QFrame::HLine); rrenkert@414: headerSeparator->setFrameShadow(QFrame::Sunken); rrenkert@414: headerLayout->addWidget(header); rrenkert@414: headerLayout->addLayout(headerSubLayout); rrenkert@414: headerLayout->addWidget(headerSeparator); rrenkert@428: headerLayout->insertSpacing(4, 10); rrenkert@361: aheinecke@515: QLabel *archiveLabel = new QLabel(tr("Select binary folder:")); aheinecke@515: QLabel *certLabel = new QLabel(tr("Select code signing certificate:")); rrenkert@428: QLabel *saveLabel = new QLabel(tr("Select output folder:")); rrenkert@428: labelLayout->addWidget(archiveLabel); rrenkert@428: labelLayout->addWidget(certLabel); rrenkert@428: labelLayout->addWidget(saveLabel); rrenkert@428: aheinecke@515: mBinaryFolder = new QLineEdit(); rrenkert@428: mCertFile = new QLineEdit(); rrenkert@428: mSaveFile = new QLineEdit(); aheinecke@515: fieldLayout->addWidget(mBinaryFolder); rrenkert@428: fieldLayout->addWidget(mCertFile); rrenkert@428: fieldLayout->addWidget(mSaveFile); rrenkert@428: rrenkert@361: QPushButton *archiveSelect = new QPushButton("..."); aheinecke@515: connect(archiveSelect, SIGNAL(clicked()), this, SLOT(openFolderSelect())); rrenkert@361: archiveSelect->setFixedWidth(30); rrenkert@361: QPushButton *certSelect = new QPushButton("..."); rrenkert@361: connect(certSelect, SIGNAL(clicked()), this, SLOT(openCertificateSelect())); rrenkert@361: certSelect->setFixedWidth(30); rrenkert@361: QPushButton *saveSelect = new QPushButton("..."); rrenkert@361: connect(saveSelect, SIGNAL(clicked()), this, SLOT(openSaveLocation())); rrenkert@361: saveSelect->setFixedWidth(30); rrenkert@428: buttonLayout->addWidget(archiveSelect); rrenkert@428: buttonLayout->addWidget(certSelect); rrenkert@428: buttonLayout->addWidget(saveSelect); rrenkert@361: rrenkert@428: centerLayout->addLayout(labelLayout); rrenkert@428: centerLayout->addLayout(fieldLayout); rrenkert@428: centerLayout->addLayout(buttonLayout); rrenkert@361: rrenkert@414: QPushButton *create = new QPushButton(tr("Create installer")); rrenkert@361: connect(create, SIGNAL(clicked()), this, SLOT(createInstaller())); rrenkert@414: QPushButton *cancel = new QPushButton(tr("Cancel")); rrenkert@414: connect(cancel, SIGNAL(clicked()), this, SLOT(close())); rrenkert@361: bottomLayout->insertStretch(0, 10); rrenkert@361: bottomLayout->addWidget(create); rrenkert@414: bottomLayout->addWidget(cancel); rrenkert@414: rrenkert@414: QFrame *bottomSeparator = new QFrame(); rrenkert@414: bottomSeparator->setFrameShape(QFrame::HLine); rrenkert@414: bottomSeparator->setFrameShadow(QFrame::Sunken); rrenkert@361: rrenkert@361: topLayout->addLayout(headerLayout); rrenkert@361: topLayout->addLayout(centerLayout); rrenkert@361: topLayout->insertStretch(2, 10); rrenkert@428: centerLayout->insertSpacing(3, 10); rrenkert@414: topLayout->addWidget(bottomSeparator); rrenkert@361: topLayout->addLayout(bottomLayout); rrenkert@361: rrenkert@361: setLayout(topLayout); rrenkert@361: aheinecke@516: mProgress.setWindowModality(Qt::WindowModal); aheinecke@516: mProgress.setCancelButton(0); aheinecke@516: mProgress.setRange(0,0); aheinecke@516: mProgress.setMinimumDuration(0); aheinecke@516: rrenkert@361: return; rrenkert@361: } rrenkert@361: rrenkert@361: void CreateInstallerDialog::openCertificateSelect() rrenkert@361: { aheinecke@515: QSettings settings; rrenkert@361: QString certFile = QFileDialog::getOpenFileName( aheinecke@515: this, tr("Select certificate"), aheinecke@515: mCertFile->text().isEmpty() ? QDir::homePath() : mCertFile->text(), aheinecke@515: "*.pem *.der *.crt"); aheinecke@515: settings.setValue("CodeSignCert", certFile); rrenkert@361: mCertFile->setText(certFile); rrenkert@361: } rrenkert@361: aheinecke@515: void CreateInstallerDialog::openFolderSelect() rrenkert@361: { aheinecke@515: QSettings settings; aheinecke@515: QString archiveFolder = QFileDialog::getExistingDirectory( aheinecke@515: this, tr("Select binary folder"), aheinecke@515: mBinaryFolder->text().isEmpty() ? QDir::homePath() : mBinaryFolder->text()); aheinecke@515: mBinaryFolder->setText(archiveFolder); aheinecke@515: settings.setValue("LastBinaryFolder", archiveFolder); rrenkert@361: } rrenkert@361: rrenkert@361: void CreateInstallerDialog::openSaveLocation() rrenkert@361: { aheinecke@515: QSettings settings; rrenkert@361: QString saveFile = QFileDialog::getExistingDirectory( aheinecke@515: this, tr("Select target location"), aheinecke@515: mSaveFile->text().isEmpty() ? QDir::homePath() : mSaveFile->text()); rrenkert@361: mSaveFile->setText(saveFile); aheinecke@515: settings.setValue("LastBinOutputFolder", saveFile); aheinecke@515: } aheinecke@515: aheinecke@515: void CreateInstallerDialog::showErrorMessage(const QString &msg) aheinecke@515: { aheinecke@515: QMessageBox::warning(this, tr("Error!"), msg); rrenkert@361: } rrenkert@361: aheinecke@516: void CreateInstallerDialog::processFinished(int exitCode, QProcess::ExitStatus exitStatus) aheinecke@516: { aheinecke@571: if (mCurrentWorkingDir) { aheinecke@571: delete mCurrentWorkingDir; aheinecke@571: mCurrentWorkingDir = NULL; aheinecke@571: } aheinecke@526: FinishedDialog *fin = new FinishedDialog(0, tr("Created installer in %1.") aheinecke@542: .arg(mSaveFile->text()), mNSISProc.readAll(), false); aheinecke@518: qDebug() << "Finished: " << mNSISProc.readAll(); aheinecke@571: mProgress.setLabelText(tr("Signing installer package...")); aheinecke@571: if (!signFile(mInstallerPath)) { aheinecke@571: showErrorMessage(tr("Failed to sign installer package.")); aheinecke@571: QFile::remove(mInstallerPath); aheinecke@571: } aheinecke@516: mProgress.cancel(); aheinecke@526: fin->show(); aheinecke@526: close(); aheinecke@516: } aheinecke@516: aheinecke@516: void CreateInstallerDialog::processError(QProcess::ProcessError error) aheinecke@516: { aheinecke@571: if (mCurrentWorkingDir) { aheinecke@571: delete mCurrentWorkingDir; aheinecke@571: mCurrentWorkingDir = NULL; aheinecke@571: } aheinecke@518: qDebug() << "Error: " << mNSISProc.readAll(); aheinecke@516: mProgress.cancel(); aheinecke@516: } aheinecke@516: rrenkert@361: void CreateInstallerDialog::createInstaller() rrenkert@361: { aheinecke@571: mProgress.setLabelText(tr("Creating installer package...")); aheinecke@515: QDir binDir(mBinaryFolder->text()); aheinecke@516: QDir outDir(mSaveFile->text()); aheinecke@516: if (mBinaryFolder->text().isEmpty() || !binDir.exists()) { aheinecke@515: showErrorMessage(tr("Please select an existing input folder.")); aheinecke@516: return; aheinecke@515: } aheinecke@516: if (mCertFile->text().isEmpty()) { aheinecke@516: showErrorMessage(tr("Please select a codesigning certificate.")); aheinecke@516: return; aheinecke@516: } aheinecke@516: if (mSaveFile->text().isEmpty() || !outDir.exists()) { aheinecke@516: showErrorMessage(tr("Please select a output folder.")); aheinecke@516: return; aheinecke@516: } aheinecke@516: QSettings options(binDir.filePath("meta.ini"), QSettings::IniFormat); aheinecke@516: options.sync(); aheinecke@516: QStringList keys = options.allKeys(); aheinecke@516: if (options.status() != QSettings::NoError || keys.size() < 1) { aheinecke@516: showErrorMessage(tr("Folder %1 does not appear to contain a meta.ini") aheinecke@516: .arg(binDir.path())); aheinecke@516: return; aheinecke@516: } andre@761: /* Sign the linux installer */ andre@761: QDir linuxDir(binDir.path() + "/linux"); andre@761: if (!linuxDir.exists()) { andre@781: showErrorMessage(tr("Failed to find the directory for linux binaries: %1") andre@761: .arg(linuxDir.path())); andre@761: return; andre@761: } andre@761: QStringList nameFilter; andre@761: nameFilter << "*.sh"; andre@761: QStringList candidates = linuxDir.entryList(nameFilter, QDir::Files | QDir::Readable); andre@761: if (candidates.isEmpty()) { andre@781: showErrorMessage(tr("Failed to find a readable *.sh file in: %1") andre@761: .arg(linuxDir.path())); andre@761: return; andre@761: } aheinecke@516: andre@885: foreach (const QString& candidate, candidates) { andre@885: mProgress.setLabelText(tr("Signing Linux package...")); andre@885: mProgress.cancel(); aheinecke@571: andre@885: bool x86arch; andre@885: if (candidate.endsWith("-i386.sh")) { andre@885: x86arch = true; andre@885: } else if (candidate.endsWith("-amd64.sh")) { andre@885: x86arch = false; andre@885: } else { andre@885: qDebug() << "Could not detrmine architecture of " << candidate; andre@885: qDebug() << "Skipping."; andre@886: continue; andre@885: } andre@885: andre@885: QString outFileName = options.value("setupname", "TrustBridge-default.exe" andre@885: ).toString().replace(".exe", andre@889: x86arch ? "-i386.sh" : "-64bit.sh").arg(QString()); andre@885: andre@885: if (!appendTextSignatureToFile(linuxDir.path() + "/" + candidate, andre@885: outDir.path() + "/" + outFileName)) { andre@885: showErrorMessage(tr("Failed to sign linux package: %1").arg(candidate)); andre@885: mProgress.close(); andre@885: return; andre@885: } andre@761: } andre@761: andre@761: /* The Windows installer */ andre@761: andre@761: mCurrentWorkingDir = codesignBinaries(binDir.path() + "/windows"); andre@761: andre@761: if (!mCurrentWorkingDir) { aheinecke@571: /* Error messages should have been shown by the codesign function */ andre@761: mProgress.close(); aheinecke@571: return; aheinecke@571: } aheinecke@571: aheinecke@571: mProgress.setLabelText(tr("Creating NSIS package...")); aheinecke@571: aheinecke@516: /* Copy windows directory contents to tmpdir */ aheinecke@516: QStringList arguments; aheinecke@516: mNSISProc.setProgram("makensis"); aheinecke@518: mNSISProc.setProcessChannelMode(QProcess::MergedChannels); aheinecke@516: mNSISProc.setWorkingDirectory(outDir.path()); aheinecke@537: #ifdef Q_OS_WIN andre@761: arguments << QString::fromLatin1("/Dfiles_dir=") + mCurrentWorkingDir->path().replace("/", "\\"); aheinecke@541: arguments << "/Dpath_sep=\\"; aheinecke@537: foreach (const QString &key, keys) { aheinecke@542: QString value = options.value(key, QString()).toString(); aheinecke@542: if (key == "setupname") { aheinecke@542: value = value.arg(outDir.path().replace("/", "\\") + "\\"); aheinecke@571: mInstallerPath = value; aheinecke@542: } aheinecke@542: arguments << QString::fromLatin1("/D%1=%2").arg(key, value); aheinecke@537: } andre@864: arguments << QString(binDir.path() + "/trustbridge.nsi").replace("/", "\\"); aheinecke@537: #else andre@761: arguments << QString::fromLatin1("-Dfiles_dir=") + mCurrentWorkingDir->path(); aheinecke@541: arguments << "-Dpath_sep=/"; aheinecke@516: foreach (const QString &key, keys) { aheinecke@542: QString value = options.value(key, QString()).toString(); aheinecke@542: if (key == "setupname") { aheinecke@542: value = value.arg(outDir.path() + "/"); aheinecke@571: mInstallerPath = value; aheinecke@542: } aheinecke@542: arguments << QString::fromLatin1("-D%1=%2").arg(key, value); aheinecke@516: } andre@864: arguments << binDir.path() + "/trustbridge.nsi"; aheinecke@537: #endif aheinecke@516: andre@864: andre@864: QFileInfo nsiFile (binDir.path() + "/trustbridge.nsi"); andre@864: if (!nsiFile.exists()) { andre@864: showErrorMessage(tr("Failed to find installer script at: %1 ").arg(nsiFile.filePath())); andre@864: } aheinecke@516: aheinecke@518: qDebug() << "Starting makensis with arguments: " << arguments; aheinecke@518: mNSISProc.setArguments(arguments); aheinecke@516: mNSISProc.start(); aheinecke@516: mProgress.show(); aheinecke@516: aheinecke@516: if (!mNSISProc.waitForStarted() || aheinecke@516: mNSISProc.state() == QProcess::NotRunning) { aheinecke@516: showErrorMessage(tr("Failed to start makensis.\n" aheinecke@516: "Please ensure that makensis is installed and in your PATH variable.")); aheinecke@516: } rrenkert@361: } aheinecke@526: aheinecke@571: bool CreateInstallerDialog::signFile(QString filePath) { aheinecke@571: QProcess signProc; aheinecke@571: signProc.setProcessChannelMode(QProcess::MergedChannels); aheinecke@571: signProc.setProgram("osslsigncode"); aheinecke@571: QStringList arguments; aheinecke@571: aheinecke@571: QSettings mySettings; aheinecke@571: aheinecke@571: QString publisher = mySettings.value("sign_publisher", SIGN_PUBLISHER).toString(); aheinecke@571: QString url = mySettings.value("sign_url", SIGN_URL).toString(); aheinecke@571: QString hash = mySettings.value("sign_hash", SIGN_HASH).toString(); aheinecke@571: aheinecke@571: arguments << "sign" << "-certs" << mCertFile->text() << "-key" aheinecke@571: << mCertFile->text() << "-n" << publisher << "-i" << aheinecke@571: url << "-h" << hash << "-in" << filePath << "-out" << filePath + ".signed"; aheinecke@571: aheinecke@571: qDebug() << "Starting osslsigncode with arguments: " << arguments; aheinecke@571: signProc.setArguments(arguments); aheinecke@571: signProc.start(); aheinecke@571: aheinecke@571: if (!signProc.waitForFinished(30000)) { aheinecke@571: qDebug() << "Signing takes longer then 30 seconds. Aborting."; aheinecke@571: return false; aheinecke@571: } aheinecke@571: aheinecke@571: if (signProc.exitStatus() != QProcess::NormalExit || aheinecke@571: signProc.exitCode() != 0) { aheinecke@571: qDebug() << "Error process returned: " << signProc.exitCode(); aheinecke@571: qDebug() << "Output: " << signProc.readAllStandardOutput(); aheinecke@571: return false; aheinecke@571: } aheinecke@571: aheinecke@571: if (!QFile::remove(filePath)) { aheinecke@571: qDebug() << "Failed to remove file."; aheinecke@571: return false; aheinecke@571: } aheinecke@571: if (!QFile::copy(filePath + ".signed", filePath)) { aheinecke@571: qDebug() << "Failed to copy signed file in place."; aheinecke@571: return false; aheinecke@571: } aheinecke@571: if (!QFile::remove(filePath + ".signed")) { aheinecke@571: qDebug() << "Failed to remove signed."; aheinecke@571: return false; aheinecke@571: } aheinecke@571: return true; aheinecke@571: } aheinecke@571: aheinecke@571: aheinecke@571: QTemporaryDir *CreateInstallerDialog::codesignBinaries(const QDir& sourceDir) { aheinecke@571: QTemporaryDir* target = new QTemporaryDir(); aheinecke@571: /* Copy all files from the source dir to a temporary location */ aheinecke@571: mProgress.setLabelText(tr("Signing binaries...")); aheinecke@571: aheinecke@571: mProgress.show(); aheinecke@571: foreach (const QFileInfo& entry, sourceDir.entryInfoList()) { aheinecke@571: QString targetPath = target->path() + QString::fromLatin1("/") + entry.fileName(); aheinecke@571: if (entry.fileName() == "." || entry.fileName() == "..") { aheinecke@571: continue; aheinecke@571: } aheinecke@571: if (!QFile::copy(entry.absoluteFilePath(), targetPath)) { aheinecke@571: qDebug() << "Failed to copy: " << entry.absoluteFilePath() << " to: " << targetPath; aheinecke@571: showErrorMessage(tr("Failed to copy binaries to temporary location.")); aheinecke@571: mProgress.cancel(); aheinecke@571: return NULL; aheinecke@571: } aheinecke@571: if (entry.suffix() == "exe") { aheinecke@571: if (!signFile(targetPath)) { aheinecke@571: showErrorMessage(tr("Failed to sign binaries with osslsigncode.\n" aheinecke@571: "Please check that %1 is a valid code signing certificate and that" aheinecke@571: "osslsigncode can be found in the PATH.").arg(mCertFile->text())); aheinecke@571: mProgress.cancel(); aheinecke@571: return NULL; aheinecke@571: } aheinecke@571: } aheinecke@571: } aheinecke@571: mProgress.cancel(); aheinecke@571: return target; aheinecke@571: } aheinecke@571: andre@761: bool CreateInstallerDialog::appendTextSignatureToFile(const QString& input, andre@761: const QString& output) { andre@761: QFile inFile(input); andre@761: pk_context pk; andre@761: andre@761: pk_init(&pk); andre@761: int ret = pk_parse_keyfile(&pk, mCertFile->text().toLocal8Bit().constData(), ""); andre@761: andre@761: if (ret != 0) { andre@761: showErrorMessage(tr("Failed to load certificate: %1") andre@761: .arg(getPolarSSLErrorMsg(ret))); andre@761: pk_free(&pk); andre@761: return false; andre@761: } andre@761: andre@761: /* Check that it is a 3072 bit RSA key as specified */ andre@761: if (!pk.pk_info || pk_get_size(&pk) != 3072 || andre@761: pk.pk_info->type != POLARSSL_PK_RSA) { andre@761: qDebug() << pk.pk_info->type << "type"; andre@761: qDebug() << POLARSSL_PK_RSA << "rsa"; andre@761: qDebug() << "size " << pk_get_size(&pk); andre@761: showErrorMessage(tr("Only 3072 bit RSA keys are supported by the current format.")); andre@761: pk_free(&pk); andre@761: return false; andre@761: } andre@761: andre@761: if (!inFile.open(QIODevice::ReadOnly)) { andre@761: showErrorMessage(tr("Failed to open input file: %1").arg(inFile.fileName())); andre@761: pk_free(&pk); andre@761: return false; andre@761: } andre@761: andre@761: const QByteArray inputContent = inFile.readAll(); // Memory is cheap :) andre@761: inFile.close(); andre@761: andre@761: if (inputContent.isEmpty()) { andre@761: showErrorMessage(tr("Failed to read input file: %1").arg(inFile.fileName())); andre@761: pk_free(&pk); andre@761: return false; andre@761: } andre@761: andre@761: const QByteArray signature = rsaSignSHA256Hash(sha256sum(inputContent), &pk); andre@761: andre@761: pk_free(&pk); andre@761: if (signature.size() != 3072 / 8) { andre@761: qDebug() << "Signature creation returned signature of invalid size."; andre@761: return false; andre@761: } andre@761: andre@761: QSaveFile outFile(output); andre@761: outFile.open(QIODevice::WriteOnly); andre@761: outFile.write(inputContent); andre@761: outFile.write("\r\nS:"); andre@761: outFile.write(signature.toBase64()); andre@867: outFile.write("\n"); andre@761: andre@761: return outFile.commit(); andre@761: } andre@761: aheinecke@526: FinishedDialog::FinishedDialog(QDialog *parent, aheinecke@526: QString msg, QString details, bool isErr): aheinecke@526: QDialog(parent) aheinecke@526: { aheinecke@526: QVBoxLayout *topLayout = new QVBoxLayout; aheinecke@526: QHBoxLayout *buttonLayout = new QHBoxLayout; aheinecke@526: QLabel *msgLabel = new QLabel; aheinecke@526: QTextEdit *detailsWindow = new QTextEdit; aheinecke@526: aheinecke@526: detailsWindow->insertPlainText(details); aheinecke@526: detailsWindow->setReadOnly(true); aheinecke@526: detailsWindow->hide(); aheinecke@526: aheinecke@526: if (!isErr) { andre@772: setWindowTitle(tr("Successfully created installation package")); aheinecke@526: msgLabel->setPixmap(QApplication::style()->standardIcon( aheinecke@526: QStyle::SP_MessageBoxInformation).pixmap(16, 16)); aheinecke@526: } else { aheinecke@526: setWindowTitle(tr("Error!")); aheinecke@526: msgLabel->setPixmap(QApplication::style()->standardIcon( aheinecke@526: QStyle::SP_MessageBoxCritical).pixmap(16, 16)); aheinecke@526: } aheinecke@526: msgLabel->setText(msg); aheinecke@526: aheinecke@526: topLayout->addWidget(msgLabel); aheinecke@526: topLayout->addWidget(detailsWindow); aheinecke@526: QPushButton *detailsBtn = new QPushButton(tr("Details")); aheinecke@526: connect(detailsBtn, SIGNAL(clicked()), detailsWindow, SLOT(show())); aheinecke@526: buttonLayout->addWidget(detailsBtn); aheinecke@526: aheinecke@526: QPushButton *okBtn = new QPushButton(tr("OK")); aheinecke@526: okBtn->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogOkButton)); aheinecke@526: connect(okBtn, SIGNAL(clicked()), this, SLOT(close())); aheinecke@526: buttonLayout->insertStretch(0, 100); aheinecke@526: buttonLayout->addWidget(okBtn); aheinecke@526: aheinecke@526: topLayout->addLayout(buttonLayout); aheinecke@526: setLayout(topLayout); aheinecke@526: } aheinecke@526: