#*************************************************************************** #* Copyright (c) 2018 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 * #* * #*************************************************************************** # This is the start page template. It builds an HTML global variable that # contains the html code of the start page. # Note: It is built only once per FreeCAD session for now... import sys import os import tempfile import time import zipfile import re import FreeCAD import FreeCADGui import urllib.parse from . import TranslationTexts from PySide import QtCore, QtGui try: from addonmanager_macro import Macro as AM_Macro has_am_macro = True except ImportError: has_am_macro = False FreeCADGui.addLanguagePath(":/translations") FreeCADGui.updateLocale() iconprovider = QtGui.QFileIconProvider() iconbank = {} # store pre-existing icons so we don't overpollute temp dir tempfolder = None # store icons inside a subfolder in temp dir defaulticon = None # store a default icon for problematic file types def gethexcolor(color): "returns a color hex value #000000" r = str(hex(int(((color>>24)&0xFF))))[2:].zfill(2) g = str(hex(int(((color>>16)&0xFF))))[2:].zfill(2) b = str(hex(int(((color>>8)&0xFF))))[2:].zfill(2) return "#"+r+g+b def isOpenableByFreeCAD(filename): "check if FreeCAD can handle this file type" if os.path.isdir(filename): return False if os.path.basename(filename)[0] == ".": return False extensions = [key.lower() for key in FreeCAD.getImportType().keys()] ext = os.path.splitext(filename)[1].lower() if ext: if ext[0] == ".": ext = ext[1:] if ext in extensions: return True return False def getInfo(filename): "returns available file information" global iconbank,tempfolder tformat = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetString("TimeFormat","%m/%d/%Y %H:%M:%S") def getLocalTime(timestamp): "returns a local time from a timestamp" return time.strftime(tformat,time.localtime(timestamp)) def getSize(size): "returns a human-readable size" if size > 1024*1024: hsize = str(int(size/(1024*1024))) + "Mb" elif size > 1024: hsize = str(int(size/1024)) + "Kb" else: hsize = str(int(size)) + "b" return hsize def getFreeDesktopThumbnail(filename): "if we have gnome libs available, try to find a system-generated thumbnail" path = os.path.abspath(filename) thumb = None try: import gnome.ui import gnomevfs except Exception: # alternative method import hashlib fhash = hashlib.md5(bytes(urllib.parse.quote("file://"+path,safe=":/"),"ascii")).hexdigest() thumb = os.path.join(os.path.expanduser("~"),".thumbnails","normal",fhash+".png") else: uri = gnomevfs.get_uri_from_local_path(path) thumb = gnome.ui.thumbnail_path_for_uri(uri, "normal") if thumb and os.path.exists(thumb): return thumb return None if os.path.exists(filename): if os.path.isdir(filename): return None # get normal file info s = os.stat(filename) size = getSize(s.st_size) ctime = getLocalTime(s.st_ctime) mtime = getLocalTime(s.st_mtime) author = "" company = TranslationTexts.T_UNKNOWN lic = TranslationTexts.T_UNKNOWN image = None descr = "" # get additional info from fcstd files if filename.lower().endswith(".fcstd"): try: zfile=zipfile.ZipFile(filename) except Exception: print("Cannot read file: ",filename) return None files=zfile.namelist() # check for meta-file if it's really a FreeCAD document if files[0] == "Document.xml": try: doc = str(zfile.read(files[0])) except OSError as e: print ("Fail to load corrupted FCStd file: '{0}' with this error: {1}".format(filename, str(e))) return None doc = doc.replace("\n"," ") r = re.findall("Property name=\"CreatedBy.*?String value=\"(.*?)\"/>",doc) if r: author = r[0] # remove email if present in author field if "<" in author: author = author.split("<")[0].strip() r = re.findall("Property name=\"Company.*?String value=\"(.*?)\"/>",doc) if r: company = r[0] r = re.findall("Property name=\"License.*?String value=\"(.*?)\"/>",doc) if r: lic = r[0] r = re.findall("Property name=\"Comment.*?String value=\"(.*?)\"/>",doc) if r: descr = r[0] if "thumbnails/Thumbnail.png" in files: if filename in iconbank: image = iconbank[filename] else: imagedata=zfile.read("thumbnails/Thumbnail.png") image = tempfile.mkstemp(dir=tempfolder,suffix='.png')[1] thumb = open(image,"wb") thumb.write(imagedata) thumb.close() iconbank[filename] = image elif filename.lower().endswith(".fcmacro"): # For FreeCAD macros, use the Macro Editor icon (but we have to have it in a file for # the web view to load it) image = os.path.join(tempfolder,"fcmacro_icon.svg") if not os.path.exists(image): f = QtCore.QFile(":/icons/MacroEditor.svg") f.copy(image) iconbank[filename] = image if has_am_macro: macro = AM_Macro(os.path.basename(filename)) macro.fill_details_from_file(filename) author = macro.author elif QtGui.QImageReader.imageFormat(filename): # use image itself as icon if it's an image file image = filename iconbank[filename] = image else: # use freedesktop thumbnail if available fdthumb = getFreeDesktopThumbnail(filename) if fdthumb: image = fdthumb iconbank[filename] = fdthumb # retrieve default mime icon if needed if not image: i = QtCore.QFileInfo(filename) t = iconprovider.type(i) if not t: t = "Unknown" if t in iconbank: image = iconbank[t] else: icon = iconprovider.icon(i) if icon.availableSizes(): preferred = icon.actualSize(QtCore.QSize(128,128)) px = icon.pixmap(preferred) image = tempfile.mkstemp(dir=tempfolder,suffix='.png')[1] px.save(image) else: image = getDefaultIcon() iconbank[t] = image return [image,size,author,ctime,mtime,descr,company,lic] return None def getDefaultIcon(): "retrieves or creates a default file icon" global defaulticon if not defaulticon: i = QtCore.QFileInfo(__file__) # MUST provide an existing file in qt5 icon = iconprovider.icon(i) preferred = icon.actualSize(QtCore.QSize(128,128)) px = icon.pixmap(preferred) image = tempfile.mkstemp(dir=tempfolder,suffix='.png')[1] px.save(image) defaulticon = image return defaulticon def buildCard(filename,method,arg=None): """builds an html
  • element representing a file. method is a script + a keyword, for ex. url.py?key=""" result = "" if os.path.exists(filename) and isOpenableByFreeCAD(filename): basename = os.path.basename(filename) if not arg: arg = basename finfo = getInfo(filename) if finfo: image = finfo[0] size = finfo[1] author = finfo[2] infostring = TranslationTexts.T_CREATIONDATE+": "+finfo[3] + "\n" infostring += TranslationTexts.T_LASTMODIFIED+": "+finfo[4] if finfo[5]: infostring += "\n\n" + finfo[5] if size: result += '
  • ' result += '' result += ''+basename+'' result += '
    ' result += '

    '+basename+'

    ' result += '

    '+author+'

    ' result += '

    '+size+'

    ' result += '
    ' result += '
    ' result += '
  • ' return result def handle(): "builds the HTML code of the start page" global iconbank,tempfolder # reuse stuff from previous runs to reduce temp dir clutter import Start if hasattr(Start,"iconbank"): iconbank = Start.iconbank if hasattr(Start,"tempfolder"): tempfolder = Start.tempfolder else: tempfolder = tempfile.mkdtemp(prefix="FreeCADStartThumbnails") # build the html page skeleton resources_dir = os.path.join(FreeCAD.getResourceDir(), "Mod", "Start", "StartPage") p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start") template = p.GetString("Template","") if template: html_filename = template else: html_filename = os.path.join(resources_dir, "StartPage.html") js_filename = os.path.join(resources_dir, "StartPage.js") css_filename = p.GetString("CSSFile",os.path.join(resources_dir, "StartPage.css")) with open(html_filename, 'r') as f: HTML = f.read() with open(js_filename, 'r') as f: JS = f.read() with open(css_filename, 'r') as f: CSS = f.read() HTML = HTML.replace("JS",JS) HTML = HTML.replace("CSS",CSS) # set the language HTML = HTML.replace("BCP47_LANGUAGE",QtCore.QLocale().bcp47Name()) # get the stylesheet if we are using one if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("UseStyleSheet",False): qssfile = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/MainWindow").GetString("StyleSheet","") if qssfile: # Search for stylesheet in user, system and resources locations user = os.path.join(FreeCAD.getUserAppDataDir(), "Gui", "Stylesheets") system = os.path.join(FreeCAD.getResourceDir(), "Gui", "Stylesheets") resources = ":/stylesheets" res = False if QtCore.QFile.exists(os.path.join(user, qssfile)): path = os.path.join(user, qssfile) elif QtCore.QFile.exists(os.path.join(system, qssfile)): path = os.path.join(system, qssfile) elif QtCore.QFile.exists(os.path.join(resources, qssfile)): res = True path = os.path.join(resources, qssfile) else: path = None if path: if res: f = QtCore.QFile(path) if f.open(QtCore.QIODevice.ReadOnly | QtCore.QFile.Text): ALTCSS = QtCore.QTextStream(f).readAll() HTML = HTML.replace("","") else: with open(path, 'r') as f: ALTCSS = f.read() HTML = HTML.replace("","") # turn tips off if needed if not FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("ShowTips",True): HTML = HTML.replace("display: block; /* footnote tips display */","display: none; /* footnote tips display */") # get FreeCAD version v = FreeCAD.Version() VERSIONSTRING = TranslationTexts.T_VERSION + " " + v[0] + "." + v[1] + "." + v[2] +" " + TranslationTexts.T_BUILD + " " + v[3] HTML = HTML.replace("VERSIONSTRING",VERSIONSTRING) # translate texts texts = [t for t in dir(TranslationTexts) if t.startswith("T_")] for text in texts: HTML = HTML.replace(text,getattr(TranslationTexts,text)) # build a "create new" icon with the FreeCAD background color gradient if not "createimg" in iconbank: p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View") c1 = gethexcolor(p.GetUnsigned("BackgroundColor2")) c2 = gethexcolor(p.GetUnsigned("BackgroundColor3")) gradient = QtGui.QLinearGradient(0, 0, 0, 128) gradient.setColorAt(0.0, QtGui.QColor(c1)) gradient.setColorAt(1.0, QtGui.QColor(c2)) i = QtGui.QImage(128,128,QtGui.QImage.Format_RGB16) pa = QtGui.QPainter(i) pa.fillRect(i.rect(),gradient) pa.end() createimg = tempfile.mkstemp(dir=tempfolder,suffix='.png')[1] i.save(createimg) iconbank["createimg"] = createimg # build SECTION_RECENTFILES rf = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/RecentFiles") rfcount = rf.GetInt("RecentFiles",0) SECTION_RECENTFILES = "

    "+TranslationTexts.T_RECENTFILES+"

    " SECTION_RECENTFILES += "' HTML = HTML.replace("SECTION_RECENTFILES",SECTION_RECENTFILES) # build SECTION_EXAMPLES SECTION_EXAMPLES = "" if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("ShowExamples",True): SECTION_EXAMPLES = "

    "+TranslationTexts.T_EXAMPLES+"

    " SECTION_EXAMPLES += "" HTML = HTML.replace("SECTION_EXAMPLES",SECTION_EXAMPLES) # build SECTION_CUSTOM SECTION_CUSTOM = "" cfolders = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetString("ShowCustomFolder","") if cfolders: dn = 0 for cfolder in cfolders.split(";;"): # allow several paths separated by ;; if not os.path.isdir(cfolder): cfolder = os.path.dirname(cfolder) if not os.path.exists(cfolder): FreeCAD.Console.PrintWarning("Custom folder not found: %s" % cfolder) else: SECTION_CUSTOM += "

    "+os.path.basename(os.path.normpath(cfolder))+"

    " SECTION_CUSTOM += "" # hide the custom section tooltip if custom section is set (users know about it if they enabled it) HTML = HTML.replace("id=\"customtip\" class","id=\"customtip\" style=\"display:none;\" class") dn += 1 HTML = HTML.replace("SECTION_CUSTOM",SECTION_CUSTOM) # build IMAGE_SRC paths HTML = HTML.replace("IMAGE_SRC_USERHUB",'file:///'+os.path.join(resources_dir, 'images/userhub.png').replace('\\','/')) HTML = HTML.replace("IMAGE_SRC_POWERHUB",'file:///'+os.path.join(resources_dir, 'images/poweruserhub.png').replace('\\','/')) HTML = HTML.replace("IMAGE_SRC_DEVHUB",'file:///'+os.path.join(resources_dir, 'images/developerhub.png').replace('\\','/')) HTML = HTML.replace("IMAGE_SRC_MANUAL",'file:///'+os.path.join(resources_dir, 'images/manual.png').replace('\\','/')) HTML = HTML.replace("IMAGE_SRC_SETTINGS",'file:///'+os.path.join(resources_dir, 'images/settings.png').replace('\\','/')) HTML = HTML.replace("IMAGE_SRC_INSTALLED",'file:///'+os.path.join(resources_dir, 'images/installed.png').replace('\\','/')) # build UL_WORKBENCHES wblist = [] UL_WORKBENCHES = '' HTML = HTML.replace("UL_WORKBENCHES", UL_WORKBENCHES) # Detect additional addons that are not a workbench try: import dxfLibrary except Exception: pass else: wblist.append("dxf-library") try: import RebarTools except Exception: pass else: wblist.append("reinforcement") try: import CADExchangerIO except Exception: pass else: wblist.append("cadexchanger") HTML = HTML.replace("var wblist = [];","var wblist = " + str(wblist) + ";") # set and replace colors and font settings p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start") if p.GetString("BackgroundImage",""): BACKGROUND = gethexcolor(p.GetUnsigned("BackgroundColor1",1331197183))+" url("+p.GetString("BackgroundImage","")+")" else: BACKGROUND = gethexcolor(p.GetUnsigned("BackgroundColor1",1331197183)) # linear gradient not supported by QT "linear-gradient("+gethexcolor(p.GetUnsigned("BackgroundColor1",1331197183))+","+gethexcolor(p.GetUnsigned("BackgroundColor2",2141107711))+")" LINKCOLOR = gethexcolor(p.GetUnsigned("LinkColor",65535)) BASECOLOR = gethexcolor(p.GetUnsigned("PageColor",4294967295)) BOXCOLOR = gethexcolor(p.GetUnsigned("BoxColor",3722305023)) TEXTCOLOR = gethexcolor(p.GetUnsigned("PageTextColor",255)) BGTCOLOR = gethexcolor(p.GetUnsigned("BackgroundTextColor",4294703103)) OVERFLOW = "" if p.GetBool("ShowScrollBars",True) else "body::-webkit-scrollbar {display: none;}" SHADOW = "#888888" if QtGui.QColor(BASECOLOR).valueF() < 0.5: # dark page - we need to make darker shadows SHADOW = "#000000" FONTFAMILY = p.GetString("FontFamily","Arial,Helvetica,sans") if not FONTFAMILY: FONTFAMILY = "Arial,Helvetica,sans" FONTSIZE = p.GetInt("FontSize",13) HTML = HTML.replace("BASECOLOR",BASECOLOR) HTML = HTML.replace("BOXCOLOR",BOXCOLOR) HTML = HTML.replace("LINKCOLOR",LINKCOLOR) HTML = HTML.replace("TEXTCOLOR",TEXTCOLOR) HTML = HTML.replace("BGTCOLOR",BGTCOLOR) HTML = HTML.replace("BACKGROUND",BACKGROUND) HTML = HTML.replace("SHADOW",SHADOW) HTML = HTML.replace("FONTFAMILY",FONTFAMILY) HTML = HTML.replace("FONTSIZE",str(FONTSIZE)+"px") HTML = HTML.replace("OVERFLOW",OVERFLOW) # enable web access if permitted if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("AllowDownload",False): HTML = HTML.replace("var allowDownloads = 0;","var allowDownloads = 1;") # enable or disable forum if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("ShowForum",False): HTML = HTML.replace("var showForum = 0;","var showForum = 1;") HTML = HTML.replace("display: none; /* forum display */","display: block; /* forum display */") # enable or disable notepad if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start").GetBool("ShowNotes",False): HTML = HTML.replace("display: none; /* notes display */","display: block; /* notes display */") HTML = HTML.replace("width: 100%; /* thumbs display */","width: 70%; /* thumbs display */") # store variables for further use Start.iconbank = iconbank Start.tempfolder = tempfolder return HTML def exportTestFile(): "Allow to check if everything is Ok" f = open(os.path.expanduser("~")+os.sep+"freecad-startpage.html","w") f.write(handle()) f.close() def postStart(): "executes needed operations after loading a file" param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Start") # switch workbench wb = param.GetString("AutoloadModule","") if "$LastModule" == wb: wb = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/General").GetString("LastModule","") if wb: # don't switch workbenches if we are not in Start anymore if FreeCADGui.activeWorkbench() and (FreeCADGui.activeWorkbench().name() == "StartWorkbench"): FreeCADGui.activateWorkbench(wb) # close start tab cl = param.GetBool("closeStart",False) if cl: title = QtGui.QApplication.translate("Workbench","Start page") mw = FreeCADGui.getMainWindow() if mw: mdi = mw.findChild(QtGui.QMdiArea) if mdi: for mdichild in mdi.children(): for subw in mdichild.findChildren(QtGui.QMdiSubWindow): if subw.windowTitle() == title: subw.close() def checkPostOpenStartPage(): "on Start WB startup, check if we are loading a file and therefore need to close the StartPage" import Start if FreeCAD.ParamGet('User parameter:BaseApp/Preferences/Mod/Start').GetBool('DoNotShowOnOpen',False) and (not hasattr(Start,'CanOpenStartPage')): if len(sys.argv) > 1: postStart() Start.CanOpenStartPage = True