diff --git a/CMakeLists.txt b/CMakeLists.txt index eeac7d5bbb2d..926e5d5c15e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,7 @@ endif(APPLE) OPTION(BUILD_FEM "Build the FreeCAD FEM module" ON) OPTION(BUILD_SANDBOX "Build the FreeCAD Sandbox module which is only for testing purposes" OFF) OPTION(BUILD_TEMPLATE "Build the FreeCAD template module which is only for testing purposes" OFF) +OPTION(BUILD_ADDONMGR "Build the FreeCAD addon manager module" ON) OPTION(BUILD_ARCH "Build the FreeCAD Architecture module" ON) OPTION(BUILD_ASSEMBLY "Build the FreeCAD Assembly module" OFF) OPTION(BUILD_COMPLETE "Build the FreeCAD complete module" ON) diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index 1003377e5dae..440cdf981416 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -22,6 +22,10 @@ if (BUILD_VR) add_definitions(-DBUILD_VR ) endif(BUILD_VR) +if (BUILD_ADDONMGR) + add_definitions(-DBUILD_ADDONMGR ) +endif(BUILD_ADDONMGR) + include_directories( ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/src/Gui/Icons/AddonManager.svg b/src/Gui/Icons/AddonManager.svg new file mode 100644 index 000000000000..2c7537fe7741 --- /dev/null +++ b/src/Gui/Icons/AddonManager.svg @@ -0,0 +1,378 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/Icons/resource.qrc b/src/Gui/Icons/resource.qrc index 4c052c06ecdd..acbef34ba3d0 100644 --- a/src/Gui/Icons/resource.qrc +++ b/src/Gui/Icons/resource.qrc @@ -149,6 +149,7 @@ WhatsThis.svg colors.svg px.svg + AddonManager.svg diff --git a/src/Gui/Workbench.cpp b/src/Gui/Workbench.cpp index c616d2868d66..4138fd8c8386 100644 --- a/src/Gui/Workbench.cpp +++ b/src/Gui/Workbench.cpp @@ -560,6 +560,9 @@ MenuItem* StdWorkbench::setupMenuBar() const << "Std_ExportGraphviz" << "Std_ProjectUtil" << "Separator" << "Std_MeasureDistance" << "Separator" << "Std_DemoMode" << "Std_UnitsCalculator" << "Separator" << "Std_DlgCustomize"; +#ifdef BUILD_ADDONMGR + *tool << "Std_AddonMgr"; +#endif // Macro MenuItem* macro = new MenuItem( menuBar ); diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py new file mode 100644 index 000000000000..0967873f2057 --- /dev/null +++ b/src/Mod/AddonManager/AddonManager.py @@ -0,0 +1,577 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +#*************************************************************************** +#* * +#* Copyright (c) 2015 Yorik van Havre * +#* * +#* 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 + +__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 the python-git package +installed. +''' + +from PySide import QtCore, QtGui +import FreeCAD,urllib2,re,os,shutil + +NOGIT = False # for debugging purposes, set this to True to always use http downloads + +MACROS_BLACKLIST = ["BOLTS","WorkFeatures","how to install","PartsLibrary"] + +def symlink(source, link_name): + if os.path.exists(link_name): + print("symlink already exists") + else: + os_symlink = getattr(os, "symlink", None) + if callable(os_symlink): + os_symlink(source, link_name) + else: + import ctypes + csl = ctypes.windll.kernel32.CreateSymbolicLinkW + csl.argtypes = (ctypes.c_wchar_p, ctypes.c_wchar_p, ctypes.c_uint32) + csl.restype = ctypes.c_ubyte + flags = 1 if os.path.isdir(source) else 0 + if csl(link_name, source, flags) == 0: + raise ctypes.WinError() + + +class AddonsInstaller(QtGui.QDialog): + + def __init__(self): + QtGui.QDialog.__init__(self) + self.repos = [] + self.macros = [] + self.setObjectName("AddonsInstaller") + self.resize(326, 304) + self.verticalLayout = QtGui.QVBoxLayout(self) + self.tabWidget = QtGui.QTabWidget() + self.verticalLayout.addWidget(self.tabWidget) + self.listWorkbenches = QtGui.QListWidget() + self.listWorkbenches.setIconSize(QtCore.QSize(16,16)) + self.tabWidget.addTab(self.listWorkbenches,"") + self.listMacros = QtGui.QListWidget() + self.listMacros.setIconSize(QtCore.QSize(16,16)) + self.tabWidget.addTab(self.listMacros,"") + self.labelDescription = QtGui.QLabel() + self.labelDescription.setMinimumSize(QtCore.QSize(0, 75)) + self.labelDescription.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.labelDescription.setWordWrap(True) + self.verticalLayout.addWidget(self.labelDescription) + + self.progressBar = QtGui.QProgressBar(self) + #self.progressBar.setProperty("value", 24) + self.progressBar.setObjectName("progressBar") + #self.progressBar.hide() + self.progressBar.setRange(0,0) + self.verticalLayout.addWidget(self.progressBar) + + self.horizontalLayout = QtGui.QHBoxLayout() + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.horizontalLayout.addItem(spacerItem) + self.buttonInstall = QtGui.QPushButton() + icon = QtGui.QIcon.fromTheme("download") + self.buttonInstall.setIcon(icon) + self.horizontalLayout.addWidget(self.buttonInstall) + self.buttonRemove = QtGui.QPushButton() + icon = QtGui.QIcon.fromTheme("edit-delete") + self.buttonRemove.setIcon(icon) + self.horizontalLayout.addWidget(self.buttonRemove) + self.buttonCancel = QtGui.QPushButton() + icon = QtGui.QIcon.fromTheme("cancel") + self.buttonCancel.setIcon(icon) + self.horizontalLayout.addWidget(self.buttonCancel) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi() + + QtCore.QObject.connect(self.buttonCancel, QtCore.SIGNAL("clicked()"), self.reject) + QtCore.QObject.connect(self.buttonInstall, QtCore.SIGNAL("clicked()"), self.install) + QtCore.QObject.connect(self.buttonRemove, QtCore.SIGNAL("clicked()"), self.remove) + QtCore.QObject.connect(self.labelDescription, QtCore.SIGNAL("linkActivated(QString)"), self.showlink) + QtCore.QObject.connect(self.listWorkbenches, QtCore.SIGNAL("currentRowChanged(int)"), self.show) + QtCore.QObject.connect(self.tabWidget, QtCore.SIGNAL("currentChanged(int)"), self.switchtab) + QtCore.QObject.connect(self.listMacros, QtCore.SIGNAL("currentRowChanged(int)"), self.show_macro) + QtCore.QMetaObject.connectSlotsByName(self) + self.update() + + def retranslateUi(self): + self.setWindowTitle(QtGui.QApplication.translate("AddonsInstaller", + "Addon manager", None, QtGui.QApplication.UnicodeUTF8)) + self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Downloading addon list...", None, QtGui.QApplication.UnicodeUTF8)) + self.buttonCancel.setText(QtGui.QApplication.translate("AddonsInstaller", "Close", None, QtGui.QApplication.UnicodeUTF8)) + self.buttonInstall.setText(QtGui.QApplication.translate("AddonsInstaller", "Install / update", None, QtGui.QApplication.UnicodeUTF8)) + self.buttonRemove.setText(QtGui.QApplication.translate("AddonsInstaller", "Remove", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.listWorkbenches), QtGui.QApplication.translate("AddonsInstaller", "Workbenches", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.listMacros), QtGui.QApplication.translate("AddonsInstaller", "Macros", None, QtGui.QApplication.UnicodeUTF8)) + + def update(self): + self.listWorkbenches.clear() + self.repos = [] + self.info_worker = InfoWorker() + self.info_worker.addon_repos.connect(self.update_repos) + self.update_worker = UpdateWorker() + self.update_worker.info_label.connect(self.set_information_label) + self.update_worker.addon_repo.connect(self.add_addon_repo) + self.update_worker.progressbar_show.connect(self.show_progress_bar) + self.update_worker.start() + + def add_addon_repo(self, addon_repo): + self.repos.append(addon_repo) + if addon_repo[2] == 1 : + self.listWorkbenches.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(addon_repo[0]) + str(" (Installed)"))) + else: + self.listWorkbenches.addItem(" "+str(addon_repo[0])) + + def set_information_label(self, label): + self.labelDescription.setText(label) + + def show(self,idx): + if self.repos and idx >= 0: + self.show_worker = ShowWorker(self.repos, idx) + self.show_worker.info_label.connect(self.set_information_label) + self.show_worker.addon_repos.connect(self.update_repos) + self.show_worker.progressbar_show.connect(self.show_progress_bar) + self.show_worker.start() + + def show_macro(self,idx): + if self.macros and idx >= 0: + self.showmacro_worker = ShowMacroWorker(self.macros, idx) + self.showmacro_worker.info_label.connect(self.set_information_label) + self.showmacro_worker.update_macro.connect(self.update_macro) + self.showmacro_worker.progressbar_show.connect(self.show_progress_bar) + self.showmacro_worker.start() + + def switchtab(self,idx): + if idx == 1: + if not self.macros: + self.listMacros.clear() + self.macros = [] + self.macro_worker = MacroWorker() + self.macro_worker.add_macro.connect(self.add_macro) + self.macro_worker.info_label.connect(self.set_information_label) + self.macro_worker.progressbar_show.connect(self.show_progress_bar) + self.macro_worker.start() + + def update_repos(self, repos): + self.repos = repos + + def add_macro(self, macro): + self.macros.append(macro) + if macro[1] == 1: + self.listMacros.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(macro[0]) + str(" (Installed)"))) + else: + self.listMacros.addItem(" "+str(macro[0])) + + def update_macro(self, idx, macro): + self.macros[idx] = macro + + def showlink(self,link): + "opens a link with the system browser" + #print("clicked: ",link) + QtGui.QDesktopServices.openUrl(QtCore.QUrl(link, QtCore.QUrl.TolerantMode)) + + def install(self): + if self.tabWidget.currentIndex() == 0: + idx = self.listWorkbenches.currentRow() + self.install_worker = InstallWorker(self.repos, idx) + self.install_worker.info_label.connect(self.set_information_label) + self.install_worker.progressbar_show.connect(self.show_progress_bar) + self.install_worker.start() + elif self.tabWidget.currentIndex() == 1: + macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro")) + macro = self.macros[self.listMacros.currentRow()] + if len(macro) < 5: + self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Unable to install", None, QtGui.QApplication.UnicodeUTF8)) + return + macroname = "Macro_"+macro[0]+".FCMacro" + macroname = macroname.replace(" ","_") + macrofilename = os.path.join(macropath,macroname) + macrofile = open(macrofilename,"wb") + macrofile.write(macro[3]) + macrofile.close() + self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Macro successfully installed. The macro is now available from the Macros dialog.", None, QtGui.QApplication.UnicodeUTF8)) + self.update_status() + + def show_progress_bar(self, state): + if state == True: + self.listWorkbenches.setEnabled(False) + self.listMacros.setEnabled(False) + self.buttonInstall.setEnabled(False) + self.buttonRemove.setEnabled(False) + self.progressBar.show() + else: + self.progressBar.hide() + self.listWorkbenches.setEnabled(True) + self.listMacros.setEnabled(True) + self.buttonInstall.setEnabled(True) + self.buttonRemove.setEnabled(True) + + def remove(self): + if self.tabWidget.currentIndex() == 0: + idx = self.listWorkbenches.currentRow() + basedir = FreeCAD.ConfigGet("UserAppData") + moddir = basedir + os.sep + "Mod" + clonedir = basedir + os.sep + "Mod" + os.sep + self.repos[idx][0] + if os.path.exists(clonedir): + shutil.rmtree(clonedir) + self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Addon successfully removed. Please restart FreeCAD", None, QtGui.QApplication.UnicodeUTF8)) + else: + self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Unable to remove this addon", None, QtGui.QApplication.UnicodeUTF8)) + elif self.tabWidget.currentIndex() == 1: + macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro")) + macro = self.macros[self.listMacros.currentRow()] + if macro[1] != 1: + return + macroname = "Macro_"+macro[0]+".FCMacro" + macroname = macroname.replace(" ","_") + macrofilename = os.path.join(macropath,macroname) + if os.path.exists(macrofilename): + os.remove(macrofilename) + self.labelDescription.setText(QtGui.QApplication.translate("AddonsInstaller", "Macro successfully removed.", None, QtGui.QApplication.UnicodeUTF8)) + self.update_status() + + def update_status(self): + self.listWorkbenches.clear() + self.listMacros.clear() + moddir = FreeCAD.ConfigGet("UserAppData") + os.sep + "Mod" + macropath = FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Macro').GetString("MacroPath",os.path.join(FreeCAD.ConfigGet("UserAppData"),"Macro")) + for wb in self.repos: + if os.path.exists(os.path.join(moddir,wb[0])): + self.listWorkbenches.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(wb[0]) + str(" (Installed)"))) + wb[2] = 1 + else: + self.listWorkbenches.addItem(" "+str(wb[0])) + wb[2] = 0 + for macro in self.macros: + if os.path.exists(os.path.join(macropath,"Macro_"+macro[0].replace(" ","_")+".FCMacro")): + self.listMacros.addItem(QtGui.QListWidgetItem(QtGui.QIcon.fromTheme("dialog-ok"),str(macro[0]) + str(" (Installed)"))) + macro[1] = 1 + else: + self.listMacros.addItem(" "+str(macro[0])) + macro[1] = 0 + + +class UpdateWorker(QtCore.QThread): + + info_label = QtCore.Signal(str) + addon_repo = QtCore.Signal(object) + progressbar_show = QtCore.Signal(bool) + + def __init__(self): + QtCore.QThread.__init__(self) + + def run(self): + "populates the list of addons" + self.progressbar_show.emit(True) + u = urllib2.urlopen("https://github.com/FreeCAD/FreeCAD-addons") + p = u.read() + u.close() + p = p.replace("\n"," ") + p = re.findall("octicon-file-submodule(.*?)message",p) + basedir = FreeCAD.ConfigGet("UserAppData") + moddir = basedir + os.sep + "Mod" + repos = [] + for l in p: + #name = re.findall("data-skip-pjax=\"true\">(.*?)<",l)[0] + name = re.findall("title=\"(.*?) @",l)[0] + self.info_label.emit(name) + #url = re.findall("title=\"(.*?) @",l)[0] + url = "https://github.com/" + re.findall("href=\"\/(.*?)\/tree",l)[0] + addondir = moddir + os.sep + name + if not os.path.exists(addondir): + state = 0 + else: + state = 1 + repos.append([name,url,state]) + self.addon_repo.emit([name,url,state]) + if not repos: + self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Unable to download addon list.", None, QtGui.QApplication.UnicodeUTF8)) + else: + self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Workbenches list was updated.", None, QtGui.QApplication.UnicodeUTF8)) + self.progressbar_show.emit(False) + self.stop = True + + +class InfoWorker(QtCore.QThread): + addon_repos = QtCore.Signal(object) + + def __init__(self): + QtCore.QThread.__init__(self) + + def run(self): + i = 0 + for repo in self.repos: + url = repo[1] + u = urllib2.urlopen(url) + p = u.read() + u.close() + desc = re.findall("This addon is already installed.", None, QtGui.QApplication.UnicodeUTF8) + "
" + desc + ' - ' + self.repos[self.idx][1] + '' + else: + message = desc + ' - ' + self.repos[self.idx][1] + '' + self.info_label.emit( message ) + self.progressbar_show.emit(False) + self.stop = True + + +class ShowMacroWorker(QtCore.QThread): + + info_label = QtCore.Signal(str) + update_macro = QtCore.Signal(int,object) + progressbar_show = QtCore.Signal(bool) + + def __init__(self, macros, idx): + QtCore.QThread.__init__(self) + self.macros = macros + self.idx = idx + + def run(self): + self.progressbar_show.emit(True) + self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Retrieving description...", None, QtGui.QApplication.UnicodeUTF8)) + if len(self.macros[self.idx]) > 2: + desc = self.macros[self.idx][2] + url = self.macros[self.idx][4] + else: + mac = self.macros[self.idx][0].replace(" ","_") + mac = mac.replace("&","%26") + mac = mac.replace("+","%2B") + url = "http://www.freecadweb.org/wiki/index.php?title=Macro_"+mac + self.info_label.emit("Retrieving info from " + str(url)) + u = urllib2.urlopen(url) + p = u.read() + u.close() + code = re.findall("
(.*?)<\/pre>",p.replace("\n","--endl--"))
+            if code:
+                code = code[0]
+                code = code.replace("--endl--","\n")
+            else:
+                self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Unable to fetch the code of this macro.", None, QtGui.QApplication.UnicodeUTF8))
+                self.progressbar_show.emit(False)
+                self.stop = True
+                return
+            desc = re.findall("(.*?)<\/td>",p.replace("\n"," "))
+            if desc:
+                desc = desc[0]
+            else:
+                self.info_label.emit(QtGui.QApplication.translate("AddonsInstaller", "Unable to retrieve a description for this macro.", None, QtGui.QApplication.UnicodeUTF8))
+                desc = "No description available"
+            # clean HTML escape codes
+            try:
+                from HTMLParser import HTMLParser
+            except ImportError:
+                from html.parser import HTMLParser
+            try:
+                code = code.decode("utf8")
+                code = HTMLParser().unescape(code)
+                code = code.encode("utf8")
+                code = code.replace("\xc2\xa0", " ")
+            except:
+                FreeCAD.Console.PrintWarning("Unable to clean macro code: "+mac+"\n")
+            self.update_macro.emit(self.idx,self.macros[self.idx]+[desc,code,url])
+        if self.macros[self.idx][1] == 1 :
+            message = "" + QtGui.QApplication.translate("AddonsInstaller", "This addon is already installed.", None, QtGui.QApplication.UnicodeUTF8) + "
" + desc + ' - ' + url + '' + else: + message = desc + ' - ' + url + '' + self.info_label.emit( message ) + self.progressbar_show.emit(False) + self.stop = True + + +class InstallWorker(QtCore.QThread): + + info_label = QtCore.Signal(str) + progressbar_show = QtCore.Signal(bool) + + def __init__(self, repos, idx): + QtCore.QThread.__init__(self) + self.idx = idx + self.repos = repos + + def run(self): + "installs or updates the selected addon" + git = None + try: + import git + except: + self.info_label.emit("python-git not found.") + FreeCAD.Console.PrintWarning("python-git not found. Using standard download instead.\n") + try: + import zipfile,StringIO + except: + self.info_label.emit("no zip support.") + FreeCAD.Console.PrintError("your version of python doesn't appear to support ZIP files. Unable to proceed.\n") + return + if self.idx < 0: + return + if not self.repos: + return + if NOGIT: + git = None + basedir = FreeCAD.ConfigGet("UserAppData") + moddir = basedir + os.sep + "Mod" + if not os.path.exists(moddir): + os.makedirs(moddir) + clonedir = moddir + os.sep + self.repos[self.idx][0] + self.progressbar_show.emit(True) + if os.path.exists(clonedir): + self.info_label.emit("Updating module...") + if git: + repo = git.Git(clonedir) + answer = repo.pull() + else: + answer = self.download(self.repos[self.idx][1],clonedir) + else: + if git: + self.info_label.emit("Cloning module...") + repo = git.Repo.clone_from(self.repos[self.idx][1], clonedir, branch='master') + else: + self.info_label.emit("Downloading module...") + self.download(self.repos[self.idx][1],clonedir) + answer = QtGui.QApplication.translate("AddonsInstaller", "Workbench successfully installed. Please restart FreeCAD to apply the changes.", None, QtGui.QApplication.UnicodeUTF8) + # symlink any macro contained in the module to the macros folder + macrodir = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro").GetString("MacroPath") + for f in os.listdir(clonedir): + if f.lower().endswith(".fcmacro"): + symlink(clonedir+os.sep+f,macrodir+os.sep+f) + FreeCAD.ParamGet('User parameter:Plugins/'+self.repos[self.idx][0]).SetString("destination",clonedir) + answer += QtGui.QApplication.translate("AddonsInstaller", "A macro has been installed and is available the Macros menu", None, QtGui.QApplication.UnicodeUTF8) + ": " + answer += f + "" + self.info_label.emit(answer) + self.progressbar_show.emit(False) + self.stop = True + + def download(self,giturl,clonedir): + "downloads and unzip from github" + import StringIO,zipfile + bakdir = None + if os.path.exists(clonedir): + bakdir = clonedir+".bak" + if os.path.exists(bakdir): + shutil.rmtree(bakdir) + os.rename(clonedir,bakdir) + os.makedirs(clonedir) + zipurl = giturl+"/archive/master.zip" + try: + print("Downloading "+zipurl) + u = urllib2.urlopen(zipurl) + except: + return QtGui.QApplication.translate("AddonsInstaller", "Error: Unable to download", None, QtGui.QApplication.UnicodeUTF8) + " " + zipurl + zfile = StringIO.StringIO() + zfile.write(u.read()) + zfile = zipfile.ZipFile(zfile) + master = zfile.namelist()[0] # github will put everything in a subfolder + zfile.extractall(clonedir) + u.close() + zfile.close() + for filename in os.listdir(clonedir+os.sep+master): + shutil.move(clonedir+os.sep+master+os.sep+filename, clonedir+os.sep+filename) + os.rmdir(clonedir+os.sep+master) + if bakdir: + shutil.rmtree(bakdir) + return QtGui.QApplication.translate("AddonsInstaller", "Successfully installed", None, QtGui.QApplication.UnicodeUTF8) + " " + zipurl + + +def launchAddonMgr(): + # first use dialog + readWarning = FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').GetBool('readWarning',False) + if not readWarning: + if QtGui.QMessageBox.warning(None,"FreeCAD",QtGui.QApplication.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!", None, QtGui.QApplication.UnicodeUTF8), QtGui.QMessageBox.Cancel | QtGui.QMessageBox.Ok) != QtGui.QMessageBox.StandardButton.Cancel: + FreeCAD.ParamGet('User parameter:Plugins/addonsRepository').SetBool('readWarning',True) + readWarning = True + + if readWarning: + dialog = AddonsInstaller() + dialog.exec_() diff --git a/src/Mod/AddonManager/AddonManagerGui.py b/src/Mod/AddonManager/AddonManagerGui.py new file mode 100644 index 000000000000..9bf49d61fe20 --- /dev/null +++ b/src/Mod/AddonManager/AddonManagerGui.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# FreeCAD tools of the AddonManager workbench +# (c) 2001 Juergen Riegel +# License LGPL + +import FreeCAD, FreeCADGui + +class CmdAddonMgr: + def Activated(self): + import AddonManager + AddonManager.launchAddonMgr() + def IsActive(self): + return True + def GetResources(self): + return {'Pixmap': 'AddonManager', 'MenuText': '&Addon manager', + 'ToolTip': 'Manage FreeCAD workbenches and macros', + 'Group': 'Tools'} diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt new file mode 100644 index 000000000000..da710b21083a --- /dev/null +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -0,0 +1,21 @@ +SET(AddonManager_SRCS + Init.py + InitGui.py + AddonManager.py + AddonManagerGui.py +) + +SOURCE_GROUP("" FILES ${AddonManager_SRCS}) + +ADD_CUSTOM_TARGET(AddonManager ALL + SOURCES ${AddonManager_SRCS} +) + +fc_copy_sources(AddonManager "${CMAKE_BINARY_DIR}/Mod/AddonManager" ${AddonManager_SRCS}) + +INSTALL( + FILES + ${AddonManager_SRCS} + DESTINATION + Mod/AddonManager +) diff --git a/src/Mod/AddonManager/Init.py b/src/Mod/AddonManager/Init.py new file mode 100644 index 000000000000..86fc9fc2bf42 --- /dev/null +++ b/src/Mod/AddonManager/Init.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# FreeCAD init script of the AddonManager module +# (c) 2001 Juergen Riegel +# License LGPL diff --git a/src/Mod/AddonManager/InitGui.py b/src/Mod/AddonManager/InitGui.py new file mode 100644 index 000000000000..1cc8d70f57b2 --- /dev/null +++ b/src/Mod/AddonManager/InitGui.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- +# AddonManager gui init module +# (c) 2001 Juergen Riegel +# License LGPL +from AddonManagerGui import CmdAddonMgr + +FreeCADGui.addCommand('Std_AddonMgr', CmdAddonMgr()) diff --git a/src/Mod/CMakeLists.txt b/src/Mod/CMakeLists.txt index 0c63e58655ec..8a1f81aa352a 100644 --- a/src/Mod/CMakeLists.txt +++ b/src/Mod/CMakeLists.txt @@ -134,3 +134,8 @@ if(BUILD_TECHDRAW) add_subdirectory(Measure) add_subdirectory(TechDraw) endif(BUILD_TECHDRAW) + +if(BUILD_ADDONMGR) + add_subdirectory(AddonManager) +endif(BUILD_ADDONMGR) +