28
28
import FreeCADGui
29
29
30
30
from PySide2 import QtCore
31
+ from PySide2 .QtGui import QIcon
31
32
from PySide2 .QtWidgets import (
32
33
QWidget ,
33
34
QCheckBox ,
34
35
QComboBox ,
36
+ QDialog ,
37
+ QHeaderView ,
35
38
QRadioButton ,
36
39
QLineEdit ,
37
40
QTextEdit ,
38
41
)
39
42
43
+ translate = FreeCAD .Qt .translate
44
+
45
+ #pylint: disable=too-few-public-methods
40
46
41
47
class AddonManagerOptions :
42
- """ A class containing a form element that is inserted as a FreeCAD preference page. """
48
+ """A class containing a form element that is inserted as a FreeCAD preference page."""
43
49
44
50
def __init__ (self , _ = None ):
45
51
self .form = FreeCADGui .PySideUic .loadUi (
46
52
os .path .join (os .path .dirname (__file__ ), "AddonManagerOptions.ui" )
47
53
)
54
+ self .table_model = CustomRepoDataModel ()
55
+ self .form .customRepositoriesTableView .setModel (self .table_model )
56
+
57
+ self .form .addCustomRepositoryButton .setIcon (
58
+ QIcon .fromTheme ("add" , QIcon (":/icons/list-add.svg" ))
59
+ )
60
+ self .form .removeCustomRepositoryButton .setIcon (
61
+ QIcon .fromTheme ("remove" , QIcon (":/icons/list-remove.svg" ))
62
+ )
63
+
64
+ self .form .customRepositoriesTableView .horizontalHeader ().setStretchLastSection (
65
+ False
66
+ )
67
+ self .form .customRepositoriesTableView .horizontalHeader ().setSectionResizeMode (
68
+ 0 , QHeaderView .Stretch
69
+ )
70
+ self .form .customRepositoriesTableView .horizontalHeader ().setSectionResizeMode (
71
+ 1 , QHeaderView .ResizeToContents
72
+ )
73
+
74
+ self .form .addCustomRepositoryButton .clicked .connect (
75
+ self ._add_custom_repo_clicked
76
+ )
77
+ self .form .removeCustomRepositoryButton .clicked .connect (
78
+ self ._remove_custom_repo_clicked
79
+ )
80
+ self .form .customRepositoriesTableView .doubleClicked .connect (
81
+ self ._row_double_clicked
82
+ )
48
83
49
84
def saveSettings (self ):
50
85
"""Required function: called by the preferences dialog when Apply or Save is clicked,
51
86
saves out the preference data by reading it from the widgets."""
52
87
for widget in self .form .children ():
53
88
self .recursive_widget_saver (widget )
89
+ self .table_model .save_model ()
54
90
55
91
def recursive_widget_saver (self , widget ):
56
92
"""Writes out the data for this widget and all of its children, recursively."""
@@ -79,7 +115,7 @@ def recursive_widget_saver(self, widget):
79
115
text = widget .text ()
80
116
pref .SetString (str (pref_entry , "utf-8" ), text )
81
117
elif widget .metaObject ().className () == "Gui::PrefFileChooser" :
82
- filename = str (widget .property ("fileName" ), "utf-8" )
118
+ filename = str (widget .property ("fileName" ))
83
119
filename = pref .SetString (str (pref_entry , "utf-8" ), filename )
84
120
85
121
# Recurse over children
@@ -92,6 +128,7 @@ def loadSettings(self):
92
128
loads the preference data and assigns it to the widgets."""
93
129
for widget in self .form .children ():
94
130
self .recursive_widget_loader (widget )
131
+ self .table_model .load_model ()
95
132
96
133
def recursive_widget_loader (self , widget ):
97
134
"""Loads the data for this widget and all of its children, recursively."""
@@ -126,3 +163,160 @@ def recursive_widget_loader(self, widget):
126
163
if isinstance (widget , QtCore .QObject ):
127
164
for child in widget .children ():
128
165
self .recursive_widget_loader (child )
166
+
167
+ def _add_custom_repo_clicked (self ):
168
+ """Callback: show the Add custom repo dialog"""
169
+ dlg = CustomRepositoryDialog ()
170
+ url , branch = dlg .exec ()
171
+ if url and branch :
172
+ self .table_model .appendData (url , branch )
173
+
174
+ def _remove_custom_repo_clicked (self ):
175
+ """Callback: when the remove button is clicked, get the current selection and remove it."""
176
+ item = self .form .customRepositoriesTableView .currentIndex ()
177
+ if not item .isValid ():
178
+ return
179
+ row = item .row ()
180
+ self .table_model .removeRows (row , 1 , QtCore .QModelIndex ())
181
+
182
+ def _row_double_clicked (self , item ):
183
+ """Edit the row that was double-clicked"""
184
+ row = item .row ()
185
+ dlg = CustomRepositoryDialog ()
186
+ url_index = self .table_model .createIndex (row , 0 )
187
+ branch_index = self .table_model .createIndex (row , 1 )
188
+ dlg .dialog .urlLineEdit .setText (self .table_model .data (url_index ))
189
+ dlg .dialog .branchLineEdit .setText (self .table_model .data (branch_index ))
190
+ url , branch = dlg .exec ()
191
+ if url and branch :
192
+ self .table_model .setData (url_index , url )
193
+ self .table_model .setData (branch_index , branch )
194
+
195
+
196
+ class CustomRepoDataModel (QtCore .QAbstractTableModel ):
197
+ """The model for the custom repositories: wraps the underlying preference data and uses that
198
+ as its main data store."""
199
+
200
+ def __init__ (self ):
201
+ super ().__init__ ()
202
+ pref_access_string = "User parameter:BaseApp/Preferences/Addons"
203
+ self .pref = FreeCAD .ParamGet (pref_access_string )
204
+ self .load_model ()
205
+
206
+ def load_model (self ):
207
+ """Load the data from the preferences entry"""
208
+ pref_entry : str = self .pref .GetString ("CustomRepositories" , "" )
209
+
210
+ # The entry is saved as a space- and newline-delimited text block: break it into its
211
+ # constituent parts
212
+ lines = pref_entry .split ("\n " )
213
+ self .model = []
214
+ for line in lines :
215
+ if not line :
216
+ continue
217
+ split_data = line .split ()
218
+ if len (split_data ) > 1 :
219
+ branch = split_data [1 ]
220
+ else :
221
+ branch = "master"
222
+ url = split_data [0 ]
223
+ self .model .append ([url , branch ])
224
+
225
+ def save_model (self ):
226
+ """Save the data into a preferences entry"""
227
+ entry = ""
228
+ for row in self .model :
229
+ entry += f"{ row [0 ]} { row [1 ]} \n "
230
+ self .pref .SetString ("CustomRepositories" , entry )
231
+
232
+ def rowCount (self , parent : QtCore .QModelIndex = QtCore .QModelIndex ()) -> int :
233
+ """The number of rows"""
234
+ if parent .isValid ():
235
+ return 0
236
+ return len (self .model )
237
+
238
+ def columnCount (self , parent : QtCore .QModelIndex = QtCore .QModelIndex ()) -> int :
239
+ """The number of columns (which is always 2)"""
240
+ if parent .isValid ():
241
+ return 0
242
+ return 2
243
+
244
+ def data (self , index , role = QtCore .Qt .DisplayRole ):
245
+ """The data at an index."""
246
+ if role != QtCore .Qt .DisplayRole :
247
+ return None
248
+ row = index .row ()
249
+ column = index .column ()
250
+ if row > len (self .model ):
251
+ return None
252
+ if column > 1 :
253
+ return None
254
+ return self .model [row ][column ]
255
+
256
+ def headerData (self , section , orientation , role = QtCore .Qt .DisplayRole ):
257
+ """Get the row and column header data."""
258
+ if role != QtCore .Qt .DisplayRole :
259
+ return None
260
+ if orientation == QtCore .Qt .Vertical :
261
+ return section + 1
262
+ if section == 0 :
263
+ return translate (
264
+ "AddonsInstaller" ,
265
+ "Repository URL" ,
266
+ "Preferences header for custom repositories" ,
267
+ )
268
+ if section == 1 :
269
+ return translate (
270
+ "AddonsInstaller" ,
271
+ "Branch name" ,
272
+ "Preferences header for custom repositories" ,
273
+ )
274
+ return None
275
+
276
+ def removeRows (self , row , count , parent ):
277
+ """Remove rows"""
278
+ self .beginRemoveRows (parent , row , row + count - 1 )
279
+ for _ in range (count ):
280
+ self .model .pop (row )
281
+ self .endRemoveRows ()
282
+
283
+ def insertRows (self , row , count , parent ):
284
+ """Insert blank rows"""
285
+ self .beginInsertRows (parent , row , row + count - 1 )
286
+ for _ in range (count ):
287
+ self .model .insert (["" , "" ])
288
+ self .endInsertRows ()
289
+
290
+ def appendData (self , url , branch ):
291
+ """Append this url and branch to the end of the list"""
292
+ row = self .rowCount ()
293
+ self .beginInsertRows (QtCore .QModelIndex (), row , row )
294
+ self .model .append ([url , branch ])
295
+ self .endInsertRows ()
296
+
297
+ def setData (self , index , value , role = QtCore .Qt .EditRole ):
298
+ """Set the data at this index"""
299
+ if role != QtCore .Qt .EditRole :
300
+ return
301
+ self .model [index .row ()][index .column ()] = value
302
+ self .dataChanged .emit (index , index )
303
+
304
+
305
+ class CustomRepositoryDialog :
306
+ """A dialog for setting up a custom repository, with branch information"""
307
+
308
+ def __init__ (self ):
309
+ self .dialog = FreeCADGui .PySideUic .loadUi (
310
+ os .path .join (
311
+ os .path .dirname (__file__ ), "AddonManagerOptions_AddCustomRepository.ui"
312
+ )
313
+ )
314
+
315
+ def exec (self ):
316
+ """Run the dialog modally, and return either None or a tuple or (url,branch)"""
317
+ result = self .dialog .exec ()
318
+ if result == QDialog .Accepted :
319
+ url = self .dialog .urlLineEdit .text ()
320
+ branch = self .dialog .branchLineEdit .text ()
321
+ return (url , branch )
322
+ return (None , None )
0 commit comments