#include #include #include #include #include #include #include #include #include #include #include #include #include "Application.h" #include "MainWindow.h" #include "RemoteNetwork.h" #include "Settings.h" #include "version.h" #include "sqlitedb.h" class DB4SProxyStyle final : public QProxyStyle { public: DB4SProxyStyle(int toolBarIconSize, qreal dpi, QStyle *style) : QProxyStyle(style), m_toolBarIconSize(static_cast(toolBarIconSize * dpi / 96)) { #ifdef Q_OS_WIN m_smallIconSize = static_cast(11 * dpi / 96); QStyleOption so(QStyleOption::Version, QStyleOption::SO_MenuItem); m_menuItemIconSize = style->pixelMetric(PM_SmallIconSize, &so); #endif } int pixelMetric(QStyle::PixelMetric metric, const QStyleOption *option = nullptr, const QWidget *widget = nullptr) const override { if (metric == PM_ToolBarIconSize) { return m_toolBarIconSize; } #ifdef Q_OS_WIN else if (metric == PM_SmallIconSize) { // set maximum icon size for SmallIcon // Set default MenuIcon size if (option && option->type == QStyleOption::SO_MenuItem) { return m_menuItemIconSize; } // if we don't return size before QDockWidgets are created, // any of the widgets with custom stylesheet are rendered with huge float,close buttons // see void TableBrowserDock::setFocusStyle return m_smallIconSize; } #endif return QProxyStyle::pixelMetric(metric, option, widget); } private: int m_toolBarIconSize; #ifdef Q_OS_WIN int m_menuItemIconSize; int m_smallIconSize; #endif }; void printArgument(const QString& argument, const QString& description) { const int fieldWidth = 20; // Format the usage message so translators do not have to do it themselves if(argument.length() > fieldWidth) { qWarning() << qPrintable(QString(" %1").arg(argument, -fieldWidth)); qWarning() << qPrintable(QString(" %1%2").arg("", -fieldWidth).arg(description)); } else { qWarning() << qPrintable(QString(" %1%2").arg(argument, -fieldWidth).arg(description)); } } Application::Application(int& argc, char** argv) : QApplication(argc, argv) { // Get 'DB4S_SETTINGS_FILE' environment variable const auto env = qgetenv("DB4S_SETTINGS_FILE"); // If 'DB4S_SETTINGS_FILE' environment variable exists if(!env.isEmpty()) Settings::setUserSettingsFile(env); for(int i=1;iload("sqlb_" + name, QCoreApplication::applicationDirPath() + "/translations"); // If failed then try to load .qm file from resources if (ok == false) { ok = m_translatorApp->load("sqlb_" + name, ":/translations"); } if (ok == true) { Settings::setValue("General", "language", name); installTranslator(m_translatorApp); // The application translation file has been found and loaded. // And now try to load a Qt translation file. // Search path: // 1) standard Qt translations directory; // 2) the application translations directory. m_translatorQt = new QTranslator(this); ok = m_translatorQt->load("qt_" + name, QLibraryInfo::location(QLibraryInfo::TranslationsPath)); if (ok == false) ok = m_translatorQt->load("qt_" + name, "translations"); if (ok == true) installTranslator(m_translatorQt); } else { // Set the correct locale so that the program won't erroneously detect // a language change when the user toggles settings for the first time. // (it also prevents the program from always looking for a translation on launch) Settings::setValue("General", "language", "en_US"); // Don't install a translator for Qt texts if no translator for DB4S texts could be loaded m_translatorQt = nullptr; } // On Windows, add the plugins subdirectory to the list of library directories. We need this // for Qt to search for more image format plugins. #ifdef Q_WS_WIN QApplication::addLibraryPath(QApplication::applicationDirPath() + "/plugins"); #endif // Work around a bug in QNetworkAccessManager which sporadically causes high pings for Wifi connections // See https://bugreports.qt.io/browse/QTBUG-40332 qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(INT_MAX)); // Remember default font size Settings::rememberDefaultFontSize(font().pointSize()); // Parse command line QString fileToOpen; std::vector tableToBrowse; std::vector csvToImport; QStringList sqlToExecute; bool readOnly = false; m_showMainWindow = true; for(int i=1;i|<%5>|%6]\n").arg( tr("Usage"), QFileInfo(argv[0]).fileName(), tr("options"), tr("database"), tr("project"), tr("csv-file"))); qWarning() << qPrintable(tr("Possible command line arguments:")); printArgument(QString("-h, --help"), tr("Show command line options")); printArgument(QString("-q, --quit"), tr("Exit application after running scripts")); printArgument(QString("-s, --sql <%1>").arg(tr("file")), tr("Execute this SQL file after opening the DB")); printArgument(QString("--import-csv <%1>").arg(tr("file")), tr("Import this CSV file into the passed DB or into a new DB")); printArgument(QString("-t, --table <%1>").arg(tr("table")), tr("Browse this table, or use it as target of a data import")); printArgument(QString("-R, --read-only"), tr("Open database in read-only mode")); printArgument(QString("-S, --settings <%1>").arg(tr("settings_file")), tr("Run application based on this settings file")); printArgument(QString("-o, --option <%1>/<%2>=<%3>").arg(tr("group"), tr("settings"), tr("value")), tr("Run application with this setting temporarily set to value")); printArgument(QString("-O, --save-option <%1>/<%2>=<%3>").arg(tr("group"), tr("settings"), tr("value")), tr("Run application saving this value for this setting")); printArgument(QString("-v, --version"), tr("Display the current version")); printArgument(QString("<%1>").arg(tr("database")), tr("Open this SQLite database")); printArgument(QString("<%1>").arg(tr("project")), tr("Open this project file (*.sqbpro)")); printArgument(QString("<%1>").arg(tr("csv-file")), tr("Import this CSV file into an in-memory database")); m_showMainWindow = false; } else if(arguments().at(i) == "-v" || arguments().at(i) == "--version") { qWarning() << qPrintable(versionInformation()); m_showMainWindow = false; } else if(arguments().at(i) == "-s" || arguments().at(i) == "--sql") { // Run SQL file: If file exists add it to list of scripts to execute if(++i >= arguments().size()) qWarning() << qPrintable(tr("The %1 option requires an argument").arg("-s/--sql")); else if(!QFile::exists(arguments().at(i))) qWarning() << qPrintable(tr("The file %1 does not exist").arg(arguments().at(i))); else sqlToExecute.append(arguments().at(i)); } else if(arguments().at(i) == "-t" || arguments().at(i) == "--table") { if(++i >= arguments().size()) qWarning() << qPrintable(tr("The %1 option requires an argument").arg("-t/--table")); else tableToBrowse.push_back(arguments().at(i)); } else if(arguments().at(i) == "--import-csv") { if(++i >= arguments().size()) qWarning() << qPrintable(tr("The %1 option requires an argument").arg("--import-csv")); else if(!QFile::exists(arguments().at(i))) qWarning() << qPrintable(tr("The file %1 does not exist").arg(arguments().at(i))); else csvToImport.push_back(arguments().at(i)); } else if(arguments().at(i) == "-q" || arguments().at(i) == "--quit") { m_showMainWindow = false; } else if(arguments().at(i) == "-R" || arguments().at(i) == "--read-only") { readOnly = true; } else if(arguments().at(i) == "-S" || arguments().at(i) == "--settings") { // This option has already been handled above // For here, only print the error when no parameter value is given if(++i >= arguments().size()) qWarning() << qPrintable(tr("The -S/--settings option requires an argument. The option is ignored.")); } else if(arguments().at(i) == "-o" || arguments().at(i) == "--option" || arguments().at(i) == "-O" || arguments().at(i) == "--save-option") { const QString optionWarning = tr("The -o/--option and -O/--save-option options require an argument in the form group/setting=value"); bool saveToDisk = arguments().at(i) == "-O" || arguments().at(i) == "--save-option"; if(++i >= arguments().size()) qWarning() << qPrintable(optionWarning); else { QStringList option = arguments().at(i).split("="); if (option.size() != 2) qWarning() << qPrintable(optionWarning); else { QStringList setting = option.at(0).split("/"); if (setting.size() != 2) qWarning() << qPrintable(optionWarning); else { QVariant value; // Split string lists. This assumes they are always named "*list" if (setting.at(1).endsWith("list", Qt::CaseInsensitive)) value = option.at(1).split(","); else value = option.at(1); Settings::setValue(setting.at(0).toStdString(), setting.at(1).toStdString(), value, saveToDisk); } } } } else { // Other: Check if it's a valid file name if(QFile::exists(arguments().at(i))) fileToOpen = arguments().at(i); else qWarning() << qPrintable(tr("Invalid option/non-existent file: %1").arg(arguments().at(i))); } } if(!m_showMainWindow) { m_mainWindow = nullptr; return; } // Set StyleProxy QScreen *screen = primaryScreen(); setStyle(new DB4SProxyStyle(18, screen != nullptr ? screen->logicalDotsPerInch() : 96, style())); // Skip db/project autoLoadLastDBFileAtStartup if we have file passed from the cli Settings::setValue("tmp", "fileWillBeOpenedFromCLI", !fileToOpen.isEmpty(), false); // Show main window m_mainWindow = new MainWindow(); m_mainWindow->show(); connect(this, &Application::lastWindowClosed, this, &Application::quit); // Open database if one was specified if(fileToOpen.size()) { if(m_mainWindow->fileOpen(fileToOpen, false, readOnly)) { // If database could be opened run the SQL scripts for(const QString& f : sqlToExecute) { QFile file(f); if(file.open(QIODevice::ReadOnly)) { m_mainWindow->getDb().executeMultiSQL(file.readAll(), false, true); file.close(); } } if(!sqlToExecute.isEmpty()) m_mainWindow->refresh(); // Jump to table if the -t/--table parameter was set for(const QString& t : tableToBrowse) m_mainWindow->switchToBrowseDataTab(sqlb::ObjectIdentifier("main", t.toStdString())); if(!tableToBrowse.empty()) m_mainWindow->refresh(); } } if(!csvToImport.empty()) { if(tableToBrowse.empty()) { m_mainWindow->importCSVfiles(csvToImport); } else { m_mainWindow->importCSVfiles(csvToImport, tableToBrowse.front()); } } } Application::~Application() { if(m_mainWindow) delete m_mainWindow; Settings::sync(); } bool Application::event(QEvent* event) { switch(event->type()) { case QEvent::FileOpen: m_mainWindow->fileOpen(static_cast(event)->file()); return true; default: return QApplication::event(event); } } QString Application::versionString() { // Nightly builds use a patch number of 99, and for these we include the build date in the version string. For // our release versions (which use any other patch number) we don't include the build date. This should avoid // confusion about what is more important, version number or build date, and about different build dates for // the same version. This also should help making release builds reproducible out of the box. #if PATCH_VERSION == 99 return QString("%1 (%2)").arg(APP_VERSION, __DATE__); #else return QString("%1").arg(APP_VERSION); #endif } QString Application::versionInformation() { QString sqlite_version, sqlcipher_version; DBBrowserDB::getSqliteVersion(sqlite_version, sqlcipher_version); if(sqlcipher_version.isNull()) sqlite_version = tr("SQLite Version ") + sqlite_version; else sqlite_version = tr("SQLCipher Version %1 (based on SQLite %2)").arg(sqlcipher_version, sqlite_version); return tr("DB Browser for SQLite Version %1.").arg(versionString() + "\n" + tr("Last commit hash when built: %1").arg(GIT_COMMIT_HASH) + "\n\n" + tr("Built for %1, running on %2").arg(QSysInfo::buildAbi(), QSysInfo::currentCpuArchitecture()) + "\n" + tr("Qt Version %1").arg(QT_VERSION_STR) + "\n" + sqlite_version ); } void Application::reloadSettings() { // Network settings RemoteNetwork::get().reloadSettings(); // Font settings QFont f = font(); f.setPointSize(Settings::getValue("General", "fontsize").toInt()); setFont(f); } // Functions for documenting the shortcuts in the user interface using native names static QString shortcutsTip(const QList& keys) { QString tip; if (!keys.isEmpty()) { tip = " ["; for (const auto& shortcut : keys) tip.append(shortcut.toString(QKeySequence::NativeText) + ", "); tip.chop(2); tip.append("]"); } return tip; } void addShortcutsTooltip(QAction* action, const QList& extraKeys) { if (!action->shortcuts().isEmpty() || !extraKeys.isEmpty()) action->setToolTip(action->toolTip() + shortcutsTip(action->shortcuts() + extraKeys)); }