Merge branch 'master' into lupdateCPPWorkaround

This commit is contained in:
Chris Hennes
2022-01-02 11:41:17 -06:00
committed by GitHub
24 changed files with 1128 additions and 220 deletions

90
.github/codespellignore vendored Normal file
View File

@@ -0,0 +1,90 @@
aci
ake
aline
alle
alledges
alocation
als
ang
anid
apoints
ba
beginn
behaviour
bloaded
bottome
byteorder
calculater
cancelled
cancelling
cas
cascade
centimetre
childrens
childs
colour
colours
commen
connexion
currenty
dof
doubleclick
dum
eiter
elemente
ende
feld
finde
findf
freez
hist
iff
indicies
initialisation
initialise
initialised
initialises
initialisiert
inout
ist
kilometre
lod
mantatory
methode
metres
millimetre
modell
nd
noe
normale
normaly
nto
numer
oder
ontop
orgin
orginx
orginy
ot
pard
parm
parms
pres
programm
que
recurrance
rougly
seperator
serie
sinc
strack
substraction
te
thist
thru
tread
uint
unter
vertexes
wallthickness
whitespaces

31
.github/workflows/codespell.yml vendored Normal file
View File

@@ -0,0 +1,31 @@
# GitHub Action to automate the identification of common misspellings in text files.
# https://github.com/codespell-project/actions-codespell
# https://github.com/codespell-project/codespell
name: Codespell
on:
pull_request:
jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v12.2
- name: List all changed files
run: |
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
echo "$file was changed"
done
- uses: codespell-project/actions-codespell@master
with:
check_filenames: true
ignore_words_file: .github/codespellignore
skip: ./.git,*.po,*.ts,./ChangeLog.txt,./src/3rdParty,./src/Mod/Assembly/App/opendcm,./src/CXX,./src/zipios++,./src/Base/swig*,./src/Mod/Robot/App/kdl_cp,./src/Mod/Import/App/SCL,./src/WindowsInstaller,./src/Doc/FreeCAD.uml,./build/
path: ${{ steps.changed-files.outputs.all_changed_files }}

View File

