Maya window focus and QT

Hello!

I’m working on maya tool. It’s window(QDialog) has 2 states: edit mode and locked. When in edit mode this window can be activated and accept focus, it also overrides maya hotkeys with QActions. In “locked mode” window can only accept mouse events and should not be focused or deactivate maya window.

The only approach which is working for me is to toggle WindowDoesNotAcceptFocus flag True and False by pressing the button. The problem is that changing window flag is calling hide event and window disappears. It can be then shown with show() , but you can notice this “blink” which is very annoing. I want to solve this issue in a clean way without any blinking)

I thought that I could fix it without using window flags and I tried digging into focus policies, blocking focus or activation events with custom event filter, but none of these worked for me. It looks like focus policies don’t work for qt windows in maya. Even when I use window.setFocusPolicy(Qt.NoFocus), maya window still loses focus when I click on tool window.

Any thoughts how can I achieve keep maya window active while clicking on my tool’s window without using window flags?

A bit of a shot in the dark, but would setting the window to be always on top and then setting WindowDoesNotAcceptFocus work? I realize that you’re saying the does not accept focus flag seems to be minimizing the window, but I wonder if the the always on top flag would override the minimize.

Hi, ChemiKhazi!

This window has AlwaysOnTop flag and still window disappears while setting WindowDoesNotAcceptFocus flag True or False. Reason for that (which I’ve found on some QT forum) is setWindowFlag function also calls setParent() which causes window to be hidden. There are also WindowHide and HideToParent events triggered when changing flags. That’s why I’m trying to find method without using window flags.

And I am pretty confused that window.setFocusPolicy(Qt.NoFocus) does not work as expected.

It’s hard to tell exactly what might be happening without seeing code, but some issues I often see are:

  1. Setting the tool as “WindowStaysOnTop” but never actually parenting it to the Maya MainWindow when it’s created.
  2. Setting window flags like this…
    dialog.setWindowFlags(Qt.WindowDoesNotAcceptFocus)
    These are bit flags, so the above accidentally clears any other flags that are set. Do this instead…
    dialog.setWindowFlags(dialog.windowFlags() | Qt.WindowDoesNotAcceptFocus)

Hello, JoDaRober!

That’s not a code issue, it’s how QT applies flags to the window.

I’ve tried both dialog.setWindowFlags(dialog.flags() | Qt.WindowDoesNotAcceptFocus) and dialog.setWindowFlag(Qt.WindowDoesNotAcceptFocus, True), the result is the same. Window is not visible after applying the flags. I have to call dialog.show() to make it visible again.

Here is a video of behavior I want to avoid (slowed down):
qt

So by “issue”, what I meant was that often when people ask for help and share their code, they have accidentally set window flags incorrectly.

I read through your question again to make sure I understood. Unfortunately I don’t think there’s anything you can do about this. The QApplication handles making focus changes and while you can setFocusPolicy(Qt.NoFocus) on individual widgets, the focus of the window itself is probably happening in the low-level platform window management code. Window flags are the only way to change that behavior.

And you were right about setWindowFlags() calling setParent(). This is from the Qt docs

So it appears that the only option is to do this, and live with the flicker…

def toggle_lock(window):
    window.setWindowFlags(window.windowFlags() ^ Qt.WindowDoesNotAcceptFocus)
    window.show()

So it appears that the only option is to do this, and live with the flicker…

Yeah I guess so) Thanks for your help and explanation about focus policy. I was hoping it affects windows just like any other widgets.

It seems you’re facing a tricky issue with focus management and event handling in Maya, particularly when trying to prevent your tool window from deactivating Maya while still allowing mouse events in “locked mode.” Here’s a potential solution that might work without using window flags and without causing the annoying “blink” effect:

Suggested Approach:

Instead of relying on the WindowDoesNotAcceptFocus flag to control focus, you can make use of the event filter approach with more precise control over the window’s focus behavior. This solution involves intercepting specific events and selectively ignoring or blocking them depending on the mode of your window (edit or locked).

