Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion PyQtInspect/_pqi_bundle/pqi_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
ID_TO_MEANING, CMD_EXIT, CMD_WIDGET_INFO, CMD_ENABLE_INSPECT,
CMD_DISABLE_INSPECT, CMD_INSPECT_FINISHED, CMD_EXEC_CODE, CMD_EXEC_CODE_ERROR, CMD_EXEC_CODE_RESULT,
CMD_SET_WIDGET_HIGHLIGHT, CMD_SELECT_WIDGET, CMD_REQ_WIDGET_INFO, CMD_REQ_CHILDREN_INFO, CMD_CHILDREN_INFO,
CMD_REQ_CONTROL_TREE, CMD_CONTROL_TREE, CMD_REQ_WIDGET_PROPS, CMD_WIDGET_PROPS,
CMD_REQ_CONTROL_TREE, CMD_CONTROL_TREE, CMD_REQ_WIDGET_PROPS, CMD_WIDGET_PROPS, CMD_SETTINGS_CHANGED,
# Keys
TreeViewResultKeys
)
Expand Down Expand Up @@ -229,6 +229,9 @@ def process_net_command(self, global_dbg, cmd_id, seq, text):
elif cmd_id == CMD_REQ_WIDGET_PROPS:
widget_id = int(unquote(text))
global_dbg.notify_widget_props(widget_id)
elif cmd_id == CMD_SETTINGS_CHANGED:
settings = json.loads(unquote(text))
global_dbg.on_settings_changed(settings)



Expand Down Expand Up @@ -533,6 +536,9 @@ def make_req_widget_props_message(self, widget_id: int):
def make_widget_props_message(self, widget_props: typing.List[typing.Dict]):
return NetCommand(CMD_WIDGET_PROPS, 0, self._dump_json(widget_props))

def make_settings_changed_message(self, settings: dict):
return NetCommand(CMD_SETTINGS_CHANGED, 0, self._dump_json(settings))

def make_exit_message(self):
return NetCommand(CMD_EXIT, 0, '')

Expand Down
3 changes: 3 additions & 0 deletions PyQtInspect/_pqi_bundle/pqi_comm_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
# === WIDGET PROPS ===
CMD_REQ_WIDGET_PROPS = 1016
CMD_WIDGET_PROPS = 1017
# === SETTINGS SYNC ===
CMD_SETTINGS_CHANGED = 1018

ID_TO_MEANING = {
'129': 'CMD_EXIT',
Expand All @@ -54,6 +56,7 @@
'1015': 'CMD_CONTROL_TREE',
'1016': 'CMD_REQ_WIDGET_PROPS',
'1017': 'CMD_WIDGET_PROPS',
'1018': 'CMD_SETTINGS_CHANGED',
}

# === Tree Views ===
Expand Down
10 changes: 9 additions & 1 deletion PyQtInspect/pqi.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ def __init__(self, set_as_global=True):

self.inspect_enabled = False
self._inspect_extra_data = {}
self._highlight_color = DEFAULT_HIGHLIGHT_COLOR
self._selected_widget = None

# Mapping from QWidget object's ID to QWidget object
Expand Down Expand Up @@ -524,6 +525,9 @@ def enable_inspect(self, extra_data: OptionalDict = None):
self.inspect_enabled = True
self._inspect_extra_data = extra_data

if 'highlight_color' in extra_data:
self._highlight_color = extra_data['highlight_color']

def disable_inspect(self):
self.inspect_enabled = False
self._inspect_extra_data = {}
Expand All @@ -534,7 +538,11 @@ def mock_left_button_down(self) -> bool:

@property
def highlight_color(self) -> str:
return self._inspect_extra_data.get('highlight_color', DEFAULT_HIGHLIGHT_COLOR)
return self._highlight_color

def on_settings_changed(self, settings: dict):
if 'highlight_color' in settings:
self._highlight_color = settings['highlight_color']
Comment on lines +543 to +546

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Log client-side application of synced settings

on_settings_changed mutates runtime highlight behavior but does so silently, which leaves no evidence that a received CMD_SETTINGS_CHANGED was actually applied on the client. Because this is the endpoint of the new synchronization feature, the lack of logging on this branch makes it difficult to distinguish transport issues from apply-time issues when users report mismatched overlay colors.