@@ -32,10 +32,14 @@ if(HOMEBREW_PREFIX)
file(READ ${PTH_FILE} ADDITIONAL_DIR)
string(STRIP "${ADDITIONAL_DIR}" ADDITIONAL_DIR)
string(REGEX REPLACE "^${HOMEBREW_PREFIX}/Cellar/([A-Za-z0-9_]+).*$" "\\1" LIB_NAME ${ADDITIONAL_DIR})
string(REGEX REPLACE ".*libexec(.*)/site-packages" "libexec/${LIB_NAME}\\1" NEW_SITE_DIR ${ADDITIONAL_DIR})
string(FIND "${ADDITIONAL_DIR}" "${HOMEBREW_PREFIX}/Cellar" POSITION)
string(LENGTH "${ADDITIONAL_DIR}" DIR_LENGTH)
string(SUBSTRING "${ADDITIONAL_DIR}" ${POSITION} ${DIR_LENGTH}-${POSITION} DIR_TAIL)
string(REGEX MATCHALL "^([/A-Za-z0-9_.@-]+)" CLEAR_TAIL ${DIR_TAIL})
string(REGEX REPLACE "^${HOMEBREW_PREFIX}/Cellar/([A-Za-z0-9_]+).*$" "\\1" LIB_NAME ${CLEAR_TAIL})
string(REGEX REPLACE ".*libexec(.*)/site-packages" "libexec/${LIB_NAME}\\1" NEW_SITE_DIR ${CLEAR_TAIL})
install(DIRECTORY ${ADDITIONAL_DIR} DESTINATION ${CMAKE_INSTALL_PREFIX}/${NEW_SITE_DIR})
install(DIRECTORY ${CLEAR_TAIL} DESTINATION ${CMAKE_INSTALL_PREFIX}/${NEW_SITE_DIR})
#update the paths of the .pth files copied into the bundle
get_filename_component(PTH_FILENAME ${PTH_FILE} NAME)
@@ -120,7 +124,7 @@ find_package(PkgConfig)
pkg_check_modules(ICU icu-uc)
execute_process(
COMMAND find /usr/local/Cellar/nglib -name MacOS
COMMAND find -L /usr/local/Cellar/nglib -name MacOS
OUTPUT_VARIABLE CONFIG_NGLIB)
install(CODE

View File

@@ -29,8 +29,7 @@ import shutil
import stat
import tempfile
from datetime import date, timedelta
from typing import Dict, Union
from enum import Enum
from typing import Dict
from PySide2 import QtGui, QtCore, QtWidgets
import FreeCADGui
@@ -82,6 +81,7 @@ class CommandAddonManager:
"macro_worker",
"install_worker",
"update_metadata_cache_worker",
"load_macro_metadata_worker",
"update_all_worker",
"update_check_single_worker",
]
@@ -109,32 +109,66 @@ class CommandAddonManager:
def Activated(self) -> None:
# 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)
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
readWarning = pref.GetBool("readWarning2022", False)
if not readWarning:
if (
QtWidgets.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!",
),
QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Ok,
)
!= QtWidgets.QMessageBox.StandardButton.Cancel
):
readWarningParameter.SetBool("readWarning", True)
warning_dialog = FreeCADGui.PySideUic.loadUi(
os.path.join(os.path.dirname(__file__), "first_run.ui")
)
autocheck = pref.GetBool("AutoCheck", False)
download_macros = pref.GetBool("DownloadMacros", False)
proxy_string = pref.GetString("ProxyUrl", "")
if pref.GetBool("NoProxyCheck", True):
proxy_option = 0
elif pref.GetBool("SystemProxyCheck", False):
proxy_option = 1
elif pref.GetBool("UserProxyCheck", False):
proxy_option = 2
def toggle_proxy_list(option: int):
if option == 2:
warning_dialog.lineEditProxy.show()
else:
warning_dialog.lineEditProxy.hide()
warning_dialog.checkBoxAutoCheck.setChecked(autocheck)
warning_dialog.checkBoxDownloadMacroMetadata.setChecked(download_macros)
warning_dialog.comboBoxProxy.setCurrentIndex(proxy_option)
toggle_proxy_list(proxy_option)
if proxy_option == 2:
warning_dialog.lineEditProxy.setText(proxy_string)
warning_dialog.comboBoxProxy.currentIndexChanged.connect(toggle_proxy_list)
warning_dialog.labelWarning.setStyleSheet(
f"color:{utils.warning_color_string()};font-weight:bold;"
)
if warning_dialog.exec() == QtWidgets.QDialog.Accepted:
readWarning = True
pref.SetBool("readWarning2022", True)
pref.SetBool("AutoCheck", warning_dialog.checkBoxAutoCheck.isChecked())
pref.SetBool(
"DownloadMacros",
warning_dialog.checkBoxDownloadMacroMetadata.isChecked(),
)
if warning_dialog.checkBoxDownloadMacroMetadata.isChecked():
self.trigger_recache = True
selected_proxy_option = warning_dialog.comboBoxProxy.currentIndex()
if selected_proxy_option == 0:
pref.SetBool("NoProxyCheck", True)
pref.SetBool("SystemProxyCheck", False)
pref.SetBool("UserProxyCheck", False)
elif selected_proxy_option == 1:
pref.SetBool("NoProxyCheck", False)
pref.SetBool("SystemProxyCheck", True)
pref.SetBool("UserProxyCheck", False)
else:
pref.SetBool("NoProxyCheck", False)
pref.SetBool("SystemProxyCheck", False)
pref.SetBool("UserProxyCheck", True)
pref.SetString("ProxyUrl", warning_dialog.lineEditProxy.text())
if readWarning:
self.launch()
@@ -168,6 +202,8 @@ class CommandAddonManager:
# 0: Update every launch
# >0: Update every n days
self.update_cache = False
if hasattr(self, "trigger_recache") and self.trigger_recache:
self.update_cache = True
update_frequency = pref.GetInt("UpdateFrequencyComboEntry", 0)
if update_frequency == 0:
days_between_updates = -1
@@ -241,7 +277,6 @@ class CommandAddonManager:
)
self.dialog.buttonClose.clicked.connect(self.dialog.reject)
self.dialog.buttonUpdateCache.clicked.connect(self.on_buttonUpdateCache_clicked)
self.dialog.buttonShowDetails.clicked.connect(self.toggle_details)
self.dialog.buttonPauseUpdate.clicked.connect(self.stop_update)
self.packageList.itemSelected.connect(self.table_row_activated)
self.packageList.setEnabled(False)
@@ -262,7 +297,7 @@ class CommandAddonManager:
)
# set info for the progress bar:
self.dialog.progressBar.setMaximum(100)
self.dialog.progressBar.setMaximum(1000)
# begin populating the table in a set of sub-threads
self.startup()
@@ -385,6 +420,9 @@ class CommandAddonManager:
self.update_metadata_cache,
self.check_updates,
]
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
if pref.GetBool("DownloadMacros", False):
self.startup_sequence.append(self.load_macro_metadata)
self.current_progress_region = 0
self.number_of_progress_regions = len(self.startup_sequence)
self.do_next_startup_phase()
@@ -410,7 +448,6 @@ class CommandAddonManager:
def populate_packages_table(self) -> None:
self.item_model.clear()
self.current_progress_region += 1
use_cache = not self.update_cache
if use_cache:
@@ -456,7 +493,7 @@ class CommandAddonManager:
if hasattr(self, "package_cache"):
package_cache_path = self.get_cache_file_name("package_cache.json")
with open(package_cache_path, "w") as f:
f.write(json.dumps(self.package_cache))
f.write(json.dumps(self.package_cache, indent=" "))
def activate_table_widgets(self) -> None:
self.packageList.setEnabled(True)
@@ -464,42 +501,44 @@ class CommandAddonManager:
self.do_next_startup_phase()
def populate_macros(self) -> None:
self.current_progress_region += 1
if self.update_cache or not os.path.isfile(
self.get_cache_file_name("macro_cache.json")
):
macro_cache_file = self.get_cache_file_name("macro_cache.json")
cache_is_bad = True
if os.path.isfile(macro_cache_file):
size = os.path.getsize(macro_cache_file)
if size > 1000: # Make sure there is actually data in there
cache_is_bad = False
if self.update_cache or cache_is_bad:
self.macro_worker = FillMacroListWorker(self.get_cache_file_name("Macros"))
self.macro_worker.status_message_signal.connect(self.show_information)
self.macro_worker.progress_made.connect(self.update_progress_bar)
self.macro_worker.add_macro_signal.connect(self.add_addon_repo)
self.macro_worker.finished.connect(
self.do_next_startup_phase
) # Link to step 3
self.macro_worker.finished.connect(self.do_next_startup_phase)
self.macro_worker.start()
else:
self.macro_worker = LoadMacrosFromCacheWorker(
self.get_cache_file_name("macro_cache.json")
)
self.macro_worker.add_macro_signal.connect(self.add_addon_repo)
self.macro_worker.finished.connect(
self.do_next_startup_phase
) # Link to step 3
self.macro_worker.finished.connect(self.do_next_startup_phase)
self.macro_worker.start()
def cache_macro(self, macro: AddonManagerRepo):
def cache_macro(self, repo: AddonManagerRepo):
if not hasattr(self, "macro_cache"):
self.macro_cache = []
if macro.macro is not None:
self.macro_cache.append(macro.macro.to_cache())
if repo.macro is not None:
self.macro_cache.append(repo.macro.to_cache())
else:
FreeCAD.Console.PrintError(
f"Addon Manager: Internal error, cache_macro called on non-macro {repo.name}\n"
)
def write_macro_cache(self):
macro_cache_path = self.get_cache_file_name("macro_cache.json")
with open(macro_cache_path, "w") as f:
f.write(json.dumps(self.macro_cache))
f.write(json.dumps(self.macro_cache, indent=" "))
self.macro_cache = []
def update_metadata_cache(self) -> None:
self.current_progress_region += 1
if self.update_cache:
self.update_metadata_cache_worker = UpdateMetadataCacheWorker(
self.item_model.repos
@@ -528,14 +567,29 @@ class CommandAddonManager:
"""Called when the named package has either new metadata or a new icon (or both)"""
with self.lock:
self.cache_package(repo)
repo.icon = self.get_icon(repo, update=True)
self.item_model.reload_item(repo)
def load_macro_metadata(self) -> None:
if self.update_cache:
self.load_macro_metadata_worker = CacheMacroCode(self.item_model.repos)
self.load_macro_metadata_worker.status_message.connect(
self.show_information
)
self.load_macro_metadata_worker.update_macro.connect(
self.on_package_updated
)
self.load_macro_metadata_worker.progress_made.connect(
self.update_progress_bar
)
self.load_macro_metadata_worker.finished.connect(self.do_next_startup_phase)
self.load_macro_metadata_worker.start()
else:
self.do_next_startup_phase()
def check_updates(self) -> None:
"checks every installed addon for available updates"
self.current_progress_region += 1
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
autocheck = pref.GetBool("AutoCheck", False)
if not autocheck:
@@ -645,6 +699,7 @@ class CommandAddonManager:
"""shows generic text in the information pane (which might be collapsed)"""
self.dialog.labelStatusInfo.setText(message)
self.dialog.labelStatusInfo.repaint()
def show_workbench(self, repo: AddonManagerRepo) -> None:
self.packageList.hide()
@@ -700,6 +755,8 @@ class CommandAddonManager:
real_install_succeeded, errors = macro.install(self.macro_repo_dir)
if not real_install_succeeded:
failed = True
else:
utils.update_macro_installation_details(repo)
if not failed:
message = translate(
@@ -851,36 +908,31 @@ class CommandAddonManager:
self.dialog.labelStatusInfo.hide()
self.dialog.progressBar.hide()
self.dialog.buttonPauseUpdate.hide()
self.dialog.buttonShowDetails.hide()
self.dialog.labelUpdateInProgress.hide()
self.packageList.ui.lineEditFilter.setFocus()
def show_progress_widgets(self) -> None:
if self.dialog.progressBar.isHidden():
self.dialog.progressBar.show()
self.dialog.buttonPauseUpdate.show()
self.dialog.buttonShowDetails.show()
self.dialog.labelStatusInfo.hide()
self.dialog.buttonShowDetails.setArrowType(QtCore.Qt.RightArrow)
self.dialog.labelUpdateInProgress.show()
self.dialog.labelStatusInfo.show()
def update_progress_bar(self, current_value: int, max_value: int) -> None:
"""Update the progress bar, showing it if it's hidden"""
self.show_progress_widgets()
region_size = 100 / self.number_of_progress_regions
value = (self.current_progress_region - 1) * region_size + (
current_value / max_value / self.number_of_progress_regions
) * region_size
self.dialog.progressBar.setValue(value)
if current_value < 0:
FreeCAD.Console.PrintWarning(
f"Addon Manager: Internal error, current progress value is negative in region {self.current_progress_region}"
)
def toggle_details(self) -> None:
if self.dialog.labelStatusInfo.isHidden():
self.dialog.labelStatusInfo.show()
self.dialog.buttonShowDetails.setArrowType(QtCore.Qt.DownArrow)
else:
self.dialog.labelStatusInfo.hide()
self.dialog.buttonShowDetails.setArrowType(QtCore.Qt.RightArrow)
self.show_progress_widgets()
region_size = 100.0 / self.number_of_progress_regions
completed_region_portion = (self.current_progress_region - 1) * region_size
current_region_portion = (float(current_value) / float(max_value)) * region_size
value = completed_region_portion + current_region_portion
self.dialog.progressBar.setValue(
value * 10
) # Out of 1000 segments, so it moves sort of smoothly
self.dialog.progressBar.repaint()
def stop_update(self) -> None:
self.cleanup_workers()

View File

@@ -28,26 +28,6 @@
<layout class="QVBoxLayout" name="layoutUpdateInProgress">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QToolButton" name="buttonShowDetails">
<property name="toolTip">
<string>Show details</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="arrowType">
<enum>Qt::RightArrow</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="labelUpdateInProgress">
<property name="text">
<string>Loading...</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="sizePolicy">

View File

@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>628</height>
<width>388</width>
<height>621</height>
</rect>
</property>
<property name="windowTitle">
@@ -35,6 +35,19 @@ installed addons will be checked for available updates
</property>
</widget>
</item>
<item>
<widget class="Gui::PrefCheckBox" name="guiprefcheckboxdownloadmacros">
<property name="text">
<string>Download Macro metadata (approximately 10MB)</string>
</property>
<property name="prefEntry" stdset="0">
<string>DownloadMacros</string>
</property>
<property name="prefPath" stdset="0">
<string>Addons</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>

View File

@@ -13,6 +13,7 @@ SET(AddonManager_SRCS
addonmanager_workers.py
AddonManager.ui
AddonManagerOptions.ui
first_run.ui
compact_view.py
expanded_view.py
package_list.py

View File

@@ -23,10 +23,11 @@
import os
import re
import sys
import io
import codecs
import shutil
from typing import Dict, Union, List
import time
from typing import Dict, Tuple, List, Union
import FreeCAD
@@ -56,10 +57,13 @@ class Macro(object):
self.on_wiki = False
self.on_git = False
self.desc = ""
self.comment = ""
self.code = ""
self.url = ""
self.version = ""
self.date = ""
self.src_filename = ""
self.author = ""
self.other_files = []
self.parsed = False
@@ -93,37 +97,71 @@ class Macro(object):
os.path.join(FreeCAD.getUserMacroDir(True), "Macro_" + self.filename)
)
def fill_details_from_file(self, filename):
with open(filename) as f:
# Number of parsed fields of metadata. For now, __Comment__,
# __Web__, __Version__, __Files__.
number_of_required_fields = 4
re_desc = re.compile(r"^__Comment__\s*=\s*(['\"])(.*)\1")
re_url = re.compile(r"^__Web__\s*=\s*(['\"])(.*)\1")
re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1")
re_files = re.compile(r"^__Files__\s*=\s*(['\"])(.*)\1")
for line in f.readlines():
match = re.match(re_desc, line)
if match:
self.desc = match.group(2)
number_of_required_fields -= 1
match = re.match(re_url, line)
if match:
self.url = match.group(2)
number_of_required_fields -= 1
match = re.match(re_version, line)
if match:
self.version = match.group(2)
number_of_required_fields -= 1
match = re.match(re_files, line)
if match:
self.other_files = [of.strip() for of in match.group(2).split(",")]
number_of_required_fields -= 1
if number_of_required_fields <= 0:
break
f.seek(0)
def fill_details_from_file(self, filename: str) -> None:
with open(filename, errors="replace") as f:
self.code = f.read()
self.parsed = True
self.fill_details_from_code(self.code)
def fill_details_from_code(self, code: str) -> None:
# Number of parsed fields of metadata. Overrides anything set previously (the code is considered authoritative).
# For now:
# __Comment__
# __Web__
# __Version__
# __Files__
# __Author__
# __Date__
max_lines_to_search = 50
line_counter = 0
number_of_fields = 5
ic = re.IGNORECASE # Shorten the line for Black
re_comment = re.compile(r"^__Comment__\s*=\s*(['\"])(.*)\1", flags=ic)
re_url = re.compile(r"^__Web__\s*=\s*(['\"])(.*)\1", flags=ic)
re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1", flags=ic)
re_files = re.compile(r"^__Files__\s*=\s*(['\"])(.*)\1", flags=ic)
re_author = re.compile(r"^__Author__\s*=\s*(['\"])(.*)\1", flags=ic)
re_date = re.compile(r"^__Date__\s*=\s*(['\"])(.*)\1", flags=ic)
f = io.StringIO(code)
while f and line_counter < max_lines_to_search:
line = f.readline()
line_counter += 1
if not line.startswith(
"__"
): # Speed things up a bit... this comparison is very cheap
continue
match = re.match(re_comment, line)
if match:
self.comment = match.group(2)
self.comment = re.sub("<.*?>", "", self.comment) # Strip any HTML tags
number_of_fields -= 1
match = re.match(re_author, line)
if match:
self.author = match.group(2)
number_of_fields -= 1
match = re.match(re_url, line)
if match:
self.url = match.group(2)
number_of_fields -= 1
match = re.match(re_version, line)
if match:
self.version = match.group(2)
number_of_fields -= 1
match = re.match(re_date, line)
if match:
self.date = match.group(2)
number_of_fields -= 1
match = re.match(re_files, line)
if match:
self.other_files = [of.strip() for of in match.group(2).split(",")]
number_of_fields -= 1
if number_of_fields <= 0:
break
# Truncate long comments to speed up searches, and clean up display
if len(self.comment) > 512:
self.comment = self.comment[:511] + ""
self.parsed = True
def fill_details_from_wiki(self, url):
code = ""
@@ -157,13 +195,9 @@ class Macro(object):
+ "\n"
)
return
# code = u2.read()
# github is slow to respond... We need to use this trick below
response = ""
block = 8192
# expected = int(u2.headers["content-length"])
while True:
# print("expected:", expected, "got:", len(response))
data = u2.read(block)
if not data:
break
@@ -200,12 +234,14 @@ class Macro(object):
FreeCAD.Console.PrintWarning(
translate(
"AddonsInstaller",
"Unable to retrieve a description for this macro.",
f"Unable to retrieve a description from the wiki for macro {self.name}",
)
+ "\n"
)
desc = "No description available"
self.desc = desc
self.comment, _, _ = desc.partition("<br") # Up to the first line break
self.comment = re.sub("<.*?>", "", self.comment) # Strip any tags
self.url = url
if isinstance(code, list):
flat_code = ""
@@ -213,9 +249,20 @@ class Macro(object):
flat_code += chunk
code = flat_code
self.code = code
self.parsed = True
self.fill_details_from_code(self.code)
if not self.author:
self.author = self.parse_desc("Author: ")
if not self.date:
self.date = self.parse_desc("Last modified: ")
def install(self, macro_dir: str) -> (bool, List[str]):
def parse_desc(self, line_start: str) -> Union[str, None]:
components = self.desc.split(">")
for component in components:
if component.startswith(line_start):
end = component.find("<")
return component[len(line_start) : end]
def install(self, macro_dir: str) -> Tuple[bool, List[str]]:
"""Install a macro and all its related files
Returns True if the macro was installed correctly.

View File

@@ -21,21 +21,19 @@
# * *
# ***************************************************************************
import codecs
import os
import re
import shutil
import sys
import ctypes
import tempfile
import ssl
from typing import Union
import urllib
from urllib.request import Request
from urllib.error import URLError
from urllib.parse import urlparse
from http.client import HTTPResponse
from PySide2 import QtGui, QtCore, QtWidgets
from PySide2 import QtCore, QtWidgets
import FreeCAD
import FreeCADGui
@@ -94,7 +92,7 @@ def symlink(source, link_name):
raise ctypes.WinError()
def urlopen(url: str):
def urlopen(url: str) -> Union[None, HTTPResponse]:
"""Opens an url with urllib and streams it to a temp file"""
timeout = 5
@@ -125,9 +123,7 @@ def urlopen(url: str):
u = urllib.request.urlopen(req, timeout=timeout)
except URLError as e:
FreeCAD.Console.PrintError(
translate("AddonsInstaller", f"Error loading {url}") + ":\n {e.reason}\n"
)
FreeCAD.Console.PrintLog(f"Error loading {url}:\n {e.reason}\n")
return None
except Exception:
return None
@@ -298,4 +294,76 @@ def fix_relative_links(text, base_url):
return new_text
def warning_color_string() -> str:
"""A shade of red, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio."""
warningColorString = "rgb(255,0,0)"
if hasattr(QtWidgets.QApplication.instance(), "styleSheet"):
# Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info
if "dark" in QtWidgets.QApplication.instance().styleSheet().lower():
warningColorString = "rgb(255,105,97)"
else:
warningColorString = "rgb(215,0,21)"
return warningColorString
def bright_color_string() -> str:
"""A shade of green, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio."""
brightColorString = "rgb(0,255,0)"
if hasattr(QtWidgets.QApplication.instance(), "styleSheet"):
# Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info
if "dark" in QtWidgets.QApplication.instance().styleSheet().lower():
brightColorString = "rgb(48,219,91)"
else:
brightColorString = "rgb(36,138,61)"
return brightColorString
def attention_color_string() -> str:
"""A shade of orange, adapted to darkmode if possible. Targets a minimum 7:1 contrast ratio."""
attentionColorString = "rgb(255,149,0)"
if hasattr(QtWidgets.QApplication.instance(), "styleSheet"):
# Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info
if "dark" in QtWidgets.QApplication.instance().styleSheet().lower():
attentionColorString = "rgb(255,179,64)"
else:
attentionColorString = "rgb(255,149,0)"
return attentionColorString
def get_macro_version_from_file(filename: str) -> str:
re_version = re.compile(r"^__Version__\s*=\s*(['\"])(.*)\1", flags=re.IGNORECASE)
with open(filename, "r", errors="ignore") as f:
line_counter = 0
max_lines_to_scan = 50
while line_counter < max_lines_to_scan:
line_counter += 1
line = f.readline()
if line.startswith("__"):
match = re.match(re_version, line)
if match:
return match.group(2)
return ""
def update_macro_installation_details(repo) -> None:
if repo is None or not hasattr(repo, "macro") or repo.macro is None:
FreeCAD.Console.PrintLog(f"Requested macro details for non-macro object\n")
return
test_file_one = os.path.join(FreeCAD.getUserMacroDir(True), repo.macro.filename)
test_file_two = os.path.join(
FreeCAD.getUserMacroDir(True), "Macro_" + repo.macro.filename
)
if os.path.exists(test_file_one):
repo.updated_timestamp = os.path.getmtime(test_file_one)
repo.installed_version = get_macro_version_from_file(test_file_one)
elif os.path.exists(test_file_two):
repo.updated_timestamp = os.path.getmtime(test_file_two)
repo.installed_version = get_macro_version_from_file(test_file_two)
else:
return
# @}

View File

@@ -31,10 +31,11 @@ import hashlib
import threading
import queue
import io
import time
from datetime import datetime
from typing import Union, List
from PySide2 import QtCore, QtGui, QtNetwork
from PySide2 import QtCore, QtNetwork
import FreeCAD
@@ -285,7 +286,6 @@ class LoadPackagesFromCacheWorker(QtCore.QThread):
class LoadMacrosFromCacheWorker(QtCore.QThread):
add_macro_signal = QtCore.Signal(object)
done = QtCore.Signal()
def __init__(self, cache_file: str):
QtCore.QThread.__init__(self)
@@ -299,8 +299,9 @@ class LoadMacrosFromCacheWorker(QtCore.QThread):
if QtCore.QThread.currentThread().isInterruptionRequested():
return
new_macro = Macro.from_cache(item)
self.add_macro_signal.emit(AddonManagerRepo.from_macro(new_macro))
self.done.emit()
repo = AddonManagerRepo.from_macro(new_macro)
utils.update_macro_installation_details(repo)
self.add_macro_signal.emit(repo)
class CheckWorkbenchesForUpdatesWorker(QtCore.QThread):
@@ -589,6 +590,7 @@ class FillMacroListWorker(QtCore.QThread):
macro.src_filename = os.path.join(dirpath, filename)
repo = AddonManagerRepo.from_macro(macro)
repo.url = "https://github.com/FreeCAD/FreeCAD-macros.git"
utils.update_macro_installation_details(repo)
self.add_macro_signal.emit(repo)
def retrieve_macros_from_wiki(self):
@@ -634,9 +636,137 @@ class FillMacroListWorker(QtCore.QThread):
macro.on_wiki = True
repo = AddonManagerRepo.from_macro(macro)
repo.url = "https://wiki.freecad.org/Macros_recipes"
utils.update_macro_installation_details(repo)
self.add_macro_signal.emit(repo)
class CacheMacroCode(QtCore.QThread):
"""Download and cache the macro code, and parse its internal metadata"""
status_message = QtCore.Signal(str)
update_macro = QtCore.Signal(AddonManagerRepo)
progress_made = QtCore.Signal(int, int)
def __init__(self, repos: List[AddonManagerRepo]) -> None:
QtCore.QThread.__init__(self)
self.repos = repos
self.workers = []
self.terminators = []
self.lock = threading.Lock()
self.failed = []
self.counter = 0
def run(self):
self.status_message.emit(translate("AddonsInstaller", "Caching macro code..."))
self.repo_queue = queue.Queue()
current_thread = QtCore.QThread.currentThread()
num_macros = 0
for repo in self.repos:
if repo.macro is not None:
self.repo_queue.put(repo)
num_macros += 1
# Emulate QNetworkAccessManager and spool up six connections:
for _ in range(6):
self.update_and_advance(None)
while True:
if current_thread.isInterruptionRequested():
for worker in self.workers:
worker.requestInterruption()
worker.wait(100)
if not worker.isFinished():
# Kill it
worker.terminate()
return
# Ensure our signals propagate out by running an internal thread-local event loop
QtCore.QCoreApplication.processEvents()
with self.lock:
if self.counter >= num_macros:
break
time.sleep(0.1)
# Make sure all of our child threads have fully exited:
for i, worker in enumerate(self.workers):
worker.wait(50)
if not worker.isFinished():
FreeCAD.Console.PrintError(
f"Addon Manager: a worker process failed to complete while fetching {worker.macro.name}\n"
)
worker.terminate()
self.repo_queue.join()
for terminator in self.terminators:
if terminator and terminator.isActive():
terminator.stop()
if len(self.failed) > 0:
num_failed = len(self.failed)
FreeCAD.Console.PrintWarning(
translate(
"AddonsInstaller",
f"Out of {num_macros} macros, {num_failed} timed out while processing",
)
)
def update_and_advance(self, repo: AddonManagerRepo) -> None:
if repo is not None:
if repo.macro.name not in self.failed:
self.update_macro.emit(repo)
self.repo_queue.task_done()
with self.lock:
self.counter += 1
if QtCore.QThread.currentThread().isInterruptionRequested():
return
self.progress_made.emit(
len(self.repos) - self.repo_queue.qsize(), len(self.repos)
)
try:
next_repo = self.repo_queue.get_nowait()
worker = GetMacroDetailsWorker(next_repo)
worker.finished.connect(lambda: self.update_and_advance(next_repo))
with self.lock:
self.workers.append(worker)
self.terminators.append(
QtCore.QTimer.singleShot(10000, lambda: self.terminate(worker))
)
self.status_message.emit(
translate(
"AddonsInstaller",
f"Getting metadata from macro {next_repo.macro.name}",
)
)
worker.start()
except queue.Empty:
pass
def terminate(self, worker) -> None:
if not worker.isFinished():
macro_name = worker.macro.name
FreeCAD.Console.PrintWarning(
translate(
"AddonsInstaller",
f"Timeout while fetching metadata for macro {macro_name}",
)
+ "\n"
)
worker.requestInterruption()
worker.wait(100)
if worker.isRunning():
worker.terminate()
worker.wait(50)
if worker.isRunning():
FreeCAD.Console.PrintError(
f"Failed to kill process for macro {macro_name}!\n"
)
with self.lock:
self.failed.append(macro_name)
class ShowWorker(QtCore.QThread):
"""This worker retrieves info of a given workbench"""
@@ -927,17 +1057,8 @@ class GetMacroDetailsWorker(QtCore.QThread):
mac = mac.replace("+", "%2B")
url = "https://wiki.freecad.org/Macro_" + mac
self.macro.fill_details_from_wiki(url)
if self.macro.is_installed():
already_installed_msg = (
'<strong style="background: #00B629;">'
+ translate("AddonsInstaller", "This macro is already installed.")
+ "</strong><br>"
)
else:
already_installed_msg = ""
message = (
already_installed_msg
+ "<h1>"
"<h1>"
+ self.macro.name
+ "</h1>"
+ self.macro.desc
@@ -1545,11 +1666,15 @@ class UpdateAllWorker(QtCore.QThread):
self.done.emit()
def on_success(self, repo: AddonManagerRepo) -> None:
self.progress_made.emit(self.repo_queue.qsize(), len(self.repos))
self.progress_made.emit(
len(self.repos) - self.repo_queue.qsize(), len(self.repos)
)
self.success.emit(repo)
def on_failure(self, repo: AddonManagerRepo) -> None:
self.progress_made.emit(self.repo_queue.qsize(), len(self.repos))
self.progress_made.emit(
len(self.repos) - self.repo_queue.qsize(), len(self.repos)
)
self.failure.emit(repo)
@@ -1589,6 +1714,7 @@ class UpdateSingleWorker(QtCore.QThread):
install_succeeded, errors = repo.macro.install(
FreeCAD.getUserMacroDir(True)
)
utils.update_macro_installation_details(repo)
if install_succeeded:
self.success.emit(repo)

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>398</width>
<height>237</height>
</rect>
</property>
<property name="windowTitle">
<string>Welcome to the Addon Manager</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="labelWarning">
<property name="text">
<string>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!</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Download Settings</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxAutoCheck">
<property name="text">
<string>Automatically check installed Addons for updates</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxDownloadMacroMetadata">
<property name="text">
<string>Download Macro metadata (approximately 10MB)</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="comboBoxProxy">
<item>
<property name="text">
<string>No proxy</string>
</property>
</item>
<item>
<property name="text">
<string>System proxy</string>
</property>
</item>
<item>
<property name="text">
<string>User-defined proxy:</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEditProxy"/>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>These and other settings are available in the FreeCAD Preferences window.</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -31,12 +31,14 @@ from datetime import date, timedelta
import FreeCAD
from addonmanager_utilities import translate # this needs to be as is for pylupdate
import addonmanager_utilities as utils
from addonmanager_workers import ShowWorker, GetMacroDetailsWorker
from AddonManagerRepo import AddonManagerRepo
import inspect
translate = FreeCAD.Qt.translate
class PackageDetails(QWidget):
@@ -93,31 +95,34 @@ class PackageDetails(QWidget):
self.ui.buttonExecute.hide()
if repo.update_status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
installed_version_string = ""
if repo.installed_version:
installed_version_string = translate("AddonsInstaller", "Version") + " "
installed_version_string += repo.installed_version
else:
installed_version_string = (
translate(
"AddonsInstaller", "Unknown version (no package.xml file found)"
)
+ " "
)
version = repo.installed_version
date = ""
installed_version_string = "<h3>"
if repo.updated_timestamp:
installed_version_string += (
" " + translate("AddonsInstaller", "installed on") + " "
)
installed_version_string += (
date = (
QDateTime.fromTime_t(repo.updated_timestamp)
.date()
.toString(Qt.SystemLocaleShortDate)
)
installed_version_string += ". "
if version and date:
installed_version_string += (
translate(
"AddonsInstaller", f"Version {version} installed on {date}"
)
+ ". "
)
elif version:
installed_version_string += (
translate("AddonsInstaller", f"Version {version} installed") + ". "
)
elif date:
installed_version_string += (
translate("AddonsInstaller", f"Installed on {date}") + ". "
)
else:
installed_version_string += (
translate("AddonsInstaller", "installed") + ". "
translate("AddonsInstaller", "Installed") + ". "
)
if repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
@@ -129,12 +134,20 @@ class PackageDetails(QWidget):
)
installed_version_string += repo.metadata.Version
installed_version_string += ".</b>"
elif repo.macro and repo.macro.version:
installed_version_string += (
"<b>"
+ translate("AddonsInstaller", "Update available to version")
+ " "
)
installed_version_string += repo.macro.version
installed_version_string += ".</b>"
else:
installed_version_string += (
"<b>"
+ translate(
"AddonsInstaller",
"Update available to unknown version (no package.xml file found)",
"An update is available",
)
+ ".</b>"
)
@@ -166,19 +179,32 @@ class PackageDetails(QWidget):
+ "."
)
basedir = FreeCAD.getUserAppDataDir()
moddir = os.path.join(basedir, "Mod", repo.name)
installed_version_string += (
"<br/>"
+ translate("AddonsInstaller", "Installation location")
+ ": "
+ moddir
installed_version_string += "</h3>"
self.ui.labelPackageDetails.setText(installed_version_string)
if repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
self.ui.labelPackageDetails.setStyleSheet(
"color:" + utils.attention_color_string()
)
else:
self.ui.labelPackageDetails.setStyleSheet(
"color:" + utils.bright_color_string()
)
self.ui.labelPackageDetails.show()
if repo.macro is not None:
moddir = FreeCAD.getUserMacroDir(True)
else:
basedir = FreeCAD.getUserAppDataDir()
moddir = os.path.join(basedir, "Mod", repo.name)
installationLocationString = (
translate("AddonsInstaller", "Installation location") + ": " + moddir
)
self.ui.labelPackageDetails.setText(installed_version_string)
self.ui.labelPackageDetails.show()
self.ui.labelInstallationLocation.setText(installationLocationString)
self.ui.labelInstallationLocation.show()
else:
self.ui.labelPackageDetails.hide()
self.ui.labelInstallationLocation.hide()
if repo.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
self.ui.buttonInstall.show()
@@ -206,14 +232,6 @@ class PackageDetails(QWidget):
self.ui.buttonUpdate.hide()
self.ui.buttonCheckForUpdate.hide()
warningColorString = "rgb(255,0,0)"
if hasattr(QApplication.instance(),"styleSheet"):
# Qt 5.9 doesn't give a QApplication instance, so can't give the stylesheet info
if "dark" in QApplication.instance().styleSheet().lower():
warningColorString = "rgb(255,50,50)"
else:
warningColorString = "rgb(200,0,0)"
if repo.obsolete:
self.ui.labelWarningInfo.show()
self.ui.labelWarningInfo.setText(
@@ -221,7 +239,9 @@ class PackageDetails(QWidget):
+ translate("AddonsInstaller", "WARNING: This addon is obsolete")
+ "</h1>"
)
self.ui.labelWarningInfo.setStyleSheet("color:" + warningColorString)
self.ui.labelWarningInfo.setStyleSheet(
"color:" + utils.warning_color_string()
)
elif repo.python2:
self.ui.labelWarningInfo.show()
self.ui.labelWarningInfo.setText(
@@ -229,7 +249,9 @@ class PackageDetails(QWidget):
+ translate("AddonsInstaller", "WARNING: This addon is Python 2 Only")
+ "</h1>"
)
self.ui.labelWarningInfo.setStyleSheet("color:" + warningColorString)
self.ui.labelWarningInfo.setStyleSheet(
"color:" + utils.warning_color_string()
)
else:
self.ui.labelWarningInfo.hide()
@@ -416,6 +438,12 @@ class Ui_PackageDetails(object):
self.verticalLayout_2.addWidget(self.labelPackageDetails)
self.labelInstallationLocation = QLabel(PackageDetails)
self.labelInstallationLocation.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.labelInstallationLocation.hide()
self.verticalLayout_2.addWidget(self.labelInstallationLocation)
self.labelWarningInfo = QLabel(PackageDetails)
self.labelWarningInfo.hide()

View File

@@ -358,6 +358,32 @@ class PackageListItemDelegate(QStyledItemDelegate):
f"\n{maintainer['name']} <{maintainer['email']}>"
)
self.widget.ui.labelMaintainer.setText(maintainers_string)
elif repo.macro and repo.macro.parsed:
self.widget.ui.labelDescription.setText(repo.macro.comment)
self.widget.ui.labelVersion.setText(repo.macro.version)
if repo.macro.date:
if repo.macro.version:
new_label = (
"v"
+ repo.macro.version
+ ", "
+ translate("AddonsInstaller", "updated")
+ " "
+ repo.macro.date
)
else:
new_label = (
translate("AddonsInstaller", "Updated") + " " + repo.macro.date
)
self.widget.ui.labelVersion.setText(new_label)
if self.displayStyle == ListDisplayStyle.EXPANDED:
if repo.macro.author:
caption = translate("AddonsInstaller", "Author")
self.widget.ui.labelMaintainer.setText(
caption + ": " + repo.macro.author
)
else:
self.widget.ui.labelMaintainer.setText("")
else:
self.widget.ui.labelDescription.setText("")
self.widget.ui.labelVersion.setText("")
@@ -533,6 +559,12 @@ class PackageListFilter(QSortFilterProxyModel):
return True
if re.match(desc).hasMatch():
return True
if (
data.macro
and data.macro.comment
and re.match(data.macro.comment).hasMatch()
):
return True
return False
else:
return False

View File

@@ -197,6 +197,7 @@ class Move(gui_base_original.Modifier):
def move_subelements(self, is_copy):
"""Move the subelements."""
Gui.addModule("Draft")
try:
if is_copy:
self.commit(translate("draft", "Copy"),

View File

@@ -271,6 +271,7 @@ class Rotate(gui_base_original.Modifier):
def rotate_subelements(self, is_copy):
"""Rotate the subelements."""
Gui.addModule("Draft")
try:
if is_copy:
self.commit(translate("draft", "Copy"),

View File

@@ -192,6 +192,7 @@ class Scale(gui_base_original.Modifier):
the selected object is not a rectangle or another object
that can't be used with `scaleVertex` and `scaleEdge`.
"""
Gui.addModule("Draft")
try:
if self.task.isCopy.isChecked():
self.commit(translate("draft", "Copy"),

View File

@@ -9,7 +9,6 @@
\brief Airfoil (.dat) file importer
This module provides support for importing airfoil .dat files.
Note (2019): this module has been unmaintained for a long time.
'''
# Check code with
# flake8 --ignore=E226,E266,E401,W503
@@ -103,8 +102,9 @@ def open(filename):
"""
docname = os.path.splitext(os.path.basename(filename))[0]
doc = FreeCAD.newDocument(docname)
doc.Label = decodeName(docname[:-4])
process(doc, filename)
doc.Label = decodeName(docname)
process(filename)
doc.recompute()
def insert(filename, docname):
@@ -131,13 +131,16 @@ def insert(filename, docname):
doc = FreeCAD.getDocument(docname)
except NameError:
doc = FreeCAD.newDocument(docname)
importgroup = doc.addObject("App::DocumentObjectGroup", groupname)
importgroup.Label = decodeName(groupname)
process(doc, filename)
obj = process(filename)
if obj is not None:
importgroup = doc.addObject("App::DocumentObjectGroup", groupname)
importgroup.Label = decodeName(groupname)
importgroup.Group = [obj]
doc.recompute()
def process(doc, filename):
"""Process the filename and provide the document with the information.
def process(filename):
"""Process the filename and create a Draft Wire from the data.
The common airfoil dat format has many flavors.
This code should work with almost every dialect.
@@ -146,18 +149,15 @@ def process(doc, filename):
----------
filename : str
The path to the filename to be opened.
docname : str
The name of the active App::Document if one exists, or
of the new one created.
Returns
-------
App::Document
The active FreeCAD document, or the document created if none exists,
with the parsed information.
Part::Part2DObject or None.
The created Draft Wire object or None if the file contains less
than 3 points.
"""
# Regex to identify data rows and throw away unused metadata
xval = r'(?P<xval>(\-|\d*)\.*\d*([Ee]\-?\d+)?)'
xval = r'(?P<xval>\-?\s*\d*\.*\d*([Ee]\-?\d+)?)'
yval = r'(?P<yval>\-?\s*\d*\.*\d*([Ee]\-?\d+)?)'
_regex = r'^\s*' + xval + r'\,?\s*' + yval + r'\s*$'
@@ -170,21 +170,28 @@ def process(doc, filename):
# upside = True
# last_x = None
# Collect the data for the upper and the lower side separately if possible
# Collect the data
for lin in afile:
curdat = regex.match(lin)
if curdat is not None:
if (curdat is not None
and curdat.group("xval")
and curdat.group("yval")):
x = float(curdat.group("xval"))
y = float(curdat.group("yval"))
# the normal processing
coords.append(Vector(x, y, 0))
# Some files specify the number of upper and lower points on the 2nd line:
# " 67. 72."
# See: http://airfoiltools.com/airfoil
# This line must be skipped:
if x < 2 and y < 2:
# the normal processing
coords.append(Vector(x, y, 0))
afile.close()
if len(coords) < 3:
FCC.PrintError(translate("ImportAirfoilDAT", "Did not find enough coordinates") + "\n")
return
return None
# sometimes coords are divided in upper an lower side
# so that x-coordinate begin new from leading or trailing edge
@@ -227,4 +234,4 @@ def process(doc, filename):
obj = FreeCAD.ActiveDocument.addObject('Part::Feature', airfoilname)
obj.Shape = face
doc.recompute()
return obj

View File

@@ -162,7 +162,7 @@ void DrawPage::onChanged(const App::Property* prop)
//Page is just a container. It doesn't "do" anything.
App::DocumentObjectExecReturn *DrawPage::execute(void)
{
return App::DocumentObject::StdReturn;
return App::DocumentObject::execute();
}
// this is now irrelevant, b/c DP::execute doesn't do anything.

View File

@@ -54,6 +54,7 @@ else()
endif()
set(TechDrawGui_UIC_SRCS
DlgPageChooser.ui
DlgPrefsTechDrawAdvanced.ui
DlgPrefsTechDrawAnnotation.ui
DlgPrefsTechDrawColors.ui
@@ -117,6 +118,9 @@ SET(TechDrawGui_SRCS
TaskProjGroup.ui
TaskProjGroup.cpp
TaskProjGroup.h
DlgPageChooser.ui
DlgPageChooser.cpp
DlgPageChooser.h
DlgPrefsTechDrawGeneral.ui
DlgPrefsTechDrawGeneralImp.cpp
DlgPrefsTechDrawGeneralImp.h

View File

@@ -141,6 +141,7 @@ void CmdTechDrawPageDefault::activated(int iMsg)
doCommand(Doc,"App.activeDocument().%s.Template = '%s'",TemplateName.c_str(), templateFileName.toStdString().c_str());
doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str());
updateActive();
commitCommand();
TechDraw::DrawPage* fp = dynamic_cast<TechDraw::DrawPage*>(getDocument()->getObject(PageName.c_str()));
if (!fp) {
@@ -220,6 +221,7 @@ void CmdTechDrawPageTemplate::activated(int iMsg)
doCommand(Doc,"App.activeDocument().%s.Template = App.activeDocument().%s",PageName.c_str(),TemplateName.c_str());
// consider renaming DrawSVGTemplate.Template property?
updateActive();
commitCommand();
TechDraw::DrawPage* fp = dynamic_cast<TechDraw::DrawPage*>(getDocument()->getObject(PageName.c_str()));
if (!fp) {

View File

@@ -0,0 +1,103 @@
/****************************************************************************
* Copyright (c) 2021 Wanderer Fan <wandererfan@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
****************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <Qt>
# include <QListWidget>
# include <QListWidgetItem>
# include <QPushButton>
# include <QList>
#endif
#include <Base/Console.h>
#include <Base/Tools.h>
#include "DlgPageChooser.h"
#include "ui_DlgPageChooser.h"
FC_LOG_LEVEL_INIT("Gui",true,true)
using namespace TechDrawGui;
/* TRANSLATOR Gui::DlgPageChooser */
DlgPageChooser::DlgPageChooser(
const std::vector<std::string> labels,
const std::vector<std::string> names,
QWidget* parent, Qt::WindowFlags fl)
: QDialog(parent, fl), ui(new Ui_DlgPageChooser)
{
ui->setupUi(this);
ui->lwPages->setSortingEnabled(true);
fillList(labels, names);
connect(ui->bbButtons, SIGNAL(accepted()), this, SLOT(accept()));
connect(ui->bbButtons, SIGNAL(rejected()), this, SLOT(reject()));
}
/**
* Destroys the object and frees any allocated resources
*/
DlgPageChooser::~DlgPageChooser()
{
// no need to delete child widgets, Qt does it all for us
delete ui;
}
void DlgPageChooser::fillList(std::vector<std::string> labels, std::vector<std::string> names)
{
QListWidgetItem* item;
QString qLabel;
QString qName;
QString qText;
int labelCount = labels.size();
int i = 0;
for(; i < labelCount; i++) {
qLabel = Base::Tools::fromStdString(labels[i]);
qName = Base::Tools::fromStdString(names[i]);
qText = QString::fromUtf8("%1 (%2)").arg(qLabel).arg(qName);
item = new QListWidgetItem(qText, ui->lwPages);
item->setData(Qt::UserRole, qName);
}
}
std::string DlgPageChooser::getSelection() const
{
std::string result;
QList<QListWidgetItem*> sels = ui->lwPages->selectedItems();
if (!sels.empty()) {
QListWidgetItem* item = sels.front();
result = item->data(Qt::UserRole).toByteArray().constData();
}
return result;
}
void DlgPageChooser::accept() {
QDialog::accept();
}
void DlgPageChooser::reject() {
QDialog::reject();
}
#include "moc_DlgPageChooser.cpp"

View File

@@ -0,0 +1,57 @@
/****************************************************************************
* Copyright (c) 2021 Wanderer Fan <wandererfan@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
****************************************************************************/
#ifndef GUI_DLGPAGECHOOSER_H
#define GUI_DLGPAGECHOOSER_H
#include <QDialog>
namespace TechDrawGui {
class Ui_DlgPageChooser;
class TechDrawGuiExport DlgPageChooser : public QDialog
{
Q_OBJECT
public:
DlgPageChooser(const std::vector<std::string> labels,
const std::vector<std::string> names,
QWidget* parent = 0, Qt::WindowFlags fl = Qt::WindowFlags());
~DlgPageChooser();
std::string getSelection() const;
void accept();
void reject();
private Q_SLOTS:
private:
void fillList(std::vector<std::string> labels, std::vector<std::string> names);
private:
Ui_DlgPageChooser* ui;
};
} // namespace Gui
#endif // GUI_DLGPAGECHOOSER_H

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TechDrawGui::DlgPageChooser</class>
<widget class="QDialog" name="TechDrawGui::DlgPageChooser">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>360</width>
<height>280</height>
</rect>
</property>
<property name="windowTitle">
<string>Page Chooser</string>
</property>
<property name="toolTip">
<string/>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>FreeCAD could not determine which Page to use. Please select a Page.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QListWidget" name="lwPages">
<property name="toolTip">
<string>Select a Page that should be used</string>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="bbButtons">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
<property name="centerButtons">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>bbButtons</sender>
<signal>accepted()</signal>
<receiver>TechDrawGui::DlgPageChooser</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>179</x>
<y>228</y>
</hint>
<hint type="destinationlabel">
<x>179</x>
<y>139</y>
</hint>
</hints>
</connection>
<connection>
<sender>bbButtons</sender>
<signal>rejected()</signal>
<receiver>TechDrawGui::DlgPageChooser</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>179</x>
<y>228</y>
</hint>
<hint type="destinationlabel">
<x>179</x>
<y>139</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@@ -80,6 +80,7 @@
#include "QGVPage.h"
#include "MDIViewPage.h"
#include "ViewProviderPage.h"
#include "DlgPageChooser.h"
#include "DrawGuiUtil.h"
using namespace TechDrawGui;
@@ -104,6 +105,8 @@ void DrawGuiUtil::loadArrowBox(QComboBox* qcb)
TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd)
{
TechDraw::DrawPage* page = nullptr;
std::vector<std::string> names;
std::vector<std::string> labels;
//check Selection for a page
std::vector<App::DocumentObject*> selPages = cmd->getSelection().
@@ -127,8 +130,18 @@ TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd)
page = qp->getDrawPage();
} else {
// no active page
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Which page?"),
QObject::tr("Can not determine correct page."));
for(auto obj: selPages) {
std::string name = obj->getNameInDocument();
names.push_back(name);
std::string label = obj->Label.getValue();
labels.push_back(label);
}
DlgPageChooser dlg(labels, names, Gui::getMainWindow());
if(dlg.exec()==QDialog::Accepted) {
std::string selName = dlg.getSelection();
App::Document* doc = cmd->getDocument();
page = static_cast<TechDraw::DrawPage*>(doc->getObject(selName.c_str()));
}
}
} else {
//only 1 page in document - use it
@@ -136,8 +149,18 @@ TechDraw::DrawPage* DrawGuiUtil::findPage(Gui::Command* cmd)
}
} else if (selPages.size() > 1) {
//multiple pages in selection
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Too many pages"),
QObject::tr("Select only 1 page."));
for(auto obj: selPages) {
std::string name = obj->getNameInDocument();
names.push_back(name);
std::string label = obj->Label.getValue();
labels.push_back(label);
}
DlgPageChooser dlg(labels, names, Gui::getMainWindow());
if(dlg.exec()==QDialog::Accepted) {
std::string selName = dlg.getSelection();
App::Document* doc = cmd->getDocument();
page = static_cast<TechDraw::DrawPage*>(doc->getObject(selName.c_str()));
}
} else {
//exactly 1 page in selection, use it
page = static_cast<TechDraw::DrawPage*>(selPages.front());