diff --git a/NEWS.md b/NEWS.md index 46b9b802..ebcaa011 100644 --- a/NEWS.md +++ b/NEWS.md @@ -13,6 +13,7 @@ Version 0.6.0 - enhancement: replace in files - enhancement: refactor in project (using search symbol occurence and replace in files) - fix: search in files + - implement: register file associations Version 0.5.0 - enhancement: support C++ using type alias; diff --git a/RedPandaIDE/RedPandaIDE.pro b/RedPandaIDE/RedPandaIDE.pro index 731d2ef5..0e7709c9 100644 --- a/RedPandaIDE/RedPandaIDE.pro +++ b/RedPandaIDE/RedPandaIDE.pro @@ -48,6 +48,7 @@ SOURCES += \ settingsdialog/editormiscwidget.cpp \ settingsdialog/editorsnippetwidget.cpp \ settingsdialog/editortooltipswidget.cpp \ + settingsdialog/environmentfileassociationwidget.cpp \ settingsdialog/formattergeneralwidget.cpp \ settingsdialog/projectcompileparamaterswidget.cpp \ settingsdialog/projectcompilerwidget.cpp \ @@ -151,6 +152,7 @@ HEADERS += \ settingsdialog/editormiscwidget.h \ settingsdialog/editorsnippetwidget.h \ settingsdialog/editortooltipswidget.h \ + settingsdialog/environmentfileassociationwidget.h \ settingsdialog/formattergeneralwidget.h \ settingsdialog/projectcompileparamaterswidget.h \ settingsdialog/projectcompilerwidget.h \ @@ -227,6 +229,7 @@ FORMS += \ settingsdialog/editormiscwidget.ui \ settingsdialog/editorsnippetwidget.ui \ settingsdialog/editortooltipswidget.ui \ + settingsdialog/environmentfileassociationwidget.ui \ settingsdialog/formattergeneralwidget.ui \ settingsdialog/projectcompileparamaterswidget.ui \ settingsdialog/projectcompilerwidget.ui \ @@ -275,7 +278,7 @@ RESOURCES += \ icons.qrc \ translations.qrc -RC_ICONS = images/devcpp.ico +RC_ICONS = images/devcpp.ico images/associations/c.ico images/associations/cpp.ico images/associations/h.ico images/associations/hpp.ico images/associations/dev.ico #win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../QScintilla/src/release/ -lqscintilla2_qt5d #else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../QScintilla/src/debug/ -lqscintilla2_qt5d diff --git a/RedPandaIDE/mainwindow.h b/RedPandaIDE/mainwindow.h index c1c58419..66ed7fcc 100644 --- a/RedPandaIDE/mainwindow.h +++ b/RedPandaIDE/mainwindow.h @@ -377,6 +377,8 @@ private slots: void on_btnReplace_clicked(); + void on_btnCancelReplace_clicked(); + private: Ui::MainWindow *ui; EditorList *mEditorList; diff --git a/RedPandaIDE/mainwindow.ui b/RedPandaIDE/mainwindow.ui index 8124e831..57c1dc46 100644 --- a/RedPandaIDE/mainwindow.ui +++ b/RedPandaIDE/mainwindow.ui @@ -776,6 +776,13 @@ + + + + Cancel + + + diff --git a/RedPandaIDE/settings.cpp b/RedPandaIDE/settings.cpp index 9f6545e9..9e9d498d 100644 --- a/RedPandaIDE/settings.cpp +++ b/RedPandaIDE/settings.cpp @@ -189,6 +189,11 @@ QString Settings::Dirs::config(Settings::Dirs::DataType dataType) const return ""; } +QString Settings::Dirs::executable() const +{ + return QApplication::instance()->applicationFilePath(); +} + void Settings::Dirs::doSave() { diff --git a/RedPandaIDE/settings.h b/RedPandaIDE/settings.h index 868d9fd5..fc99b1c8 100644 --- a/RedPandaIDE/settings.h +++ b/RedPandaIDE/settings.h @@ -87,6 +87,7 @@ public: QString projectDir() const; QString data(DataType dataType = DataType::None) const; QString config(DataType dataType = DataType::None) const; + QString executable() const; // _Base interface protected: diff --git a/RedPandaIDE/settingsdialog/environmentfileassociationwidget.cpp b/RedPandaIDE/settingsdialog/environmentfileassociationwidget.cpp new file mode 100644 index 00000000..eec6920e --- /dev/null +++ b/RedPandaIDE/settingsdialog/environmentfileassociationwidget.cpp @@ -0,0 +1,301 @@ +#include "environmentfileassociationwidget.h" +#include "ui_environmentfileassociationwidget.h" +#include "../systemconsts.h" +#include "../settings.h" + +#include +#include +#include + +EnvironmentFileAssociationWidget::EnvironmentFileAssociationWidget(const QString& name, const QString& group, QWidget *parent) : + SettingsWidget(name,group,parent), + ui(new Ui::EnvironmentFileAssociationWidget) +{ + ui->setupUi(this); + mModel.addItem("C Source File","c",1); + mModel.addItem("C++ Source File","cpp",2); + mModel.addItem("C++ Source File","cxx",2); + mModel.addItem("C++ Source File","cc",2); + mModel.addItem("C/C++ Header File","h",3); + mModel.addItem("C++ Header File","hpp",4); + mModel.addItem("C++ Header File","hxx",4); + mModel.addItem("Dev-C++ Project File","dev",5); + ui->lstFileTypes->setModel(&mModel); + connect(&mModel, &FileAssociationModel::associationChanged, + [this](){ + setSettingsChanged(); + }); +} + +EnvironmentFileAssociationWidget::~EnvironmentFileAssociationWidget() +{ + delete ui; +} + +void EnvironmentFileAssociationWidget::doLoad() +{ + mModel.updateAssociationStates(); +} + +void EnvironmentFileAssociationWidget::doSave() +{ + mModel.saveAssociations(); + mModel.updateAssociationStates(); +} + +FileAssociationModel::FileAssociationModel(QObject *parent) : QAbstractListModel(parent) +{ + +} + +void FileAssociationModel::addItem(const QString &name, const QString &suffix, int icon) +{ + beginInsertRows(QModelIndex(), mItems.count(), mItems.count()); + PFileAssociationItem item = std::make_shared(); + item->name = name; + item->suffix = suffix; + item->icon = icon; + item->selected = false; + item->defaultSelected = false; + mItems.append(item); + endInsertRows(); +} + +void FileAssociationModel::updateAssociationStates() +{ + beginResetModel(); + foreach (const PFileAssociationItem& item, mItems) { + item->selected = checkAssociation( + "."+item->suffix, + "DevCpp."+item->suffix, + item->name, + "open", + pSettings->dirs().executable()+" \"%1\"" + ); + item->defaultSelected = item->selected; + } + endResetModel(); +} + +void FileAssociationModel::saveAssociations() +{ + QSet fileTypeUsed; + QSet fileTypes; + QMap fileTypeDescriptions; + + foreach (const PFileAssociationItem& item, mItems) { + if (item->selected == item->defaultSelected) + continue; + bool ok; + fileTypes.insert("DevCpp."+item->suffix); + fileTypeDescriptions.insert("DevCpp."+item->suffix,item); + if (!item->selected) { + ok = unregisterAssociation("."+item->suffix); + } else { + fileTypeUsed.insert("DevCpp."+item->suffix); + ok = registerAssociation( + "."+item->suffix, + "DevCpp."+item->suffix + ); + } + if (!ok) { + QMessageBox::critical(NULL, + tr("Register File Association Error"), + tr("Don't have privilege to register file types!")); + return; + } + } + foreach (const QString& fileType, fileTypes) { + bool ok; + if (fileTypeUsed.contains(fileType)) { + PFileAssociationItem item = fileTypeDescriptions[fileType]; + ok = registerFileType(fileType, + item->name, + "open", + pSettings->dirs().executable(), + item->icon); + } else { + ok = unregisterFileType(fileType); + } + if (!ok) { + QMessageBox::critical(NULL, + tr("Register File Type Error"), + tr("Don't have privilege to register file types!")); + return; + } + } + +} + +bool readRegistry(HKEY key,QByteArray subKey, QString& value) { + DWORD dataSize; + LONG result; + result = RegGetValueA(HKEY_CLASSES_ROOT,subKey, + "", RRF_RT_REG_SZ | RRF_RT_REG_MULTI_SZ, + NULL, + NULL, + &dataSize); + if (result!=ERROR_SUCCESS) + return false; + char * buffer = new char[dataSize+10]; + result = RegGetValueA(HKEY_CLASSES_ROOT,subKey, + "", RRF_RT_REG_SZ | RRF_RT_REG_MULTI_SZ, + NULL, + buffer, + &dataSize); + if (result!=ERROR_SUCCESS) { + delete[] buffer; + return false; + } + value=QString::fromLocal8Bit(buffer); + delete [] buffer; + return true; +} + +bool FileAssociationModel::checkAssociation(const QString &extension, const QString &filetype, const QString &description, const QString &verb, const QString &serverApp) +{ + HKEY key; + LONG result; + + result = RegOpenKeyExA(HKEY_CLASSES_ROOT,extension.toLocal8Bit(),0,KEY_READ,&key); + RegCloseKey(key); + if (result != ERROR_SUCCESS ) + return false; + + result = RegOpenKeyExA(HKEY_CLASSES_ROOT,filetype.toLocal8Bit(),0,KEY_READ,&key); + RegCloseKey(key); + if (result != ERROR_SUCCESS ) + return false; + + QString keyString = QString("%1\\shell\\%2\\command").arg(filetype).arg(verb); + QString value1,value2; + if (!readRegistry(HKEY_CLASSES_ROOT,keyString.toLocal8Bit(),value1)) + return false; + if (!readRegistry(HKEY_CLASSES_ROOT,extension.toLocal8Bit(),value2)) + return false; + + return (value2 == filetype) + && (value1.compare(serverApp,PATH_SENSITIVITY)==0); +} + +bool writeRegistry(HKEY parentKey, const QByteArray& subKey, const QByteArray& value) { + DWORD disposition; + HKEY key; + LONG result = RegCreateKeyExA( + parentKey, + subKey, + 0, + NULL, + REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, + NULL, + &key, + &disposition); + RegCloseKey(key); + if (result != ERROR_SUCCESS ) { + return false; + } + result = RegSetKeyValueA( + HKEY_CLASSES_ROOT, + subKey, + "", + REG_SZ, + (const BYTE*)value.data(), + value.length()+1); + return result == ERROR_SUCCESS; +} +bool FileAssociationModel::registerAssociation(const QString &extension, const QString &filetype) +{ + if (!writeRegistry(HKEY_CLASSES_ROOT, + extension.toLocal8Bit(), + filetype.toLocal8Bit())){ + return false; + } + return true; +} + +bool FileAssociationModel::unregisterAssociation(const QString &extension) +{ + LONG result; + HKEY key; + result = RegOpenKeyExA(HKEY_CLASSES_ROOT,extension.toLocal8Bit(),0,KEY_READ,&key); + if (result != ERROR_SUCCESS ) + return true; + RegCloseKey(key); + + result = RegDeleteTreeA(HKEY_CLASSES_ROOT,extension.toLocal8Bit()); + return result == ERROR_SUCCESS; + +} + +bool FileAssociationModel::unregisterFileType(const QString &fileType) +{ + LONG result; + HKEY key; + result = RegOpenKeyExA(HKEY_CLASSES_ROOT,fileType.toLocal8Bit(),0,KEY_READ,&key); + if (result != ERROR_SUCCESS ) + return true; + RegCloseKey(key); + + result = RegDeleteTreeA(HKEY_CLASSES_ROOT,fileType.toLocal8Bit()); + return result == ERROR_SUCCESS; +} + +bool FileAssociationModel::registerFileType(const QString &filetype, const QString &description, const QString &verb, const QString &serverApp, int icon) +{ + if (!writeRegistry(HKEY_CLASSES_ROOT, + filetype.toLocal8Bit(), + description.toLocal8Bit())) + return false; + + QString keyString = QString("%1\\DefaultIcon").arg(filetype); + QString value = QString("%1,%2").arg(serverApp).arg(icon); + if (!writeRegistry(HKEY_CLASSES_ROOT, + keyString.toLocal8Bit(), + value.toLocal8Bit())) + return false; + keyString = QString("%1\\shell\\%2\\command").arg(filetype).arg(verb); + value = serverApp+" \"%1\""; + if (!writeRegistry(HKEY_CLASSES_ROOT, + keyString.toLocal8Bit(), + value.toLocal8Bit())) + return false; + return true; +} + +int FileAssociationModel::rowCount(const QModelIndex &parent) const +{ + return mItems.count(); +} + +QVariant FileAssociationModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + PFileAssociationItem item = mItems[index.row()]; + if (role == Qt::DisplayRole) { + return QString("%1 (*.%2)").arg(item->name).arg(item->suffix); + } else if (role == Qt::CheckStateRole) { + return (item->selected)? Qt::Checked : Qt::Unchecked; + } + return QVariant(); +} + +bool FileAssociationModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + if (role == Qt::CheckStateRole) { + PFileAssociationItem item = mItems[index.row()]; + item->selected = value.toBool(); + emit associationChanged(); + return true; + } + return false; +} + +Qt::ItemFlags FileAssociationModel::flags(const QModelIndex &) const +{ + return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; +} diff --git a/RedPandaIDE/settingsdialog/environmentfileassociationwidget.h b/RedPandaIDE/settingsdialog/environmentfileassociationwidget.h new file mode 100644 index 00000000..9d67c627 --- /dev/null +++ b/RedPandaIDE/settingsdialog/environmentfileassociationwidget.h @@ -0,0 +1,74 @@ +#ifndef ENVIRONMENTFILEASSOCIATIONWIDGET_H +#define ENVIRONMENTFILEASSOCIATIONWIDGET_H + +#include +#include +#include "settingswidget.h" + +namespace Ui { +class EnvironmentFileAssociationWidget; +} +struct FileAssociationItem { + QString name; + QString suffix; + int icon; + bool selected; + bool defaultSelected; +}; +using PFileAssociationItem = std::shared_ptr; + +class FileAssociationModel:public QAbstractListModel { + Q_OBJECT +public: + explicit FileAssociationModel(QObject* parent = nullptr); + void addItem(const QString& name, const QString& suffix, int icon); + void updateAssociationStates(); + void saveAssociations(); +signals: + void associationChanged(); +private: + bool checkAssociation(const QString& extension, + const QString& filetype, + const QString& description, + const QString& verb, + const QString& serverApp); + bool registerAssociation(const QString& extension, + const QString& filetype); + bool unregisterAssociation(const QString& extension); + bool unregisterFileType(const QString& fileType); + bool registerFileType(const QString& filetype, + const QString& description, + const QString& verb, + const QString& serverApp, + int icon); +private: + QList mItems; + + + // QAbstractItemModel interface +public: + int rowCount(const QModelIndex &parent) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; +}; + +class EnvironmentFileAssociationWidget : public SettingsWidget +{ + Q_OBJECT + +public: + explicit EnvironmentFileAssociationWidget(const QString& name, const QString& group, QWidget *parent = nullptr); + ~EnvironmentFileAssociationWidget(); + +private: + Ui::EnvironmentFileAssociationWidget *ui; + FileAssociationModel mModel; + + // SettingsWidget interface +protected: + void doLoad() override; + void doSave() override; +}; + +#endif // ENVIRONMENTFILEASSOCIATIONWIDGET_H diff --git a/RedPandaIDE/settingsdialog/environmentfileassociationwidget.ui b/RedPandaIDE/settingsdialog/environmentfileassociationwidget.ui new file mode 100644 index 00000000..24122b3b --- /dev/null +++ b/RedPandaIDE/settingsdialog/environmentfileassociationwidget.ui @@ -0,0 +1,46 @@ + + + EnvironmentFileAssociationWidget + + + + 0 + 0 + 708 + 350 + + + + Form + + + + + + File Types: + + + + + + + + + + + + Just check or uncheck for which file types Dev-C++ wil be registered as the default application to open them ... + + + Qt::AlignCenter + + + true + + + + + + + + diff --git a/RedPandaIDE/settingsdialog/settingsdialog.cpp b/RedPandaIDE/settingsdialog/settingsdialog.cpp index d2eb21c1..b0b895cb 100644 --- a/RedPandaIDE/settingsdialog/settingsdialog.cpp +++ b/RedPandaIDE/settingsdialog/settingsdialog.cpp @@ -15,6 +15,7 @@ #include "editorsnippetwidget.h" #include "editormiscwidget.h" #include "environmentappearencewidget.h" +#include "environmentfileassociationwidget.h" #include "executorgeneralwidget.h" #include "debuggeneralwidget.h" #include "formattergeneralwidget.h" @@ -95,6 +96,10 @@ PSettingsDialog SettingsDialog::optionDialog() widget->init(); dialog->addWidget(widget); + widget = new EnvironmentFileAssociationWidget(tr("FileAssociation"),tr("Environment")); + widget->init(); + dialog->addWidget(widget); + widget = new CompilerSetOptionWidget(tr("Compiler Set"),tr("Compiler")); widget->init(); dialog->addWidget(widget);