Merge branch 'master' into lupdateCPPWorkaround
This commit is contained in:
90
.github/codespellignore
vendored
Normal file
90
.github/codespellignore
vendored
Normal 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
31
.github/workflows/codespell.yml
vendored
Normal 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 }}
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
# @}
|
||||
|
||||
@@ -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)
|
||||
|
||||
144
src/Mod/AddonManager/first_run.ui
Normal file
144
src/Mod/AddonManager/first_run.ui
Normal 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>
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
103
src/Mod/TechDraw/Gui/DlgPageChooser.cpp
Normal file
103
src/Mod/TechDraw/Gui/DlgPageChooser.cpp
Normal 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"
|
||||
57
src/Mod/TechDraw/Gui/DlgPageChooser.h
Normal file
57
src/Mod/TechDraw/Gui/DlgPageChooser.h
Normal 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
|
||||
|
||||
93
src/Mod/TechDraw/Gui/DlgPageChooser.ui
Normal file
93
src/Mod/TechDraw/Gui/DlgPageChooser.ui
Normal 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>
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user