Merge pull request #18 from APEbbers/Develop

Develop
This commit is contained in:
Paul Ebbers 2025-02-23 18:19:05 +01:00 committed by GitHub
commit d013d480f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 350 additions and 124 deletions

View File

@ -6,66 +6,34 @@ wax = None
sea = None
tbr = None
# Define the translation
translate = App.Qt.translate
def QT_TRANSLATE_NOOP(context, text):
return text
def addToolSearchBox():
import FreeCADGui
from PySide import QtGui
import SearchBoxLight
# Define the translation
translate = App.Qt.translate
global wax, sea, tbr
mw = FreeCADGui.getMainWindow()
mw = Gui.getMainWindow()
import SearchBox
from PySide6.QtWidgets import QToolBar
from PySide6.QtGui import QShortcut, QKeySequence
if mw:
if sea is None:
sea = SearchBoxLight.SearchBoxLight(
getItemGroups=lambda: __import__("GetItemGroups").getItemGroups(),
getToolTip=lambda groupId, setParent: __import__(
"GetItemGroups"
).getToolTip(groupId, setParent),
getItemDelegate=lambda: __import__(
"IndentedItemDelegate"
).IndentedItemDelegate(),
)
sea.resultSelected.connect(
lambda index, groupId: __import__("GetItemGroups").onResultSelected(
index, groupId
)
)
if wax is None:
wax = QtGui.QWidgetAction(None)
wax.setWhatsThis(
translate(
"SearchBar",
"Use this search bar to find tools, document objects, preferences and more",
)
)
sea.setWhatsThis(
translate(
"SearchBar",
"Use this search bar to find tools, document objects, preferences and more",
)
)
wax.setDefaultWidget(sea)
##mbr.addWidget(sea)
# mbr.addAction(wax)
wax = SearchBox.SearchBoxFunction(mw)
if tbr is None:
tbr = QtGui.QToolBar("SearchBar") # QtGui.QDockWidget()
# Include FreeCAD in the name so that one can find windows labeled with FreeCAD easily in window managers which allow search through the list of open windows.
tbr = QToolBar("SearchBar") # QtGui.QDockWidget()
# Include FreeCAD in the name so that one can find windows labeled with
# FreeCAD easily in window managers which allow search through the list of open windows.
tbr.setObjectName("SearchBar")
tbr.addAction(wax)
mw.addToolBar(tbr)
tbr.show()
return
addToolSearchBox()
import FreeCADGui
FreeCADGui.getMainWindow().workbenchActivated.connect(addToolSearchBox)
Gui.getMainWindow().workbenchActivated.connect(addToolSearchBox)

View File

