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 | |
25 | EnvironmentFileAssociationWidget::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 | |
45 | EnvironmentFileAssociationWidget::~EnvironmentFileAssociationWidget() |
46 | { |
47 | delete ui; |
48 | } |
49 | |
50 | void 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 | |
59 | void EnvironmentFileAssociationWidget::doSave() |
60 | { |
61 | mModel.saveAssociations(); |
62 | mModel.updateAssociationStates(); |
63 | pSettings->environment().setOpenFilesInSingleInstance(ui->rbOpenInSingleApplication->isChecked()); |
64 | pSettings->environment().save(); |
65 | } |
66 | |
67 | FileAssociationModel::FileAssociationModel(QObject *parent) : QAbstractListModel(parent) |
68 | { |
69 | |
70 | } |
71 | |
72 | void 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 | |
85 | void 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 | |
101 | void 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 | |
152 | bool 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 | |
178 | bool 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 | } |
204 | bool 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 | |
214 | bool 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 | |
228 | bool 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 | |
241 | bool 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 | |
263 | int FileAssociationModel::rowCount(const QModelIndex &) const |
264 | { |
265 | return mItems.count(); |
266 | } |
267 | |
268 | QVariant 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 | |
282 | bool 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 | |
295 | Qt::ItemFlags FileAssociationModel::flags(const QModelIndex &) const |
296 | { |
297 | return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable; |
298 | } |
299 | |