1/*
2 * Copyright (C) 2020-2022 Roy Qu (royqh1979@gmail.com)
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17#include "environmentfileassociationwidget.h"
18#include "ui_environmentfileassociationwidget.h"
19#include "../systemconsts.h"
20#include "../settings.h"
21
22#include <QMessageBox>
23#include <windows.h>
24
25EnvironmentFileAssociationWidget::EnvironmentFileAssociationWidget(const QString& name, const QString& group, QWidget *parent) :
26 SettingsWidget(name,group,parent),
27 ui(new Ui::EnvironmentFileAssociationWidget)
28{
29 ui->setupUi(this);
30 mModel.addItem("C Source File","c",1);
31 mModel.addItem("C++ Source File","cpp",2);
32 mModel.addItem("C++ Source File","cxx",2);
33 mModel.addItem("C++ Source File","cc",2);
34 mModel.addItem("C/C++ Header File","h",3);
35 mModel.addItem("C++ Header File","hpp",4);
36 mModel.addItem("C++ Header File","hxx",4);
37 mModel.addItem("Red Panda C++ Project File","dev",5);
38 ui->lstFileTypes->setModel(&mModel);
39 connect(&mModel, &FileAssociationModel::associationChanged,
40 [this](){
41 setSettingsChanged();
42 });
43}
44
45EnvironmentFileAssociationWidget::~EnvironmentFileAssociationWidget()
46{
47 delete ui;
48}
49
50void EnvironmentFileAssociationWidget::doLoad()
51{
52 if (pSettings->environment().openFilesInSingleInstance())
53 ui->rbOpenInSingleApplication->setChecked(true);
54 else
55 ui->rbOpenInMultiApplication->setChecked(true);
56 mModel.updateAssociationStates();
57}
58
59void EnvironmentFileAssociationWidget::doSave()
60{
61 mModel.saveAssociations();
62 mModel.updateAssociationStates();
63 pSettings->environment().setOpenFilesInSingleInstance(ui->rbOpenInSingleApplication->isChecked());
64 pSettings->environment().save();
65}
66
67FileAssociationModel::FileAssociationModel(QObject *parent) : QAbstractListModel(parent)
68{
69
70}
71
72void FileAssociationModel::addItem(const QString &name, const QString &suffix, int icon)
73{
74 beginInsertRows(QModelIndex(), mItems.count(), mItems.count());
75 PFileAssociationItem item = std::make_shared<FileAssociationItem>();
76 item->name = name;
77 item->suffix = suffix;
78 item->icon = icon;
79 item->selected = false;
80 item->defaultSelected = false;
81 mItems.append(item);
82 endInsertRows();
83}
84
85void FileAssociationModel::updateAssociationStates()
86{
87 beginResetModel();
88 foreach (const PFileAssociationItem& item, mItems) {
89 item->selected = checkAssociation(
90 "."+item->suffix,
91 "DevCpp."+item->suffix,
92// item->name,
93 "Open",
94 pSettings->dirs().executable()+" \"%1\""
95 );
96 item->defaultSelected = item->selected;
97 }
98 endResetModel();
99}
100
101void FileAssociationModel::saveAssociations()
102{
103 QSet<QString> fileTypeUsed;
104 QSet<QString> fileTypes;
105 QMap<QString,PFileAssociationItem> fileTypeDescriptions;
106
107 foreach (const PFileAssociationItem& item, mItems) {
108 if (item->selected == item->defaultSelected
109 && !item->selected)
110 continue;
111 bool ok;
112 fileTypes.insert("DevCpp."+item->suffix);
113 fileTypeDescriptions.insert("DevCpp."+item->suffix,item);
114 if (!item->selected) {
115 ok = unregisterAssociation("."+item->suffix);
116 } else {
117 fileTypeUsed.insert("DevCpp."+item->suffix);
118 ok = registerAssociation(
119 "."+item->suffix,
120 "DevCpp."+item->suffix
121 );
122 }
123 if (!ok) {
124 QMessageBox::critical(NULL,
125 tr("Register File Association Error"),
126 tr("Don't have privilege to register file types!"));
127 return;
128 }
129 }
130 foreach (const QString& fileType, fileTypes) {
131 bool ok;
132 if (fileTypeUsed.contains(fileType)) {
133 PFileAssociationItem item = fileTypeDescriptions[fileType];
134 ok = registerFileType(fileType,
135 item->name,
136 "Open",
137 pSettings->dirs().executable(),
138 item->icon);
139 } else {
140 ok = unregisterFileType(fileType);
141 }
142 if (!ok) {
143 QMessageBox::critical(NULL,
144 tr("Register File Type Error"),
145 tr("Don't have privilege to register file types!"));
146 return;
147 }
148 }
149
150}
151
152bool FileAssociationModel::checkAssociation(const QString &extension, const QString &filetype, const QString &verb, const QString &serverApp)
153{
154 HKEY key;
155 LONG result;
156
157 result = RegOpenKeyExA(HKEY_CLASSES_ROOT,extension.toLocal8Bit(),0,KEY_READ,&key);
158 RegCloseKey(key);
159 if (result != ERROR_SUCCESS )
160 return false;
161
162 result = RegOpenKeyExA(HKEY_CLASSES_ROOT,filetype.toLocal8Bit(),0,KEY_READ,&key);
163 RegCloseKey(key);
164 if (result != ERROR_SUCCESS )
165 return false;
166
167 QString keyString = QString("%1\\Shell\\%2\\Command").arg(filetype).arg(verb);
168 QString value1,value2;
169 if (!readRegistry(HKEY_CLASSES_ROOT,keyString.toLocal8Bit(),"",value1))
170 return false;
171 if (!readRegistry(HKEY_CLASSES_ROOT,extension.toLocal8Bit(),"",value2))
172 return false;
173
174 return (value2 == filetype)
175 && (value1.compare(serverApp,PATH_SENSITIVITY)==0);
176}
177
178bool writeRegistry(HKEY parentKey, const QByteArray& subKey, const QByteArray& value) {
179 DWORD disposition;
180 HKEY key;
181 LONG result = RegCreateKeyExA(
182 parentKey,
183 subKey,
184 0,
185 NULL,
186 REG_OPTION_NON_VOLATILE,
187 KEY_ALL_ACCESS,
188 NULL,
189 &key,
190 &disposition);
191 RegCloseKey(key);
192 if (result != ERROR_SUCCESS ) {
193 return false;
194 }
195 result = RegSetKeyValueA(
196 HKEY_CLASSES_ROOT,
197 subKey,
198 "",
199 REG_SZ,
200 (const BYTE*)value.data(),
201 value.length()+1);
202 return result == ERROR_SUCCESS;
203}
204bool FileAssociationModel::registerAssociation(const QString &extension, const QString &filetype)
205{
206 if (!writeRegistry(HKEY_CLASSES_ROOT,
207 extension.toLocal8Bit(),
208 filetype.toLocal8Bit())){
209 return false;
210 }
211 return true;
212}
213
214bool FileAssociationModel::unregisterAssociation(const QString &extension)
215{
216 LONG result;
217 HKEY key;
218 result = RegOpenKeyExA(HKEY_CLASSES_ROOT,extension.toLocal8Bit(),0,KEY_READ,&key);
219 if (result != ERROR_SUCCESS )
220 return true;
221 RegCloseKey(key);
222
223 result = RegDeleteTreeA(HKEY_CLASSES_ROOT,extension.toLocal8Bit());
224 return result == ERROR_SUCCESS;
225
226}
227
228bool FileAssociationModel::unregisterFileType(const QString &fileType)
229{
230 LONG result;
231 HKEY key;
232 result = RegOpenKeyExA(HKEY_CLASSES_ROOT,fileType.toLocal8Bit(),0,KEY_READ,&key);
233 if (result != ERROR_SUCCESS )
234 return true;
235 RegCloseKey(key);
236
237 result = RegDeleteTreeA(HKEY_CLASSES_ROOT,fileType.toLocal8Bit());
238 return result == ERROR_SUCCESS;
239}
240
241bool FileAssociationModel::registerFileType(const QString &filetype, const QString &description, const QString &verb, const QString &serverApp, int icon)
242{
243 if (!writeRegistry(HKEY_CLASSES_ROOT,
244 filetype.toLocal8Bit(),
245 description.toLocal8Bit()))
246 return false;
247
248 QString keyString = QString("%1\\DefaultIcon").arg(filetype);
249 QString value = QString("%1,%2").arg(serverApp).arg(icon);
250 if (!writeRegistry(HKEY_CLASSES_ROOT,
251 keyString.toLocal8Bit(),
252 value.toLocal8Bit()))
253 return false;
254 keyString = QString("%1\\Shell\\%2\\Command").arg(filetype).arg(verb);
255 value = serverApp+" \"%1\"";
256 if (!writeRegistry(HKEY_CLASSES_ROOT,
257 keyString.toLocal8Bit(),
258 value.toLocal8Bit()))
259 return false;
260 return true;
261}
262
263int FileAssociationModel::rowCount(const QModelIndex &) const
264{
265 return mItems.count();
266}
267
268QVariant FileAssociationModel::data(const QModelIndex &index, int role) const
269{
270 if (!index.isValid()) {
271 return QVariant();
272 }
273 PFileAssociationItem item = mItems[index.row()];
274 if (role == Qt::DisplayRole) {
275 return QString("%1 (*.%2)").arg(item->name).arg(item->suffix);
276 } else if (role == Qt::CheckStateRole) {
277 return (item->selected)? Qt::Checked : Qt::Unchecked;
278 }
279 return QVariant();
280}
281
282bool FileAssociationModel::setData(const QModelIndex &index, const QVariant &value, int role)
283{
284 if (!index.isValid())
285 return false;
286 if (role == Qt::CheckStateRole) {
287 PFileAssociationItem item = mItems[index.row()];
288 item->selected = value.toBool();
289 emit associationChanged();
290 return true;
291 }
292 return false;
293}
294
295Qt::ItemFlags FileAssociationModel::flags(const QModelIndex &) const
296{
297 return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable;
298}
299