Add data directory selection dialog on first run
Shows an intro dialog on first launch letting users choose where to store blockchain data. Saves the choice in QSettings so it only appears once. Styled to match the existing Triangles dark theme. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
#include "introdialog.h"
|
||||
#include "util.h"
|
||||
|
||||
#include <QSettings>
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QFileDialog>
|
||||
#include <QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef INTRODIALOG_H
|
||||
#define INTRODIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QRadioButton>
|
||||
#include <QPushButton>
|
||||
|
||||
/** 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
|
||||
@@ -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)))
|
||||
{
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user