Files
create/src/Mod/AddonManager/AddonManager.py
luz paz 7417d9ec48 AddonManager: Consolidate preference in user.cfg from Plugins to Addons
Currently when a user first uses the Addon Manager a dialog popup
prompts them about how FC is not liable for the code plugin authors
provide. It stores the binary response in a `readWarning` boolean
within the user.cfg file.

This commit changes the location of said boolean from
`User parameter:Plugins` to `User parameter:Addons`.

It also migrates old the old setting if available.

Co-authored-by: Benjamin Nauck <benjamin@nauck.se>
Add suggestions from @chennes

Co-authored-by: Chris Hennes <chennes@pioneerlibrarysystem.org>
2021-08-20 10:59:23 -04:00

631 lines
29 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#***************************************************************************
#* *
#* Copyright (c) 2015 Yorik van Havre <yorik@uncreated.net> *
#* *
#* This program is free software; you can redistribute it and/or modify *
#* it under the terms of the GNU Lesser General Public License (LGPL) *
#* as published by the Free Software Foundation; either version 2 of *
#* the License, or (at your option) any later version. *
#* for detail see the LICENCE text file. *
#* *
#* 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 Library General Public License for more details. *
#* *
#* You should have received a copy of the GNU Library General Public *
#* License along with this program; if not, write to the Free Software *
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
#* USA *
#* *
#***************************************************************************
from __future__ import print_function
import os
import shutil
import stat
import tempfile
from PySide import QtGui, QtCore
import FreeCADGui
from addonmanager_utilities import translate # this needs to be as is for pylupdate
from addonmanager_workers import *
import addonmanager_utilities as utils
import AddonManager_rc
__title__ = "FreeCAD Addon Manager Module"
__author__ = "Yorik van Havre", "Jonathan Wiedemann", "Kurt Kremitzki"
__url__ = "http://www.freecadweb.org"
"""
FreeCAD Addon Manager Module
It will fetch its contents from https://github.com/FreeCAD/FreeCAD-addons
You need a working internet connection, and optionally the GitPython package
installed.
"""
# \defgroup ADDONMANAGER AddonManager
# \ingroup ADDONMANAGER
# \brief The Addon Manager allows to install workbenches and macros made by users
# @{
def QT_TRANSLATE_NOOP(ctx, txt):
return txt
class CommandAddonManager:
"""The main Addon Manager class and FreeCAD command"""
def GetResources(self):
return {"Pixmap": "AddonManager",
"MenuText": QT_TRANSLATE_NOOP("Std_AddonMgr", "&Addon manager"),
"ToolTip": QT_TRANSLATE_NOOP("Std_AddonMgr", "Manage external workbenches and macros"),
"Group": "Tools"}
def Activated(self):
# display first use dialog if needed
readWarningParameter = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
readWarning = readWarningParameter.GetBool("readWarning", False)
newReadWarningParameter = FreeCAD.ParamGet("User parameter:Plugins/addonsRepository")
readWarning |= newReadWarningParameter.GetBool("readWarning", False)
if not readWarning:
if (QtGui.QMessageBox.warning(None,
"FreeCAD",
translate("AddonsInstaller",
"The addons that can be installed here are not "
"officially part of FreeCAD, and are not reviewed "
"by the FreeCAD team. Make sure you know what you "
"are installing!"),
QtGui.QMessageBox.Cancel |
QtGui.QMessageBox.Ok) !=
QtGui.QMessageBox.StandardButton.Cancel):
readWarningParameter.SetBool("readWarning", True)
readWarning = True
if readWarning:
self.launch()
def launch(self):
"""Shows the Addon Manager UI"""
# create the dialog
self.dialog = FreeCADGui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__),
"AddonManager.ui"))
# cleanup the leftovers from previous runs
self.repos = []
self.macros = []
self.macro_repo_dir = tempfile.mkdtemp()
self.doUpdate = []
self.addon_removed = False
for worker in ["update_worker", "check_worker", "show_worker",
"showmacro_worker", "macro_worker", "install_worker"]:
if hasattr(self, worker):
thread = getattr(self, worker)
if thread:
if thread.isFinished():
setattr(self, worker, None)
self.dialog.tabWidget.setCurrentIndex(0)
# these 2 settings to prevent loading an addon description on start (let the user click something first)
self.firsttime = True
self.firstmacro = True
# restore window geometry and splitter state from stored state
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
w = pref.GetInt("WindowWidth", 600)
h = pref.GetInt("WindowHeight", 480)
self.dialog.resize(w, h)
sl = pref.GetInt("SplitterLeft", 298)
sr = pref.GetInt("SplitterRight", 274)
self.dialog.splitter.setSizes([sl, sr])
# set nice icons to everything, by theme with fallback to FreeCAD icons
self.dialog.setWindowIcon(QtGui.QIcon(":/icons/AddonManager.svg"))
self.dialog.buttonExecute.setIcon(QtGui.QIcon.fromTheme("execute", QtGui.QIcon(":/icons/button_valid.svg")))
self.dialog.buttonUninstall.setIcon(QtGui.QIcon.fromTheme("cancel", QtGui.QIcon(":/icons/edit_Cancel.svg")))
self.dialog.buttonInstall.setIcon(QtGui.QIcon.fromTheme("download", QtGui.QIcon(":/icons/edit_OK.svg")))
self.dialog.buttonUpdateAll.setIcon(QtGui.QIcon(":/icons/button_valid.svg"))
self.dialog.buttonConfigure.setIcon(QtGui.QIcon(":/icons/preferences-system.svg"))
self.dialog.buttonClose.setIcon(QtGui.QIcon.fromTheme("close", QtGui.QIcon(":/icons/process-stop.svg")))
self.dialog.tabWidget.setTabIcon(0, QtGui.QIcon.fromTheme("folder", QtGui.QIcon(":/icons/folder.svg")))
self.dialog.tabWidget.setTabIcon(1, QtGui.QIcon(":/icons/applications-python.svg"))
# enable/disable stuff
self.dialog.buttonExecute.setEnabled(False)
self.dialog.buttonUninstall.setEnabled(False)
self.dialog.buttonInstall.setEnabled(False)
self.dialog.buttonUpdateAll.setEnabled(False)
# connect slots
self.dialog.buttonExecute.clicked.connect(self.executemacro)
self.dialog.rejected.connect(self.reject)
self.dialog.buttonInstall.clicked.connect(self.install)
self.dialog.buttonUninstall.clicked.connect(self.remove)
self.dialog.buttonUpdateAll.clicked.connect(self.apply_updates)
self.dialog.listWorkbenches.currentRowChanged.connect(self.show)
self.dialog.tabWidget.currentChanged.connect(self.switchtab)
self.dialog.listMacros.currentRowChanged.connect(self.show_macro)
self.dialog.buttonConfigure.clicked.connect(self.show_config)
self.dialog.buttonClose.clicked.connect(self.dialog.reject)
# allow to open links in browser
self.dialog.description.setOpenLinks(True)
self.dialog.description.setOpenExternalLinks(True)
# center the dialog over the FreeCAD window
mw = FreeCADGui.getMainWindow()
self.dialog.move(mw.frameGeometry().topLeft() + mw.rect().center() - self.dialog.rect().center())
# populate the list
self.update()
# rock 'n roll!!!
self.dialog.exec_()
def reject(self):
"""called when the window has been closed"""
# save window geometry and splitter state for next use
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetInt("WindowWidth", self.dialog.width())
pref.SetInt("WindowHeight", self.dialog.height())
pref.SetInt("SplitterLeft", self.dialog.splitter.sizes()[0])
pref.SetInt("SplitterRight", self.dialog.splitter.sizes()[1])
# ensure all threads are finished before closing
oktoclose = True
for worker in ["update_worker", "check_worker", "show_worker", "showmacro_worker",
"macro_worker", "install_worker"]:
if hasattr(self, worker):
thread = getattr(self, worker)
if thread:
if not thread.isFinished():
oktoclose = False
# all threads have finished
if oktoclose:
if ((hasattr(self, "install_worker") and self.install_worker) or
(hasattr(self, "addon_removed") and self.addon_removed)):
# display restart dialog
m = QtGui.QMessageBox()
m.setWindowTitle(translate("AddonsInstaller", "Addon manager"))
m.setWindowIcon(QtGui.QIcon(":/icons/AddonManager.svg"))
m.setText(translate("AddonsInstaller",
"You must restart FreeCAD for changes to take "
"effect. Press Ok to restart FreeCAD now, or "
"Cancel to restart later."))
m.setIcon(m.Warning)
m.setStandardButtons(m.Ok | m.Cancel)
m.setDefaultButton(m.Cancel)
ret = m.exec_()
if ret == m.Ok:
shutil.rmtree(self.macro_repo_dir, onerror=self.remove_readonly)
# restart FreeCAD after a delay to give time to this dialog to close
QtCore.QTimer.singleShot(1000, utils.restart_freecad)
try:
shutil.rmtree(self.macro_repo_dir, onerror=self.remove_readonly)
except Exception:
pass
return True
def update(self):
"""updates the list of workbenches"""
self.dialog.listWorkbenches.clear()
self.dialog.buttonExecute.setEnabled(False)
self.repos = []
self.update_worker = UpdateWorker()
self.update_worker.info_label.connect(self.show_information)
self.update_worker.addon_repo.connect(self.add_addon_repo)
self.update_worker.progressbar_show.connect(self.show_progress_bar)
self.update_worker.done.connect(self.check_updates)
self.update_worker.start()
def check_updates(self):
"checks every installed addon for available updates"
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
if pref.GetBool("AutoCheck", False) and not self.doUpdate:
if hasattr(self, "check_worker"):
thread = self.check_worker
if thread:
if not thread.isFinished():
return
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "Checking for updates..."))
self.check_worker = CheckWBWorker(self.repos)
self.check_worker.mark.connect(self.mark)
self.check_worker.enable.connect(self.enable_updates)
self.check_worker.addon_repos.connect(self.update_repos)
self.check_worker.start()
def apply_updates(self):
"""apply all available updates"""
if self.doUpdate:
self.install(self.doUpdate)
self.dialog.buttonUpdateAll.setEnabled(False)
def enable_updates(self, num):
"""enables the update button"""
if num:
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "Apply") +
" " + str(num) + " " +
translate("AddonsInstaller", "update(s)"))
self.dialog.buttonUpdateAll.setEnabled(True)
else:
self.dialog.buttonUpdateAll.setText(translate("AddonsInstaller", "No update available"))
self.dialog.buttonUpdateAll.setEnabled(False)
def add_addon_repo(self, addon_repo):
"""adds a workbench to the list"""
self.repos.append(addon_repo)
addonicon = self.get_icon(addon_repo[0])
if addon_repo[2] > 0:
item = QtGui.QListWidgetItem(addonicon,
str(addon_repo[0]) +
str(" (" +
translate("AddonsInstaller", "Installed") +
")"))
item.setForeground(QtGui.QBrush(QtGui.QColor(0, 182, 41)))
self.dialog.listWorkbenches.addItem(item)
else:
self.dialog.listWorkbenches.addItem(QtGui.QListWidgetItem(addonicon, str(addon_repo[0])))
def get_icon(self, repo):
"""returns an icon for a repo"""
path = ":/icons/" + repo + "_workbench_icon.svg"
if QtCore.QFile.exists(path):
addonicon = QtGui.QIcon(path)
else:
addonicon = QtGui.QIcon(":/icons/document-package.svg")
if addonicon.isNull():
addonicon = QtGui.QIcon(":/icons/document-package.svg")
return addonicon
def show_information(self, label):
"""shows text in the information pane"""
self.dialog.description.setText(label)
if self.dialog.listWorkbenches.isVisible():
self.dialog.listWorkbenches.setFocus()
else:
self.dialog.listMacros.setFocus()
def show(self, idx):
"""loads information of a given workbench"""
# this function is triggered also when the list is populated, prevent that here
if idx == 0 and self.firsttime:
self.dialog.listWorkbenches.setCurrentRow(-1)
self.firsttime = False
return
if self.repos and idx >= 0:
if hasattr(self, "show_worker"):
# kill existing show worker (might still be busy loading images...)
if self.show_worker:
self.show_worker.exit()
self.show_worker.stopImageLoading()
# wait until the thread stops
while True:
if self.show_worker.isFinished():
break
self.show_worker = ShowWorker(self.repos, idx)
self.show_worker.info_label.connect(self.show_information)
self.show_worker.addon_repos.connect(self.update_repos)
self.show_worker.progressbar_show.connect(self.show_progress_bar)
self.show_worker.start()
self.dialog.buttonInstall.setEnabled(True)
self.dialog.buttonUninstall.setEnabled(True)
def show_macro(self, idx):
"""loads information of a given macro"""
# this function is triggered when the list is populated, prevent that here
if idx == 0 and self.firstmacro:
self.dialog.listMacros.setCurrentRow(-1)
self.firstmacro = False
return
if self.macros and idx >= 0:
if hasattr(self, "showmacro_worker"):
if self.showmacro_worker:
if not self.showmacro_worker.isFinished():
self.showmacro_worker.exit()
if not self.showmacro_worker.isFinished():
return
self.showmacro_worker = GetMacroDetailsWorker(self.macros[idx])
self.showmacro_worker.info_label.connect(self.show_information)
self.showmacro_worker.progressbar_show.connect(self.show_progress_bar)
self.showmacro_worker.start()
self.dialog.buttonInstall.setEnabled(True)
self.dialog.buttonUninstall.setEnabled(True)
if self.macros[idx].is_installed():
self.dialog.buttonExecute.setEnabled(True)
else:
self.dialog.buttonExecute.setEnabled(False)
def switchtab(self, idx):
"""does what needs to be done when switching tabs"""
if idx == 1:
if not self.macros:
self.dialog.listMacros.clear()
self.macros = []
self.macro_worker = FillMacroListWorker(self.macro_repo_dir)
self.macro_worker.add_macro_signal.connect(self.add_macro)
self.macro_worker.info_label_signal.connect(self.show_information)
self.macro_worker.progressbar_show.connect(self.show_progress_bar)
self.macro_worker.start()
self.dialog.listMacros.setCurrentRow(0)
def update_repos(self, repos):
"""this function allows threads to update the main list of workbenches"""
self.repos = repos
def add_macro(self, macro):
"""adds a macro to the list"""
if macro.name:
if macro in self.macros:
# The macro is already in the list of macros.
old_macro = self.macros[self.macros.index(macro)]
utils.update_macro_details(old_macro, macro)
else:
self.macros.append(macro)
path = ":/icons/" + macro.name.replace(" ", "_") + "_macro_icon.svg"
if QtCore.QFile.exists(path):
addonicon = QtGui.QIcon(path)
else:
addonicon = QtGui.QIcon(":/icons/document-python.svg")
if addonicon.isNull():
addonicon = QtGui.QIcon(":/icons/document-python.svg")
if macro.is_installed():
item = QtGui.QListWidgetItem(addonicon, macro.name + str(" (Installed)"))
item.setForeground(QtGui.QBrush(QtGui.QColor(0, 182, 41)))
self.dialog.listMacros.addItem(item)
else:
self.dialog.listMacros.addItem(QtGui.QListWidgetItem(addonicon, macro.name))
def install(self, repos=None):
"""installs a workbench or macro"""
if self.dialog.tabWidget.currentIndex() == 0:
# Tab "Workbenches".
idx = None
if repos:
idx = []
for repo in repos:
for i, r in enumerate(self.repos):
if r[0] == repo:
idx.append(i)
else:
idx = self.dialog.listWorkbenches.currentRow()
if idx is not None:
if hasattr(self, "install_worker") and self.install_worker:
if self.install_worker.isRunning():
return
self.install_worker = InstallWorker(self.repos, idx)
self.install_worker.info_label.connect(self.show_information)
self.install_worker.progressbar_show.connect(self.show_progress_bar)
self.install_worker.mark_recompute.connect(self.mark_recompute)
self.install_worker.start()
elif self.dialog.tabWidget.currentIndex() == 1:
# Tab "Macros".
macro = self.macros[self.dialog.listMacros.currentRow()]
if utils.install_macro(macro, self.macro_repo_dir):
self.dialog.description.setText(translate("AddonsInstaller",
"Macro successfully installed. The macro is "
"now available from the Macros dialog."))
else:
self.dialog.description.setText(translate("AddonsInstaller", "Unable to install"))
def show_progress_bar(self, state):
"""shows or hides the progress bar"""
if state:
self.dialog.tabWidget.setEnabled(False)
self.dialog.buttonInstall.setEnabled(False)
self.dialog.buttonUninstall.setEnabled(False)
self.dialog.progressBar.show()
else:
self.dialog.progressBar.hide()
self.dialog.tabWidget.setEnabled(True)
if not (self.firsttime and self.firstmacro):
self.dialog.buttonInstall.setEnabled(True)
self.dialog.buttonUninstall.setEnabled(True)
if self.dialog.listWorkbenches.isVisible():
self.dialog.listWorkbenches.setFocus()
else:
self.dialog.listMacros.setFocus()
def executemacro(self):
"""executes a selected macro"""
if self.dialog.tabWidget.currentIndex() == 1:
# Tab "Macros".
macro = self.macros[self.dialog.listMacros.currentRow()]
if not macro.is_installed():
# Macro not installed, nothing to do.
return
macro_path = os.path.join(FreeCAD.getUserMacroDir(True), macro.filename)
if os.path.exists(macro_path):
macro_path = macro_path.replace("\\", "/")
FreeCADGui.open(str(macro_path))
self.dialog.hide()
FreeCADGui.SendMsgToActiveView("Run")
else:
self.dialog.buttonExecute.setEnabled(False)
def remove_readonly(self, func, path, _):
"""Remove a read-only file."""
os.chmod(path, stat.S_IWRITE)
func(path)
def remove(self):
"""uninstalls a macro or workbench"""
if self.dialog.tabWidget.currentIndex() == 0:
# Tab "Workbenches".
idx = self.dialog.listWorkbenches.currentRow()
basedir = FreeCAD.getUserAppDataDir()
moddir = basedir + os.sep + "Mod"
clonedir = moddir + os.sep + self.repos[idx][0]
if os.path.exists(clonedir):
shutil.rmtree(clonedir, onerror=self.remove_readonly)
self.dialog.description.setText(translate("AddonsInstaller",
"Addon successfully removed. Please restart FreeCAD"))
else:
self.dialog.description.setText(translate("AddonsInstaller", "Unable to remove this addon"))
elif self.dialog.tabWidget.currentIndex() == 1:
# Tab "Macros".
macro = self.macros[self.dialog.listMacros.currentRow()]
if utils.remove_macro(macro):
self.dialog.description.setText(translate("AddonsInstaller", "Macro successfully removed."))
else:
self.dialog.description.setText(translate("AddonsInstaller", "Macro could not be removed."))
self.update_status(soft=True)
self.addon_removed = True # A value to trigger the restart message on dialog close
def mark_recompute(self, addon):
"""marks an addon in the list as installed but needs recompute"""
for i in range(self.dialog.listWorkbenches.count()):
txt = self.dialog.listWorkbenches.item(i).text().strip()
if txt.endswith(" ("+translate("AddonsInstaller", "Installed")+")"):
txt = txt[:-12]
elif txt.endswith(" ("+translate("AddonsInstaller", "Update available")+")"):
txt = txt[:-19]
if txt == addon:
self.dialog.listWorkbenches.item(i).setText(txt + " (" +
translate("AddonsInstaller",
"Restart required") +
")")
self.dialog.listWorkbenches.item(i).setIcon(QtGui.QIcon(":/icons/edit-undo.svg"))
def update_status(self, soft=False):
"""Updates the list of workbenches/macros. If soft is true, items
are not recreated (and therefore display text isn't triggered)"
"""
moddir = FreeCAD.getUserAppDataDir() + os.sep + "Mod"
if soft:
for i in range(self.dialog.listWorkbenches.count()):
txt = self.dialog.listWorkbenches.item(i).text().strip()
ext = ""
if txt.endswith(" ("+translate("AddonsInstaller", "Installed")+")"):
txt = txt[:-12]
ext = " ("+translate("AddonsInstaller", "Installed")+")"
elif txt.endswith(" ("+translate("AddonsInstaller", "Update available")+")"):
txt = txt[:-19]
ext = " ("+translate("AddonsInstaller", "Update available")+")"
elif txt.endswith(" ("+translate("AddonsInstaller", "Restart required")+")"):
txt = txt[:-19]
ext = " ("+translate("AddonsInstaller", "Restart required")+")"
if os.path.exists(os.path.join(moddir, txt)):
self.dialog.listWorkbenches.item(i).setText(txt+ext)
else:
self.dialog.listWorkbenches.item(i).setText(txt)
self.dialog.listWorkbenches.item(i).setIcon(self.get_icon(txt))
for i in range(self.dialog.listMacros.count()):
txt = self.dialog.listMacros.item(i).text().strip()
if txt.endswith(" ("+translate("AddonsInstaller", "Installed")+")"):
txt = txt[:-12]
elif txt.endswith(" ("+translate("AddonsInstaller", "Update available")+")"):
txt = txt[:-19]
if os.path.exists(os.path.join(moddir, txt)):
self.dialog.listMacros.item(i).setText(txt+ext)
else:
self.dialog.listMacros.item(i).setText(txt)
self.dialog.listMacros.item(i).setIcon(QtGui.QIcon(":/icons/document-python.svg"))
else:
self.dialog.listWorkbenches.clear()
self.dialog.listMacros.clear()
for wb in self.repos:
if os.path.exists(os.path.join(moddir, wb[0])):
self.dialog.listWorkbenches.addItem(
QtGui.QListWidgetItem(QtGui.QIcon(":/icons/button_valid.svg"),
str(wb[0]) + " (" +
translate("AddonsInstaller", "Installed") + ")"))
wb[2] = 1
else:
self.dialog.listWorkbenches.addItem(
QtGui.QListWidgetItem(QtGui.QIcon(":/icons/document-python.svg"), str(wb[0])))
wb[2] = 0
for macro in self.macros:
if macro.is_installed():
self.dialog.listMacros.addItem(item)
else:
self.dialog.listMacros.addItem(
QtGui.QListWidgetItem(QtGui.QIcon(":/icons/document-python.svg"), macro.name))
def mark(self, repo):
"""mark a workbench as updatable"""
for i in range(self.dialog.listWorkbenches.count()):
w = self.dialog.listWorkbenches.item(i)
if (w.text() == str(repo)) or w.text().startswith(str(repo)+" "):
w.setText(str(repo) + str(" ("+translate("AddonsInstaller", "Update available")+")"))
w.setForeground(QtGui.QBrush(QtGui.QColor(182, 90, 0)))
if repo not in self.doUpdate:
self.doUpdate.append(repo)
def show_config(self):
"""shows the configuration dialog"""
self.config = FreeCADGui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__), "AddonManagerOptions.ui"))
# restore stored values
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
self.config.checkUpdates.setChecked(pref.GetBool("AutoCheck", False))
self.config.customRepositories.setPlainText(pref.GetString("CustomRepositories", ""))
self.config.radioButtonNoProxy.setChecked(pref.GetBool("NoProxyCheck", True))
self.config.radioButtonSystemProxy.setChecked(pref.GetBool("SystemProxyCheck", False))
self.config.radioButtonUserProxy.setChecked(pref.GetBool("UserProxyCheck", False))
self.config.userProxy.setPlainText(pref.GetString("ProxyUrl", ""))
# center the dialog over the Addon Manager
self.config.move(self.dialog.frameGeometry().topLeft() +
self.dialog.rect().center() -
self.config.rect().center())
ret = self.config.exec_()
if ret:
# OK button has been pressed
pref.SetBool("AutoCheck", self.config.checkUpdates.isChecked())
pref.SetString("CustomRepositories", self.config.customRepositories.toPlainText())
pref.SetBool("NoProxyCheck", self.config.radioButtonNoProxy.isChecked())
pref.SetBool("SystemProxyCheck", self.config.radioButtonSystemProxy.isChecked())
pref.SetBool("UserProxyCheck", self.config.radioButtonUserProxy.isChecked())
pref.SetString("ProxyUrl", self.config.userProxy.toPlainText())
def check_updates(addon_name, callback):
"""Checks for updates for a given addon"""
oname = "update_checker_"+addon_name
setattr(FreeCAD, oname, CheckSingleWorker(addon_name))
getattr(FreeCAD, oname).updateAvailable.connect(callback)
getattr(FreeCAD, oname).start()
# @}