RedPanda-CPP/RedPandaIDE/main.cpp

425 lines
14 KiB
C++

/*
* Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "mainwindow.h"
#include "settings.h"
#include "systemconsts.h"
#include "utils.h"
#include <QApplication>
#include <QDir>
#include <QTranslator>
#include <QStandardPaths>
#include <QMessageBox>
#include <QStringList>
#include <QAbstractNativeEventFilter>
#include <QDesktopWidget>
#include <QDir>
#include <QScreen>
#include "common.h"
#include "colorscheme.h"
#include "iconsmanager.h"
#include "autolinkmanager.h"
#include <qt_utils/charsetinfo.h>
#include "parser/parserutils.h"
#include "editorlist.h"
#include "widgets/choosethemedialog.h"
#include "thememanager.h"
#ifdef Q_OS_WIN
#include <QTemporaryFile>
#include <windows.h>
#include <psapi.h>
#include <QSharedMemory>
#include <QBuffer>
#include <winuser.h>
#include "widgets/cpudialog.h"
#endif
QString getSettingFilename(const QString& filepath, bool& firstRun);
#ifdef Q_OS_WIN
class WindowLogoutEventFilter : public QAbstractNativeEventFilter {
// QAbstractNativeEventFilter interface
public:
bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) override;
};
#define WM_USER_OPEN_FILE (WM_USER+1)
HWND prevAppInstance = NULL;
BOOL CALLBACK GetPreviousInstanceCallback(HWND hwnd, LPARAM param){
BOOL result = TRUE;
WCHAR buffer[4098];
HINSTANCE hWindowModule = (HINSTANCE)GetWindowLongPtr(hwnd,GWLP_HINSTANCE);
if (hWindowModule==0)
return result;
DWORD processID;
// Get the ID of the process that created this window
GetWindowThreadProcessId(hwnd,&processID);
if (processID==0)
return result;
// Get the process associated with the ID
HANDLE hWindowProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);
if (hWindowProcess == 0)
return result;
// Get its module filename
if (GetModuleFileNameExW(hWindowProcess, hWindowModule, buffer, sizeof(buffer)) == 0)
return TRUE;
CloseHandle(hWindowProcess); // not needed anymore
WCHAR * compareFilename=(WCHAR*)param;
QString s1=QString::fromWCharArray(compareFilename);
QString s2=QString::fromWCharArray(buffer);
//Is from the "same" application?
if (QString::compare(s1,s2,PATH_SENSITIVITY)==0) {
//found, stop EnumWindows loop
prevAppInstance = hwnd;
return FALSE;
}
return TRUE;
}
HWND getPreviousInstance() {
WCHAR buffer[4098];
//ShowMessage('ERROR_ALREADY_EXISTS');
// Store our own module filename
if (GetModuleFileNameW(GetModuleHandle(NULL), buffer, sizeof(buffer)) == 0)
return NULL;
// If that's the case, walk all top level windows and find the previous instance
// At this point, the program that created the mutex might not have created its MainForm yet
if (EnumWindows(GetPreviousInstanceCallback, LPARAM(buffer))==0) {
return prevAppInstance;
} else
return NULL;
}
bool WindowLogoutEventFilter::nativeEventFilter(const QByteArray & /*eventType*/, void *message, long *result){
MSG * pMsg = static_cast<MSG *>(message);
switch(pMsg->message) {
case WM_QUERYENDSESSION:
if (pMsg->lParam == 0
|| (pMsg->lParam & ENDSESSION_LOGOFF)) {
if (!pMainWindow->close()) {
*result = 0;
} else {
*result = 1;
}
return true;
}
break;
case WM_DPICHANGED:{
if (pMsg->hwnd == (HWND)pMainWindow->winId()) {
int oldDPI = screenDPI();
//postEvent takes the owner ship
QEvent * dpiEvent = new QEvent(DPI_CHANGED_EVENT);
qApp->postEvent(pMainWindow,dpiEvent);
setScreenDPI(HIWORD(pMsg->wParam));
int newDPI = screenDPI();
pMainWindow->updateDPI(oldDPI,newDPI);
} else if (pMainWindow->cpuDialog() &&
(HWND)pMainWindow->cpuDialog()->winId() == pMsg->hwnd) {
int newDPI = HIWORD(pMsg->wParam);
pMainWindow->cpuDialog()->updateDPI(newDPI);
}
break;
}
case WM_USER_OPEN_FILE: {
QSharedMemory sharedMemory("RedPandaCpp/openfiles");
if (sharedMemory.attach()) {
QBuffer buffer;
QDataStream in(&buffer);
QStringList files;
sharedMemory.lock();
buffer.setData((char*)sharedMemory.constData(), sharedMemory.size());
buffer.open(QBuffer::ReadOnly);
in >> files;
sharedMemory.unlock();
if (pMainWindow->isMinimized()) {
pMainWindow->showNormal();
}
pMainWindow->openFiles(files);
sharedMemory.detach();
}
return true;
}
}
return false;
}
bool sendFilesToInstance() {
HWND prevInstance = getPreviousInstance();
if (prevInstance != NULL) {
QSharedMemory sharedMemory("RedPandaCpp/openfiles");
QBuffer buffer;
buffer.open(QBuffer::ReadWrite);
QDataStream out(&buffer);
QStringList filesToOpen = qApp->arguments();
filesToOpen.pop_front();
out<<filesToOpen;
int size = buffer.size();
if (sharedMemory.create(size)) {
sharedMemory.lock();
char *to = (char*)sharedMemory.data();
const char *from = buffer.data().data();
memcpy(to, from, qMin(sharedMemory.size(), size));
sharedMemory.unlock();
SendMessage(prevInstance,WM_USER_OPEN_FILE,0,0);
return true;
}
}
return false;
}
#endif
QString getSettingFilename(const QString& filepath, bool& firstRun) {
QString filename;
if (filepath.isEmpty()) {
if (isGreenEdition()) {
filename = includeTrailingPathDelimiter(QApplication::applicationDirPath()) +
"config/" + APP_SETTSINGS_FILENAME;
} else {
filename =includeTrailingPathDelimiter(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation)[0])
+ APP_SETTSINGS_FILENAME;
}
} else {
filename = filepath;
}
QFileInfo fileInfo(filename);
firstRun = !fileInfo.exists();
QDir dir(fileInfo.absoluteDir());
if (!dir.exists()) {
if (!dir.mkpath(dir.absolutePath())) {
QMessageBox::critical(nullptr, QObject::tr("Error"),
QString(QObject::tr("Can't create configuration folder %1")).arg(dir.absolutePath()));
return "";
}
}
if (fileInfo.exists() && !fileInfo.isWritable()) {
QMessageBox::critical(nullptr, QObject::tr("Error"),
QString(QObject::tr("Can't write to configuration file %1")).arg(filename));
return "";
}
return filename;
}
void setTheme(const QString& theme) {
pSettings->environment().setTheme(theme);
ThemeManager themeManager;
PAppTheme appTheme = themeManager.theme(theme);
if (appTheme && !appTheme->defaultColorScheme().isEmpty()) {
pSettings->editor().setColorScheme(appTheme->defaultColorScheme());
pSettings->editor().save();
}
if (appTheme && !appTheme->defaultIconSet().isEmpty()) {
pSettings->environment().setIconSet(appTheme->defaultIconSet());
}
pSettings->environment().save();
}
int main(int argc, char *argv[])
{
//QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
//#ifdef Q_OS_WIN
// QApplication::setAttribute(Qt::AA_DisableHighDpiScaling);
// QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
// qputenv("QT_SCALE_FACTOR_ROUNDING_POLICY","PassThrough");
// qputenv("QT_DEVICE_PIXEL_RATIO ","auto");
// qputenv("QT_AUTO_SCREEN_SCALE_FACTOR","false");
//#endif
QApplication app(argc, argv);
QFile tempFile(QDir::tempPath()+QDir::separator()+"RedPandaDevCppStartUp.lock");
{
bool firstRun;
QString settingFilename = getSettingFilename(QString(), firstRun);
bool openInSingleInstance = false;
if (!settingFilename.isEmpty() && !firstRun) {
QSettings envSetting(settingFilename,QSettings::IniFormat);
envSetting.beginGroup(SETTING_ENVIRONMENT);
openInSingleInstance = envSetting.value("open_files_in_single_instance",false).toBool();
} else if (!settingFilename.isEmpty() && firstRun)
openInSingleInstance = false;
if (app.arguments().contains("-ns")) {
openInSingleInstance = false;
} else if (app.arguments().contains("-s"))
openInSingleInstance = true;
if (openInSingleInstance) {
int openCount = 0;
while (true) {
if (tempFile.open(QFile::NewOnly))
break;
QThread::msleep(100);
openCount++;
if (openCount>100)
break;
}
if (app.arguments().length()>=2 && openCount<100) {
#ifdef Q_OS_WIN
if (sendFilesToInstance()) {
tempFile.remove();
return 0;
}
#endif
}
}
}
//Translation must be loaded first
QTranslator trans,transQt;
bool firstRun;
QString settingFilename = getSettingFilename(QString(), firstRun);
if (!isGreenEdition()) {
QDir::setCurrent(QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation)[0]);
}
if (settingFilename.isEmpty()) {
tempFile.remove();
return -1;
}
QString language;
{
QSettings languageSetting(settingFilename,QSettings::IniFormat);
languageSetting.beginGroup(SETTING_ENVIRONMENT);
language = languageSetting.value("language",QLocale::system().name()).toString();
if (trans.load("RedPandaIDE_"+language,":/i18n/")) {
app.installTranslator(&trans);
}
if (transQt.load("qt_"+language,":/translations")) {
app.installTranslator(&transQt);
}
}
qRegisterMetaType<PCompileIssue>("PCompileIssue");
qRegisterMetaType<PCompileIssue>("PCompileIssue&");
qRegisterMetaType<QVector<int>>("QVector<int>");
qRegisterMetaType<QHash<int,QString>>("QHash<int,QString>");
initParser();
try {
SystemConsts systemConsts;
pSystemConsts = &systemConsts;
CharsetInfoManager charsetInfoManager(language);
pCharsetInfoManager=&charsetInfoManager;
//We must use smarter point here, to manually control it's lifetime:
// when restore default settings, it must be destoyed before we remove all setting files.
auto settings = std::make_unique<Settings>(settingFilename);
//load settings
pSettings = settings.get();
if (firstRun) {
pSettings->compilerSets().findSets();
pSettings->compilerSets().saveSets();
}
pSettings->load();
if (firstRun) {
//set theme
ChooseThemeDialog themeDialog;
themeDialog.exec();
switch (themeDialog.theme()) {
case ChooseThemeDialog::Theme::Dark:
setTheme("dark");
break;
default:
setTheme("default");
}
pSettings->editor().setDefaultFileCpp(themeDialog.language()==ChooseThemeDialog::Language::CPlusPlus);
pSettings->editor().save();
//auto detect git in path
pSettings->vcs().detectGitInPath();
}
//Color scheme settings must be loaded after translation
ColorManager colorManager;
pColorManager = &colorManager;
IconsManager iconsManager;
pIconsManager = &iconsManager;
AutolinkManager autolinkManager;
pAutolinkManager = &autolinkManager;
try {
pAutolinkManager->load();
} catch (FileError e) {
QMessageBox::critical(nullptr,
QObject::tr("Can't load autolink settings"),
e.reason(),
QMessageBox::Ok);
}
MainWindow mainWindow;
pMainWindow = &mainWindow;
#if QT_VERSION_MAJOR==5 && QT_VERSION_MINOR < 15
setScreenDPI(qApp->primaryScreen()->logicalDotsPerInch());
#else
if (mainWindow.screen())
setScreenDPI(mainWindow.screen()->logicalDotsPerInch());
#endif
mainWindow.show();
if (app.arguments().count()>1) {
QStringList filesToOpen = app.arguments();
filesToOpen.pop_front();
pMainWindow->openFiles(filesToOpen);
} else {
if (pSettings->editor().autoLoadLastFiles())
pMainWindow->loadLastOpens();
if (pMainWindow->editorList()->pageCount()==0 && !pMainWindow->project()) {
pMainWindow->newEditor();
}
}
//reset default open folder
QDir::setCurrent(pSettings->environment().defaultOpenFolder());
pMainWindow->setFilesViewRoot(pSettings->environment().currentFolder());
#ifdef Q_OS_WIN
WindowLogoutEventFilter filter;
app.installNativeEventFilter(&filter);
#endif
if (tempFile.isOpen()) {
tempFile.close();
tempFile.remove();
}
int retCode = app.exec();
QString configDir = pSettings->dirs().config();
// save settings
// settings->compilerSets().saveSets();
if (mainWindow.shouldRemoveAllSettings()) {
settings.release();
delete pSettings;
QDir dir(configDir);
dir.removeRecursively();
}
return retCode;
} catch (BaseError e) {
tempFile.remove();
QMessageBox::critical(nullptr,QApplication::tr("Error"),e.reason());
return -1;
}
}