@ -1,41 +1,49 @@
import os
import FreeCAD as App
import FreeCADGui as Gui
import StyleMapping_SearchBar
# Define the translation
translate = App.Qt.translate
def loadAllWorkbenches():
from PySide import QtGui
import FreeCADGui
import FreeCADGui as Gui
from PySide.QtGui import QLabel
from PySide.QtCore import Qt, SIGNAL, Signal, QObject, QThread, QSize
from PySide.QtGui import QIcon, QPixmap, QAction, QGuiApplication
activeWorkbench = Gui.activeWorkbench().name()
lbl = QLabel(translate("SearchBar", "Loading workbench … (…/…)"))
lbl.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
# Get the stylesheet from the main window and use it for this form
lbl.setStyleSheet("background-color: " + StyleMapping_SearchBar.ReturnStyleItem("Background_Color") + ";")
# # Get the main window from FreeCAD
# mw = Gui.getMainWindow()
# # Center the widget
# cp = QGuiApplication.screenAt(mw.pos()).geometry().center()
# lbl.move(cp)
activeWorkbench = FreeCADGui.activeWorkbench().name()
lbl = QtGui.QLabel(translate("SearchBar", "Loading workbench … (…/…)"))
lbl.show()
lst = FreeCADGui.listWorkbenches()
lst = Gui.listWorkbenches()
for i, wb in enumerate(lst):
msg = (
translate("SearchBar", "Loading workbench ")
+ wb
+ " ("
+ str(i)
+ "/"
+ str(len(lst))
+ ")"
)
msg = translate("SearchBar", "Loading workbench ") + wb + " (" + str(i + 1) + "/" + str(len(lst)) + ")"
print(msg)
lbl.setText(msg)
geo = lbl.geometry()
geo.setSize(lbl.sizeHint())
lbl.setGeometry(geo)
lbl.repaint()
FreeCADGui.updateGui() # Probably slower with this, because it redraws the entire GUI with all tool buttons changed etc. but allows the label to actually be updated, and it looks nice and gives a quick overview of all the workbenches…
Gui.updateGui() # Probably slower with this, because it redraws the entire GUI with all tool buttons changed etc. but allows the label to actually be updated, and it looks nice and gives a quick overview of all the workbenches…
try:
FreeCADGui.activateWorkbench(wb)
except:
Gui.activateWorkbench(wb)
except Exception:
pass
lbl.hide()
FreeCADGui.activateWorkbench(activeWorkbench)
Gui.activateWorkbench(activeWorkbench)
return
def cachePath():
@ -91,23 +99,25 @@ def refreshToolbars(doLoadAllWorkbenches=True):
def refreshToolsAction():
from PySide import QtGui
from PySide.QtWidgets import QApplication, QMessageBox
from PySide.QtCore import Qt
print("Refresh cached results")
fw = QtGui.QApplication.focusWidget()
if fw is not None:
fw.clearFocus()
reply = QtGui.QMessageBox.question(
None,
msgBox = QMessageBox()
msgBox.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
# Get the main window from FreeCAD
mw = Gui.getMainWindow()
reply = msgBox.question(
mw,
translate("SearchBar", "Load all workbenches?"),
translate(
"SearchBar",
"""Load all workbenches? This can cause FreeCAD to become unstable, and this "reload tools" feature contained a bug that crashed freecad systematically, so please make sure you save your work before. It\'s a good idea to restart FreeCAD after this operation.""",
),
QtGui.QMessageBox.Yes,
QtGui.QMessageBox.No,
QMessageBox.Yes,
QMessageBox.No,
)
if reply == QtGui.QMessageBox.Yes:
if reply == QMessageBox.Yes:
refreshToolbars()
else:
print("cancelled")

View File

@ -27,6 +27,7 @@ from PySide.QtWidgets import (
QVBoxLayout,
QApplication,
QListWidget,
QWidgetAction,
)
from PySide.QtGui import (
QIcon,
@ -48,6 +49,11 @@ globalIgnoreFocusOut = False
# Define the translation
translate = App.Qt.translate
# Avoid garbage collection by storing the action in a global variable
wax = None
sea = None
tbr = None
def easyToolTipWidget(html):
foo = QTextEdit()
@ -57,6 +63,41 @@ def easyToolTipWidget(html):
return foo
def SearchBoxFunction(mw):
import SearchBoxLight
global wax, sea, tbr
if mw:
if sea is None:
sea = SearchBoxLight.SearchBoxLight(
getItemGroups=lambda: __import__("GetItemGroups").getItemGroups(),
getToolTip=lambda groupId, setParent: __import__("GetItemGroups").getToolTip(groupId, setParent),
getItemDelegate=lambda: __import__("IndentedItemDelegate").IndentedItemDelegate(),
)
sea.resultSelected.connect(
lambda index, groupId: __import__("GetItemGroups").onResultSelected(index, groupId)
)
if wax is None:
wax = QWidgetAction(None)
wax.setWhatsThis(
translate(
"SearchBar",
"Use this search bar to find tools, document objects, preferences and more",
)
)
sea.setWhatsThis(
translate(
"SearchBar",
"Use this search bar to find tools, document objects, preferences and more",
)
)
wax.setDefaultWidget(sea)
return wax
class SearchBox(QLineEdit):
# The following block of code is present in the lightweight proxy SearchBoxLight
"""
@ -91,9 +132,7 @@ class SearchBox(QLineEdit):
self.getItemGroups = getItemGroups
self.getToolTip = getToolTip
self.itemGroups = None # Will be initialized by calling getItemGroups() the first time the search box gains focus, through focusInEvent and refreshItemGroups
self.maxVisibleRows = (
maxVisibleRows # TODO: use this to compute the correct height
)
self.maxVisibleRows = maxVisibleRows # TODO: use this to compute the correct height
# Create proxy model
self.proxyModel = QIdentityProxyModel()
# Filtered model to which items are manually added. Store it as a property of the object instead of a local variable, to prevent grbage collection.
@ -105,9 +144,7 @@ class SearchBox(QLineEdit):
self.listView.setWindowFlag(Qt.WindowType.FramelessWindowHint)
self.listView.setSelectionMode(self.listView.SelectionMode.SingleSelection)
self.listView.setModel(self.proxyModel)
self.listView.setItemDelegate(
getItemDelegate()
) # https://stackoverflow.com/a/65930408/324969
self.listView.setItemDelegate(getItemDelegate()) # https://stackoverflow.com/a/65930408/324969
self.listView.setMouseTracking(True)
# make the QListView non-editable
self.listView.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
@ -137,18 +174,10 @@ class SearchBox(QLineEdit):
# Note: should probably use the eventFilter method instead...
wdgctx = Qt.ShortcutContext.WidgetShortcut
QShortcut(
QKeySequence(Qt.Key.Key_Down), self, context=wdgctx
).activated.connect(self.listDown)
QShortcut(QKeySequence(Qt.Key.Key_Up), self, context=wdgctx).activated.connect(
self.listUp
)
QShortcut(
QKeySequence(Qt.Key.Key_PageDown), self, context=wdgctx
).activated.connect(self.listPageDown)
QShortcut(
QKeySequence(Qt.Key.Key_PageUp), self, context=wdgctx
).activated.connect(self.listPageUp)
QShortcut(QKeySequence(Qt.Key.Key_Down), self, context=wdgctx).activated.connect(self.listDown)
QShortcut(QKeySequence(Qt.Key.Key_Up), self, context=wdgctx).activated.connect(self.listUp)
QShortcut(QKeySequence(Qt.Key.Key_PageDown), self, context=wdgctx).activated.connect(self.listPageDown)
QShortcut(QKeySequence(Qt.Key.Key_PageUp), self, context=wdgctx).activated.connect(self.listPageUp)
# Home and End do not work, for some reason.
# QShortcut(QKeySequence.MoveToEndOfDocument, self, context = wdgctx).activated.connect(self.listEnd)
@ -156,25 +185,13 @@ class SearchBox(QLineEdit):
# QShortcut(QKeySequence(Qt.Key.Key_End), self, context = wdgctx).activated.connect(self.listEnd)
# QShortcut(QKeySequence('Home'), self, context = wdgctx).activated.connect(self.listStart)
QShortcut(
QKeySequence(Qt.Key.Key_Enter), self, context=wdgctx
).activated.connect(self.listAccept)
QShortcut(
QKeySequence(Qt.Key.Key_Return), self, context=wdgctx
).activated.connect(self.listAccept)
QShortcut(QKeySequence("Ctrl+Return"), self, context=wdgctx).activated.connect(
self.listAcceptToggle
)
QShortcut(QKeySequence("Ctrl+Enter"), self, context=wdgctx).activated.connect(
self.listAcceptToggle
)
QShortcut(QKeySequence("Ctrl+Space"), self, context=wdgctx).activated.connect(
self.listAcceptToggle
)
QShortcut(QKeySequence(Qt.Key.Key_Enter), self, context=wdgctx).activated.connect(self.listAccept)
QShortcut(QKeySequence(Qt.Key.Key_Return), self, context=wdgctx).activated.connect(self.listAccept)
QShortcut(QKeySequence("Ctrl+Return"), self, context=wdgctx).activated.connect(self.listAcceptToggle)
QShortcut(QKeySequence("Ctrl+Enter"), self, context=wdgctx).activated.connect(self.listAcceptToggle)
QShortcut(QKeySequence("Ctrl+Space"), self, context=wdgctx).activated.connect(self.listAcceptToggle)
QShortcut(
QKeySequence(Qt.Key.Key_Escape), self, context=wdgctx
).activated.connect(self.listCancel)
QShortcut(QKeySequence(Qt.Key.Key_Escape), self, context=wdgctx).activated.connect(self.listCancel)
# Initialize the model with the full list (assuming the text() is empty)
# self.proxyFilterModel(self.text()) # This is done by refreshItemGroups on focusInEvent, because the initial loading from cache can take time
@ -221,9 +238,7 @@ class SearchBox(QLineEdit):
[
QStandardItem(
genericToolIcon,
translate(
"SearchBar", "Please wait, loading results from cache…"
),
translate("SearchBar", "Please wait, loading results from cache…"),
),
QStandardItem("0"),
QStandardItem("-1"),
@ -275,17 +290,11 @@ class SearchBox(QLineEdit):
@staticmethod
def proxyListPageDown(self):
self.movementKey(
lambda current, nbRows: min(
current + max(1, self.maxVisibleRows / 2), nbRows - 1
)
)
self.movementKey(lambda current, nbRows: min(current + max(1, self.maxVisibleRows / 2), nbRows - 1))
@staticmethod
def proxyListPageUp(self):
self.movementKey(
lambda current, nbRows: max(current - max(1, self.maxVisibleRows / 2), 0)
)
self.movementKey(lambda current, nbRows: max(current - max(1, self.maxVisibleRows / 2), 0))
@staticmethod
def proxyListEnd(self):
@ -422,9 +431,7 @@ class SearchBox(QLineEdit):
def getScreenPosition(widget):
geo = widget.geometry()
parent = widget.parent()
parentPos = (
getScreenPosition(parent) if parent is not None else QPoint(0, 0)
)
parentPos = getScreenPosition(parent) if parent is not None else QPoint(0, 0)
return QPoint(geo.x() + parentPos.x(), geo.y() + parentPos.y())
pos = getScreenPosition(self)

239
StyleMapping_SearchBar.py Normal file
View File

@ -0,0 +1,239 @@
# *************************************************************************
# * *
# * Copyright (c) 2019-2024 Paul Ebbers *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 3 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# *************************************************************************
import FreeCAD as App
import FreeCADGui as Gui
import os
from PySide.QtGui import QIcon, QPixmap, QAction
from PySide.QtWidgets import (
QListWidgetItem,
QTableWidgetItem,
QListWidget,
QTableWidget,
QToolBar,
QToolButton,
QComboBox,
QPushButton,
QMenu,
QWidget,
QMainWindow,
)
from PySide.QtCore import Qt, SIGNAL, Signal, QObject, QThread
import sys
import json
from datetime import datetime
import shutil
import Standard_Functions_RIbbon as StandardFunctions
import Parameters_Ribbon
import webbrowser
import time
# Get the resources
pathIcons = Parameters_Ribbon.ICON_LOCATION
pathStylSheets = Parameters_Ribbon.STYLESHEET_LOCATION
pathUI = Parameters_Ribbon.UI_LOCATION
pathBackup = Parameters_Ribbon.BACKUP_LOCATION
sys.path.append(pathIcons)
sys.path.append(pathStylSheets)
sys.path.append(pathUI)
sys.path.append(pathBackup)
def ReturnStyleItem(ControlName, ShowCustomIcon=False, IgnoreOverlay=False):
"""
Enter one of the names below:
ControlName (string):
"Background_Color" returns string,
"Border_Color" returns string,
"FontColor" returns string,
"FontColor" returns string,
"""
# define a result holder and a dict for the StyleMapping file
result = "none"
# Get the current stylesheet for FreeCAD
FreeCAD_preferences = App.ParamGet("User parameter:BaseApp/Preferences/MainWindow")
currentStyleSheet = FreeCAD_preferences.GetString("StyleSheet")
IsInList = False
for key, value in StyleMapping_default["Stylesheets"].items():
if key == currentStyleSheet:
IsInList = True
break
if IsInList is False:
currentStyleSheet = "none"
try:
result = StyleMapping_default["Stylesheets"][currentStyleSheet][ControlName]
if result == "" or result is None:
result = StyleMapping_default["Stylesheets"][""][ControlName]
return result
except Exception as e:
print(e)
return None
def ReturnColor(ColorType="Background_Color"):
mw: QMainWindow = Gui.getMainWindow()
palette = mw.style().standardPalette()
# Get the color
Color = palette.base().color().toTuple() # RGBA tupple
if ColorType == "Border_Color":
Color = palette.buttonText().color().toTuple()
if ColorType == "Background_Color_Hover":
Color = palette.highlight().color().toTuple()
HexColor = StandardFunctions.ColorConvertor(Color, Color[3] / 255, True, False)
return HexColor
def ReturnFontColor():
fontColor = "#000000"
IsDarkTheme = DarkMode()
if IsDarkTheme is True:
fontColor = "#ffffff"
return fontColor
def DarkMode():
import xml.etree.ElementTree as ET
import os
# Define the standard result
IsDarkTheme = False
# Get the current stylesheet for FreeCAD
FreeCAD_preferences = App.ParamGet("User parameter:BaseApp/Preferences/MainWindow")
currentStyleSheet = FreeCAD_preferences.GetString("StyleSheet")
path = os.path.dirname(__file__)
# Get the folder with add-ons
for i in range(2):
# Starting point
path = os.path.dirname(path)
# Go through the sub-folders
for root, dirs, files in os.walk(path):
for name in dirs:
# if the current stylesheet matches a sub directory, try to geth the pacakgexml
if currentStyleSheet.replace(".qss", "").lower() in name.lower():
try:
packageXML = os.path.join(path, name, "package.xml")
# Get the tree and root of the xml file
tree = ET.parse(packageXML)
treeRoot = tree.getroot()
# Get all the tag elements
elements = []
namespaces = {"i": "https://wiki.freecad.org/Package_Metadata"}
elements = treeRoot.findall(".//i:content/i:preferencepack/i:tag", namespaces)
# go throug all tags. If 'dark' in the element text, this is a dark theme
for element in elements:
if "dark" in element.text.lower():
IsDarkTheme = True
break
except Exception:
continue
return IsDarkTheme
StyleMapping_default = {
"Stylesheets": {
"": {
"Background_Color": "#f0f0f0",
"Background_Color_Hover": "#ced4da",
"Border_Color": "#646464",
"FontColor": ReturnFontColor(),
},
"none": {
"Background_Color": "none",
"Background_Color_Hover": "#48a0f8",
"Border_Color": ReturnColor("Border_Color"),
"FontColor": ReturnFontColor(),
},
"FreeCAD Dark.qss": {
"Background_Color": "#333333",
"Background_Color_Hover": "#48a0f8",
"Border_Color": "#ffffff",
"FontColor": "#ffffff",
},
"FreeCAD Light.qss": {
"Background_Color": "#f0f0f0",
"Background_Color_Hover": "#48a0f8",
"Border_Color": "#646464",
"FontColor": "#000000",
},
"OpenLight.qss": {
"Background_Color": "#dee2e6",
"Background_Color_Hover": "#a5d8ff",
"Border_Color": "#1c7ed6",
"FontColor": "#000000",
},
"OpenDark.qss": {
"Background_Color": "#212529",
"Background_Color_Hover": "#1f364d",
"Border_Color": "#264b69",
"FontColor": "#ffffff",
},
"Behave-dark.qss": {
"Background_Color": "#232932",
"Background_Color_Hover": "#557bb6",
"Border_Color": "#3a7400",
"FontColor": ReturnFontColor(),
},
"ProDark.qss": {
"Background_Color": "#333333",
"Background_Color_Hover": "#557bb6",
"Border_Color": "#adc5ed",
"FontColor": ReturnFontColor(),
},
"Darker.qss": {
"Background_Color": "#444444",
"Background_Color_Hover": "#4aa5ff",
"Border_Color": "#696968",
"FontColor": ReturnFontColor(),
},
"Light-modern.qss": {
"Background_Color": "#f0f0f0",
"Background_Color_Hover": "#4aa5ff",
"Border_Color": "#646464",
"FontColor": ReturnFontColor(),
},
"Dark-modern.qss": {
"Background_Color": "#2b2b2b",
"Background_Color_Hover": "#4aa5ff",
"Border_Color": "#ffffff",
"FontColor": ReturnFontColor(),
},
"Dark-contrast.qss": {
"Background_Color": "#444444",
"Background_Color_Hover": "#4aa5ff",
"Border_Color": "#787878",
"FontColor": ReturnFontColor(),
},
}
}

View File

@ -5,7 +5,7 @@
<description>Adds a search bar widget for tools, document objects, and preferences</description>
<version>1.3.3</version>
<version>1.4.0</version>
<date>2022-06-01</date>

View File

@ -114,8 +114,10 @@ help() {
# Main function ------------------------------------------------------------------------------------
# LUPDATE="C:/Program Files/FreeCAD 1.0/bin/Lib/site-packages/PySide6/lupdate" # from Qt6
LUPDATE=/usr/lib/qt6/bin/lupdate # from Qt6
# LUPDATE=lupdate # from Qt5
# LRELEASE="C:/Program Files/FreeCAD 1.0/bin/Lib/site-packages/PySide6/lrelease" # from Qt6
LRELEASE=/usr/lib/qt6/bin/lrelease # from Qt6
# LRELEASE=lrelease # from Qt5
WB="SearchBar"