Steps:

  1. Event Filter for Focus Events: Implement an event filter to intercept the QEvent.FocusIn and QEvent.FocusOut events. This will allow you to block or modify focus behavior based on the mode (edit or locked).
  2. Prevent Focus In/Out in Locked Mode: In locked mode, you can intercept the focus event to prevent the window from taking focus while still allowing mouse events.
  3. Handle Mouse Events in Locked Mode: Even when the window doesn’t take focus, you can still handle mouse events (like clicks) by overriding the mousePressEvent or other mouse event handlers in your window.

I did throw together a quick test to try that method and it didn’t work for me. The focus events fire when the focus state changes, but the events themselves don’t seem to be what actually triggers the state change. They are in response to it.

IDK, my test was outside of maya so it may not have been accurate to what would happen in maya, if they override any of Qt’s focus behavior. I’d be happy to find out that I’m wrong.

Also… is that an AI generated response? I kind of reads like one.

Hello, carlos!

I’ve already tried using event filter. Unfortunately it seems like you can’t block focus events for window. Only thing that worked for me is to handle focus change event and hardfocus maya window after, which looks even worse than my initial problem, because it makes maya window header to blink every time I click on my tool’s window.

you need to extend the showEvent method
def showEvent(self, e):
super(TableExampleDialog, self).showEvent(e)
self.refresh_table()

“”"
from PySide2 import QtCore
from PySide2 import QtWidgets
from shiboken2 import wrapInstance

import maya.OpenMaya as om
import maya.OpenMayaUI as omui
import maya.cmds as cmds

def maya_main_window():
“”"
Return the Maya main window widget as a Python object
“”"
main_window_ptr = omui.MQtUtil.mainWindow()
return wrapInstance(int(main_window_ptr), QtWidgets.QWidget)

class TableExampleDialog(QtWidgets.QDialog):

ATTR_ROLE = QtCore.Qt.UserRole
VALUE_ROLE = QtCore.Qt.UserRole + 1

def __init__(self, parent=maya_main_window()):
    super(TableExampleDialog, self).__init__(parent)

    self.setWindowTitle("Table Example")
    self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowContextHelpButtonHint)
    self.setMinimumWidth(500)

    self.create_widgets()
    self.create_layout()
    self.create_connections()

def create_widgets(self):
    self.table_wdg = QtWidgets.QTableWidget()
    self.table_wdg.setColumnCount(5)
    self.table_wdg.setColumnWidth(0, 22)
    self.table_wdg.setColumnWidth(2, 70)
    self.table_wdg.setColumnWidth(3, 70)
    self.table_wdg.setColumnWidth(4, 70)
    self.table_wdg.setHorizontalHeaderLabels(["", "Name", "TransX", "TransY", "TransZ"])
    header_view = self.table_wdg.horizontalHeader()
    header_view.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch)

    self.refresh_btn = QtWidgets.QPushButton("Refresh")
    self.close_btn = QtWidgets.QPushButton("Close")

def create_layout(self):
    button_layout = QtWidgets.QHBoxLayout()
    button_layout.setSpacing(2)
    button_layout.addStretch()
    button_layout.addWidget(self.refresh_btn)
    button_layout.addWidget(self.close_btn)

    main_layout = QtWidgets.QVBoxLayout(self)
    main_layout.setContentsMargins(2, 2, 2, 2)
    main_layout.setSpacing(2)
    main_layout.addWidget(self.table_wdg)
    main_layout.addStretch()
    main_layout.addLayout(button_layout)

def create_connections(self):
    self.set_cell_changed_connection_enabled(True)

    self.refresh_btn.clicked.connect(self.refresh_table)
    self.close_btn.clicked.connect(self.close)

def set_cell_changed_connection_enabled(self, enabled):
    if enabled:
        self.table_wdg.cellChanged.connect(self.on_cell_changed)
    else:
        self.table_wdg.cellChanged.disconnect(self.on_cell_changed)

def keyPressEvent(self, e):
    super(TableExampleDialog, self).keyPressEvent(e)
    e.accept()

def showEvent(self, e):
    super(TableExampleDialog, self).showEvent(e)
    self.refresh_table()

