diff --git a/src/qt/introdialog.cpp b/src/qt/introdialog.cpp new file mode 100644 index 0000000..9146365 --- /dev/null +++ b/src/qt/introdialog.cpp @@ -0,0 +1,205 @@ +#include "introdialog.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +IntroDialog::IntroDialog(QWidget *parent) : + QDialog(parent) +{ + setWindowTitle("Triangles"); + setMinimumWidth(520); + + // Match existing Triangles dark theme + setStyleSheet( + "QDialog { background-color: #000; color: #f26522; }" + "QLabel { color: #f26522; }" + "QRadioButton { color: #f26522; }" + "QRadioButton::indicator { border: 1px solid #f26522; background-color: #000; width: 12px; height: 12px; border-radius: 7px; }" + "QRadioButton::indicator:checked { background-color: #f26522; }" + "QLineEdit { background-color: #1c1c1c; border: 1px solid #f26522; color: #f26522; padding: 4px; }" + "QPushButton { background-color: #000; color: #f26522; border: 1px solid #f26522; padding: 4px 16px; min-height: 20px; }" + "QPushButton:hover { background-color: #61280E; }" + ); + + defaultDataDir = QString::fromStdString(GetDefaultDataDir().string()); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setContentsMargins(20, 20, 20, 20); + mainLayout->setSpacing(12); + + // Welcome header + QLabel *welcomeLabel = new QLabel(tr("Welcome to Triangles!")); + welcomeLabel->setStyleSheet("font-size: 16px; font-weight: bold; color: #f26522;"); + mainLayout->addWidget(welcomeLabel); + + // Description + QLabel *descLabel = new QLabel(tr( + "Triangles will store its blockchain data, wallet, and configuration in a data directory. " + "You can use the default directory or choose a custom location. " + "The data directory requires several hundred MB of free space." + )); + descLabel->setWordWrap(true); + mainLayout->addWidget(descLabel); + + mainLayout->addSpacing(8); + + // Default directory radio + defaultRadio = new QRadioButton(tr("Use the default data directory")); + defaultRadio->setChecked(true); + mainLayout->addWidget(defaultRadio); + + // Show default path + QLabel *defaultPathLabel = new QLabel(defaultDataDir); + defaultPathLabel->setStyleSheet("color: #999; margin-left: 24px; font-size: 11px;"); + mainLayout->addWidget(defaultPathLabel); + + mainLayout->addSpacing(4); + + // Custom directory radio + customRadio = new QRadioButton(tr("Use a custom data directory:")); + mainLayout->addWidget(customRadio); + + // Path input + browse button + QHBoxLayout *pathLayout = new QHBoxLayout(); + pathLayout->setContentsMargins(24, 0, 0, 0); + + pathEdit = new QLineEdit(defaultDataDir); + pathEdit->setEnabled(false); + pathLayout->addWidget(pathEdit); + + browseButton = new QPushButton(tr("Browse...")); + browseButton->setEnabled(false); + pathLayout->addWidget(browseButton); + + mainLayout->addLayout(pathLayout); + + // Free space label + freeSpaceLabel = new QLabel(); + freeSpaceLabel->setStyleSheet("color: #999; margin-left: 24px; font-size: 11px;"); + mainLayout->addWidget(freeSpaceLabel); + + mainLayout->addStretch(1); + + // OK / Cancel buttons + QHBoxLayout *buttonLayout = new QHBoxLayout(); + buttonLayout->addStretch(1); + + QPushButton *okButton = new QPushButton(tr("OK")); + QPushButton *cancelButton = new QPushButton(tr("Cancel")); + + buttonLayout->addWidget(okButton); + buttonLayout->addWidget(cancelButton); + mainLayout->addLayout(buttonLayout); + + // Connections + connect(defaultRadio, SIGNAL(toggled(bool)), this, SLOT(on_defaultRadio_toggled(bool))); + connect(browseButton, SIGNAL(clicked()), this, SLOT(on_browseButton_clicked())); + connect(pathEdit, SIGNAL(textChanged(QString)), this, SLOT(updateFreeSpace())); + connect(okButton, SIGNAL(clicked()), this, SLOT(accept())); + connect(cancelButton, SIGNAL(clicked()), this, SLOT(reject())); + + updateFreeSpace(); +} + +QString IntroDialog::getDataDirectory() const +{ + if (defaultRadio->isChecked()) + return defaultDataDir; + return pathEdit->text(); +} + +void IntroDialog::setDataDirectory(const QString &dir) +{ + pathEdit->setText(dir); + if (dir == defaultDataDir) { + defaultRadio->setChecked(true); + } else { + customRadio->setChecked(true); + } +} + +void IntroDialog::on_browseButton_clicked() +{ + QString dir = QFileDialog::getExistingDirectory(this, tr("Choose data directory"), pathEdit->text()); + if (!dir.isEmpty()) + pathEdit->setText(dir); +} + +void IntroDialog::on_defaultRadio_toggled(bool checked) +{ + pathEdit->setEnabled(!checked); + browseButton->setEnabled(!checked); + if (checked) + pathEdit->setText(defaultDataDir); + updateFreeSpace(); +} + +void IntroDialog::updateFreeSpace() +{ + QString path = getDataDirectory(); + boost::filesystem::path fsPath(path.toStdString()); + + // Walk up to find an existing parent + try { + while (!fsPath.empty() && !boost::filesystem::exists(fsPath)) + fsPath = fsPath.parent_path(); + + if (!fsPath.empty()) { + boost::filesystem::space_info si = boost::filesystem::space(fsPath); + double freeGB = (double)si.available / (1024.0 * 1024.0 * 1024.0); + freeSpaceLabel->setText(tr("Free space: %1 GB").arg(QString::number(freeGB, 'f', 2))); + } else { + freeSpaceLabel->setText(tr("Cannot determine free space")); + } + } catch (const boost::filesystem::filesystem_error &) { + freeSpaceLabel->setText(tr("Cannot determine free space")); + } +} + +bool IntroDialog::pickDataDirectory() +{ + namespace fs = boost::filesystem; + + QSettings settings; + // If -datadir was passed on the command line, skip the dialog entirely + if (mapArgs.count("-datadir")) + return true; + + QString dataDir = settings.value("strDataDir", "").toString(); + + if (dataDir.isEmpty()) { + // First run - show the dialog + IntroDialog dlg; + if (dlg.exec() != QDialog::Accepted) + return false; + + dataDir = dlg.getDataDirectory(); + settings.setValue("strDataDir", dataDir); + } + + // If the saved path is the default, don't set -datadir (let normal defaults work) + QString defaultDir = QString::fromStdString(GetDefaultDataDir().string()); + if (dataDir != defaultDir) { + mapArgs["-datadir"] = dataDir.toStdString(); + } + + // Ensure the directory exists + try { + fs::create_directories(fs::path(dataDir.toStdString())); + } catch (const fs::filesystem_error &) { + QMessageBox::critical(0, "Triangles", + QString("Error: Could not create data directory \"%1\".").arg(dataDir)); + return false; + } + + return true; +} diff --git a/src/qt/introdialog.h b/src/qt/introdialog.h new file mode 100644 index 0000000..672ddcd --- /dev/null +++ b/src/qt/introdialog.h @@ -0,0 +1,42 @@ +#ifndef INTRODIALOG_H +#define INTRODIALOG_H + +#include +#include +#include +#include +#include + +/** Data directory selection dialog shown on first run. */ +class IntroDialog : public QDialog +{ + Q_OBJECT + +public: + explicit IntroDialog(QWidget *parent = 0); + + QString getDataDirectory() const; + void setDataDirectory(const QString &dir); + + /** + * Check settings or show the dialog to choose data directory. + * Returns true if a directory was selected, false if the user cancelled. + * Sets mapArgs["-datadir"] if a non-default directory was chosen. + */ + static bool pickDataDirectory(); + +private slots: + void on_browseButton_clicked(); + void on_defaultRadio_toggled(bool checked); + void updateFreeSpace(); + +private: + QRadioButton *defaultRadio; + QRadioButton *customRadio; + QLineEdit *pathEdit; + QPushButton *browseButton; + QLabel *freeSpaceLabel; + QString defaultDataDir; +}; + +#endif // INTRODIALOG_H diff --git a/src/qt/triangles.cpp b/src/qt/triangles.cpp index c081caf..23a6c88 100644 --- a/src/qt/triangles.cpp +++ b/src/qt/triangles.cpp @@ -8,6 +8,7 @@ #include "guiutil.h" #include "guiconstants.h" +#include "introdialog.h" #include "init.h" #include "ui_interface.h" #include "qtipcserver.h" @@ -131,6 +132,10 @@ int main(int argc, char *argv[]) // Command-line options take precedence: ParseParameters(argc, argv); + // Show data directory selection dialog on first run (unless -datadir was passed) + if (!IntroDialog::pickDataDirectory()) + return 0; + // ... then triangles.conf: if (!boost::filesystem::is_directory(GetDataDir(false))) { diff --git a/triangles-qt.pro b/triangles-qt.pro index 5465886..40a8874 100644 --- a/triangles-qt.pro +++ b/triangles-qt.pro @@ -202,6 +202,7 @@ HEADERS += src/qt/trianglesgui.h \ src/qt/sendcoinsdialog.h \ src/qt/addressbookpage.h \ src/qt/aboutdialog.h \ + src/qt/introdialog.h \ src/qt/editaddressdialog.h \ src/qt/trianglesaddressvalidator.h \ src/alert.h \ @@ -217,6 +218,7 @@ HEADERS += src/qt/trianglesgui.h \ src/kernel.h \ src/net_bootstrap.h \ src/tor/onion_v3.h \ + src/tor/tor_process.h \ src/tor/tor_crypto_compat.h \ src/scrypt.h \ src/pbkdf2.h \ @@ -315,6 +317,7 @@ SOURCES += src/qt/triangles.cpp src/qt/trianglesgui.cpp \ src/qt/coincontroltreewidget.cpp \ src/qt/addressbookpage.cpp \ src/qt/aboutdialog.cpp \ + src/qt/introdialog.cpp \ src/qt/editaddressdialog.cpp \ src/qt/trianglesaddressvalidator.cpp \ # Old embedded Tor v2 client removed - incompatible with OpenSSL 3.x @@ -386,6 +389,7 @@ SOURCES += src/qt/triangles.cpp src/qt/trianglesgui.cpp \ src/kernel.cpp \ src/net_bootstrap.cpp \ src/tor/onion_v3.cpp \ + src/tor/tor_process.cpp \ src/scrypt-arm.S \ src/scrypt-x86.S \ src/scrypt-x86_64.S \