Useful? React with 👍 / 👎.


def notify_inspect_finished(self, widget):
self.select_widget(widget)
Expand Down
73 changes: 73 additions & 0 deletions PyQtInspect/pqi_gui/windows/settings_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,24 @@ def __init__(self, parent):

self._mainLayout.addWidget(self._colorWidget)

# Preview area: hover to see the overlay effect
self._previewLabel = QtWidgets.QLabel("Preview:", self)
self._previewLabel.setFixedWidth(100)

self._previewArea = _HighlightPreviewWidget(self)
self._previewArea.setFixedHeight(80)

self._previewWidget = QtWidgets.QWidget(self)
self._previewLayout = QtWidgets.QHBoxLayout(self._previewWidget)
self._previewLayout.setSpacing(10)
self._previewLayout.addWidget(self._previewLabel, 0, QtCore.Qt.AlignTop)
self._previewLayout.addWidget(self._previewArea)

self._mainLayout.addWidget(self._previewWidget)

self._color = self._DEFAULT_COLOR
self._updateButtonPreview()
self._updateOverlayPreview()

@property
def _qcolor(self) -> QtGui.QColor:
Expand All @@ -234,6 +250,9 @@ def _updateButtonPreview(self):
border: 1px solid #888;
}}""")

def _updateOverlayPreview(self):
self._previewArea.setOverlayColor(self._qcolor)

def _selectColor(self):
initial = self._qcolor
color = QtWidgets.QColorDialog.getColor(
Expand All @@ -243,6 +262,7 @@ def _selectColor(self):
if color.isValid():
self._color = f"{color.red()},{color.green()},{color.blue()},{color.alpha()}"
self._updateButtonPreview()
self._updateOverlayPreview()

def getColor(self) -> str:
return self._color
Expand All @@ -259,9 +279,61 @@ def setColor(self, colorStr: str):
pqi_log.warning(f"Invalid color string: {colorStr}. Resetting to default. Error: {e}")
self._color = self._DEFAULT_COLOR
self._updateButtonPreview()
self._updateOverlayPreview()


class _HighlightPreviewWidget(QtWidgets.QWidget):
""" A preview widget that shows a colored overlay when the mouse hovers over it. """

def __init__(self, parent=None):
super().__init__(parent)
self._highlightColor = QtGui.QColor(255, 0, 0, 51)

self.setStyleSheet("_HighlightPreviewWidget { border: 1px solid #aaa; }")

Copilot AI Apr 8, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_HighlightPreviewWidget.setStyleSheet("_HighlightPreviewWidget { ... }") is unlikely to apply as intended because the selector may not match when the stylesheet is set on the widget itself (and Python subclass names/selectors can be unreliable). If the goal is just to draw a border around the preview, set the border style directly (no selector) or set a stable objectName and target it via #objectName to ensure the border consistently appears.

Suggested change
self.setStyleSheet("_HighlightPreviewWidget { border: 1px solid #aaa; }")
self.setStyleSheet("border: 1px solid #aaa;")

Copilot uses AI. Check for mistakes.
self.setMouseTracking(True)

contentLayout = QtWidgets.QHBoxLayout(self)
contentLayout.setContentsMargins(20, 20, 20, 20)

self._sampleLabel = QtWidgets.QLabel("Hover here to preview the highlight overlay effect", self)
self._sampleLabel.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
contentLayout.addWidget(self._sampleLabel)

# Highlight overlay (hidden by default, shown on hover)
self._highlightOverlay = QtWidgets.QWidget(self)
self._highlightOverlay.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
self._highlightOverlay.hide()
self._updateHighlightStylesheet()

def setOverlayColor(self, color: QtGui.QColor):
self._highlightColor = color
self._updateHighlightStylesheet()

def _updateHighlightStylesheet(self):
c = self._highlightColor
self._highlightOverlay.setStyleSheet(
f"background: transparent; background-color: rgba({c.red()},{c.green()},{c.blue()},{c.alpha()});"
)

def enterEvent(self, event):
self._highlightOverlay.setGeometry(self.rect())
self._highlightOverlay.show()
self._highlightOverlay.raise_()
super().enterEvent(event)

def leaveEvent(self, event):
self._highlightOverlay.hide()
super().leaveEvent(event)

def resizeEvent(self, event):
if self._highlightOverlay.isVisible():
self._highlightOverlay.setGeometry(self.rect())
super().resizeEvent(event)


class SettingWindow(QtWidgets.QDialog):
sigSettingsSaved = QtCore.pyqtSignal()

def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Settings")
Expand Down Expand Up @@ -350,6 +422,7 @@ def saveSettings(self):
pqi_log.info(f"Settings saved: IDE Type={ideType}, IDE Path={idePath}, Parameters={ideParameters},"
f" Highlight Color={highlightColor}")

self.sigSettingsSaved.emit()
self.close()

def showEvent(self, ev):
Expand Down
3 changes: 3 additions & 0 deletions PyQtInspect/pqi_gui/workers/dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,5 +107,8 @@ def sendRequestControlTreeInfoEvent(self, extra: OptionalDict = None):
def sendRequestWidgetPropsEvent(self, widgetId: int):
self.writer.add_command(self.net_command_factory.make_req_widget_props_message(widgetId))

def sendSettingsChanged(self, settings: dict):
self.writer.add_command(self.net_command_factory.make_settings_changed_message(settings))

def notifyDelete(self):
self.close()
4 changes: 4 additions & 0 deletions PyQtInspect/pqi_gui/workers/pqy_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ def sendDisableInspect(self):
for dispatcher in self.dispatchers:
dispatcher.sendDisableInspect()

def sendSettingsChanged(self, settings: dict):
for dispatcher in self.dispatchers:
dispatcher.sendSettingsChanged(settings)

def sendExecCodeEvent(self, dispatcherId: int, code: str):
dispatcher = self.idToDispatcher.get(dispatcherId)
if dispatcher:
Expand Down
19 changes: 16 additions & 3 deletions PyQtInspect/pqi_server_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,10 +587,16 @@ def _selectWidget(self, widgetId: int):
# |
# * Close the server / Stop Serving -> Disable Inspect
# ----------------------------------------------------------------------------------------
def _buildClientSettings(self) -> dict:
""" Build a dict of settings that need to be synced to the client. """
return {
'highlight_color': SettingsController.instance().highlightColor,
}

def _buildInspectExtraData(self) -> dict:
return {
'mock_left_button_down': self._isMockLeftButtonDownAction.isChecked(),
'highlight_color': SettingsController.instance().highlightColor,
**self._buildClientSettings(),
}

def _beginInspect(self):
Expand Down Expand Up @@ -631,8 +637,14 @@ def _disableInspect(self):
def _openSettingWindow(self):
if self._settingWindow is None:
self._settingWindow = SettingWindow(self)
self._settingWindow.sigSettingsSaved.connect(self._onSettingsSaved)
self._settingWindow.show()

def _onSettingsSaved(self):
worker = self._getWorker()
if worker:
worker.sendSettingsChanged(self._buildClientSettings())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Log settings sync broadcasts to connected clients

The new real-time sync path sends CMD_SETTINGS_CHANGED from _onSettingsSaved without any pqi_log entry, so when highlight-color updates appear stale there is no observable trace showing that the broadcast was attempted (or with what settings). For this feature, the save→broadcast step is a critical execution path, and missing logs here makes production diagnosis and regression triage significantly harder.

Useful? React with 👍 / 👎.


# endregion

# region -- code exec --
Expand Down Expand Up @@ -805,16 +817,17 @@ def _openLogDir(self):

def _clearLogs(self):
""" Clear the logs in the console and file. """
_title = "Clear Logs"
reply = QtWidgets.QMessageBox.question(
self,
self._getWindowTitle(),
_title,
"Are you sure you want to delete all logs?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No,
QtWidgets.QMessageBox.No
)
if reply == QtWidgets.QMessageBox.Yes:
pqi_log.clear_logs()
QtWidgets.QMessageBox.information(self, self._getWindowTitle(),
QtWidgets.QMessageBox.information(self, _title,
"All logs have been deleted.")
# endregion

Expand Down