def refresh_table(self):
    self.set_cell_changed_connection_enabled(False)

    self.table_wdg.setRowCount(0)

    meshes = cmds.ls(type="mesh")
    for i in range(len(meshes)):
        transform_name = cmds.listRelatives(meshes[i], parent=True)[0]
        translation = cmds.getAttr("{0}.translate".format(transform_name))[0]
        visible = cmds.getAttr("{0}.visibility".format(transform_name))

        self.table_wdg.insertRow(i)
        self.insert_item(i, 0, "", "visibility", visible, True)
        self.insert_item(i, 1, transform_name, None, transform_name, False)
        self.insert_item(i, 2, self.float_to_string(translation[0]), "tx", translation[0], False)
        self.insert_item(i, 3, self.float_to_string(translation[1]), "ty", translation[1], False)
        self.insert_item(i, 4, self.float_to_string(translation[2]), "tz", translation[2], False)

    self.set_cell_changed_connection_enabled(True)

def insert_item(self, row, column, text, attr, value, is_boolean):
    item = QtWidgets.QTableWidgetItem(text)
    self.set_item_attr(item, attr)
    self.set_item_value(item, value)
    if is_boolean:
        item.setFlags(QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled)
        self.set_item_checked(item, value)
    self.table_wdg.setItem(row, column, item)

def on_cell_changed(self, row, column):
    self.set_cell_changed_connection_enabled(False)

    item = self.table_wdg.item(row, column)
    if column == 1:
        self.rename(item)
    else:
        is_boolean = column == 0
        self.update_attr(self.get_full_attr_name(row, item), item, is_boolean)

    self.set_cell_changed_connection_enabled(True)

def rename(self, item):
    old_name = self.get_item_value(item)
    new_name = self.get_item_text(item)
    if old_name != new_name:
        actual_new_name = cmds.rename(old_name, new_name)
        if actual_new_name != new_name:
            self.set_item_text(item, actual_new_name)

        self.set_item_value(item, actual_new_name)

def update_attr(self, attr_name, item, is_boolean):
    if is_boolean:
        value = self.is_item_checked(item)
        self.set_item_text(item, "")
    else:
        text = self.get_item_text(item)
        try:
            value = float(text)
        except ValueError:
            self.revert_original_value(item, False)
            return

    try:
        cmds.setAttr(attr_name, value)
    except:
        original_value = self.get_item_value(item)
        if is_boolean:
            self.set_item_checked(item, original_value)
        else:
            self.revert_original_value(item, False)

        return

    new_value = cmds.getAttr(attr_name)
    if is_boolean:
        self.set_item_checked(item, new_value)
    else:
        self.set_item_text(item, self.float_to_string(new_value))
    self.set_item_value(item, new_value)

def set_item_text(self, item, text):
    item.setText(text)

def get_item_text(self, item):
    return item.text()

def set_item_checked(self, item, checked):
    if checked:
        item.setCheckState(QtCore.Qt.Checked)
    else:
        item.setCheckState(QtCore.Qt.Unchecked)

def is_item_checked(self, item):
    return item.checkState() == QtCore.Qt.Checked

def set_item_attr(self, item, attr):
    item.setData(self.ATTR_ROLE, attr)

def get_item_attr(self, item):
    return item.data(self.ATTR_ROLE)

def set_item_value(self, item, value):
    item.setData(self.VALUE_ROLE, value)

def get_item_value(self, item):
    return item.data(self.VALUE_ROLE)

def get_full_attr_name(self, row, item):
    node_name = self.table_wdg.item(row, 1).data(self.VALUE_ROLE)
    attr_name = item.data(self.ATTR_ROLE)
    return "{0}.{1}".format(node_name, attr_name)

def float_to_string(self, value):
    return "{0:.4f}".format(value)

def revert_original_value(self, item, is_boolean):
    original_value = self.get_item_value(item)
    if is_boolean:
        self.set_item_checked(item, original_value)
    else:
        self.set_item_text(item, self.float_to_string(original_value))

if name == “main”:

try:
    table_example_dialog.close() # pylint: disable=E0601
    table_example_dialog.deleteLater()
except:
    pass

table_example_dialog = TableExampleDialog()
table_example_dialog.show()

“”"

How is this answer related to the question that was asked?

wow, a joshua… if you cant see the answer too bad your a fucking asshole