[py or pyqt] Creating window under mouse position

Hello.

In Maya I need to create a window with buttons on it that will launch scripts. This window will be created with its topLeft at the mouse position when the button was pressed a la Luxology Modo. When the button is released the window will close. I would like this to function over both of my two monitors.

For example, I want to have a few of these “script launcher windows” that are mapped to F1/F2/F3/F4.

I have heard that this is not possible in mel or python alone to query mouse position without getting really deep in the api. I’ve heard whispers that its easy in PyQt. I’ve downloaded and installed python/PyQt for maya 2012 and followed the steps from Nathans website (front page)

###########################

At this link Nate talks about getting Mouse Hover. However, I dont know how to implement this or know if it works for what I want. I could of course skip the mouse position for the window but i find it sooo convenient in modo that i would be really disappointed if I couldn’t implement it in Maya

http://forums.cgsociety.org/showthread.php?t=933209

I know that there are many ways to launch a ui (I’m using designer since its the fastest/easiest for what Im doing).

Thanks.

Why not just use a custom marking menu?
or
this that can be implemented via mel

Of course this assumes that you are not then looking to contextually determine what is under the mouse pointer and then detemine the scripts to populate to the ui that gets displayed.

http://ewertb.soundlinker.com/H2O_MEL_Scripts/buildQuickSelectMenu.html

it ends up looking like this:

//===========================================================================
// buildQuickSelectMenu
//===========================================================================
/// based on buildQuickSelectMenu.mel v1.0 (15 August 2000)
///
/// MEL script for Maya
///
/// by Bryan Ewert
/// When mapped to a hotkey, builds a popup menu containing
/// all current Quick Select Sets.
/// The ‘Press’ assignment should be:
///
/// buildQuickSelectMenu;
///
/// The ‘Release’ assignment should be:
///
/// if( popupMenu -exists tempMM ) { deleteUI tempMM; }
///
/// The Popup Menu is activated with the Left Mouse Button.
///
global proc undoQuickSelectMenu()
{
// get rid of it
if( popupMenu -exists tempMM ) deleteUI tempMM;
}
global proc QuickSelectMenu()
{
//do things here to add to the menu
createMenu tempMM;
setParent -m …;
}

global proc buildQuickSelectMenu()
{
// Reuse the name tempMM for the name of the menu,
// to ensure that there’s only one of these at
// any one time.
if( popupMenu -exists tempMM ) deleteUI tempMM;

popupMenu -alt 0 -mm 0 -b 1 -aob 0 -p viewPanes -pmc "QuickSelectMenu" tempMM;

}

[QUOTE=rhexter;15404]why not just use a custom marking menu
that can be implemented via mel

http://ewertb.soundlinker.com/H2O_MEL_Scripts/buildQuickSelectMenu.html[/QUOTE]

Rhexter,

I really need to use buttons with pictures on them and have them spaced in a window with non uniform spacing…also i have a lot of scripts that i want per page (16+)

i think a custom designer window is my only option and i love the results ive gotten so far with it.

I think you are wanting context menus, check out this link http://diotavelli.net/PyQtWiki/Handling%20context%20menus
if you notice there is a contextMenuEvent that you can override. the event argument that is passed in has a .pos property on it and that is the mouse x and y on the widget itself, you can then map that to a global position and then the menu pops up where ever you click so long as the widget you click over has that event overridden with what you want it to do.

[QUOTE=mattanimation;15406]I think you are wanting context menus, check out this link http://diotavelli.net/PyQtWiki/Handling%20context%20menus
if you notice there is a contextMenuEvent that you can override. the event argument that is passed in has a .pos property on it and that is the mouse x and y on the widget itself, you can then map that to a global position and then the menu pops up where ever you click so long as the widget you click over has that event overridden with what you want it to do.[/QUOTE]

Matt,

Thanks for the link. However, I believe that I’ve found an easier way. I saw an earlier post by Nate showing how to get XY cursor position with PyQt in one line.


import sip
from PyQt4 import QtGui, QtCore

point = QtGui.QCursor.pos()
print point
[B]PyQt4.QtCore.QPoint(140, 26)[/B]

This gives me X,Y screen position.

Then, I printed the result of that and added it to a test Window and used a mel command to move the Window (TopLeftCorner) to the position. However, I found that these ints need to be reversed…move to Y/X. This will move the window to the spot immediately after it gets created.

window 
-wh 300 300 
-vis true
-t "MyCoolWindow" 
myWindow;

window -e -tlc 26 140 myWindow;

So, Im wondering now how I can store the result of Point as a list or array as P0 and P1 so that I can insert these automatically into -tlc as P1 P0 as I further develop the script.

QPoint has x() and y() functions which give you the values you want

pos = QtGui.QCursor.pos()

cmds.window('myWindow', e=True, tlc=[pos.y(), pos.x()])

Thanks a lot Nate. I feel like I’ve made good progress, learning QtDesigner and putting a background on the window. However, Im dealing with an issue that I believe I “lose focus of my popup window and thus cant close it with the release command”. This video shows it in action. I dont want to manually have to close my window as it would defeat the purpose of what Im doing and I also want to run my windows without titlebars (:nod:)

I’ve been researching how to fix this. I’ve heard PyQt programmers talking about setFocusPolicy and WindowStaysOnTopHint…however, I havent figured out how to get that to work. Any clues?

//youtu.be/fZ0GYaCI5M4

Heres my script

import maya.cmds as cmds
import sip
from PyQt4 import QtGui, QtCore

Launcher1 = cmds.loadUI (f='C:/Users/aleks/Desktop/PyQt proj/UI/ScriptLauncher1addBG.ui')


pos = QtGui.QCursor.pos()
cmds.showWindow(Launcher1)

cmds.window('LaunchyWin1', e=True, tlc=[pos.y(), pos.x()])

And the release command

cmds.deleteUI(Launcher1, window=True )

This thread is very interesting.
Thanks for posting your progress through this.

This would make for a great way to call a character GUI for animators, instead of having floating windows cluttering up the place, and having to navigate over to them.

A couple implementation suggestions, more as ideas than actual ways to do them:
1)Have the window be aware of the screen height. If it’s invoked when the mouse is near the bottom of the screen, it should open above the mouse instead of below to prevent it opening off screen

The next one depends on whether you can get the auto-release function you have working.
2)Have it include a time out function. If you don’t interact with it for a set amount of time, it deletes itself.

Good luck!

You might want to check if both references to Launcher1 variable points to same value.

But regardless of the actual cause of the issue, I’d suggest to look at launching the script via evalDeferred command. This way the script should run after the launcher window is closed.

Stop using cmds.loadUI :slight_smile:

If you create your window with the QtCore.Qt.Popup window flag, it will auto-close the window when it loses focus.

Add on QtCore.Qt.FramelessWindowHint to the flags to clear off the window border and you have a quick little hotbox style area to play with.

Edit* here’s an example of what I’m talking about:

import sip
from PyQt4 import QtGui, QtCore
import maya.OpenMayaUI as apiUI


def getMayaWindow():
	ptr = apiUI.MQtUtil.mainWindow()
	ptr = long(ptr) #Ensure type
	return sip.wrapinstance(long(ptr), QtCore.QObject)

class HotboxWidget(QtGui.QDialog):
	def __init__(self, parent=getMayaWindow()):
		super(HotboxWidget, self).__init__(parent)
		self.setWindowFlags(QtCore.Qt.Popup|QtCore.Qt.FramelessWindowHint)
		self.resize(600, 400)
	
	def mousePressEvent(self, event):
		self.close() #Close when clicked
	
	def showAtMouse(self):
		self.show()
		pos = QtGui.QCursor.pos()
		self.move(pos.x()-(self.width()/2), pos.y()-(self.height()/2))


win = HotboxWidget()
win.showAtMouse()

Edit Edit**
You can also paint a mask and set it with setMask(), if you want to be able to “see though” around your buttons and such to the main window like the hotbox… If you want that look anyways :slight_smile:

Thanks for those extra tips Nate! got anymore? :smiley:

Thanks guys.

Nate,
I’ve tried the code that you provided. While it did work for opening up a PyQt window, I have had trouble with getting the UI file open with UIC. Specifically, if I use this below method as opposed to the one on the first page, It doesnt recognize any commands from the buttons, whether written in python or in mel in Designer.

Also, if you add a background to your window it wont load the window and gives an error saying

Error: ImportError: file <string> line 48: No module named backgroundResources_rc

(backgrounds are added in Designer by right clicking on your top widget, changing stylesheet and adding a BG after you set a path/loaded images in the bottom right Resource Browser)

Additionally, I know that the logic behind getMayaWindow() is so the new PyQt window doesnt take over the Qt thread and crash Maya when you close it. However, Ive found that in your examples it doesnt have any effect - I can remove the module and the window will open/close just fine.

Having said that, I’ve run across ~2010 pyqt examples from AREA and find that they do hang/crash and even if I parent into the getMayaWindow that they DO crash maya every time on exit. This also happens when I was trying to capture keypress with pyGame.

This is script for a basic test UI located in Maya2012/usr/pref/ui with 2 buttons on it opened with UIC, one for polySphere and one to open the scriptEditor. The buttons dont work.

import maya.cmds as cmds
import sip
import os
from PyQt4 import QtGui, QtCore, uic
import maya.OpenMayaUI as mui

def getMayaWindow():
    ptr = mui.MQtUtil.mainWindow()
    ptr = long(ptr)
    return sip.wrapinstance(long(ptr), QtCore.QObject)
    
uiFilename = 'test1.ui'
    
uiFile = os.path.join(cmds.internalVar(upd=True), 'ui', 'test1.ui')


form_class, base_class = uic.loadUiType(uiFile)

class LauncherWindow(base_class, form_class):
    def __init__(self, parent=getMayaWindow()):
        super(base_class, self).__init__(parent)
        
        self.setupUi(self)
        self.setObjectName('myWindow')

def main():
    global myWindow
    myWindow = LauncherWindow()
    myWindow.show()
    
main()

Error: ImportError: file <string> line 48: No module named backgroundResources_rc

You need to convert your .qrc files to python modules, there’s an executable included with PyQt to do so.

(I usually use this function and loop over my qrc folder)


import os
import subprocess
from PyQt4 import QtGui

def compileQrcFile(path, target):
	'''Given a path to a qrc file, create the target python file'''
	rcc_exe = os.path.join( os.path.dirname(QtGui.__file__), 'bin', 'pyrcc4.exe')
	proc = subprocess.Popen([rcc_exe, path, '-o', target], shell=True)
	return proc.wait()

Additionally, I know that the logic behind getMayaWindow() is so the new PyQt window doesnt take over the Qt thread and crash Maya when you close it. However, Ive found that in your examples it doesnt have any effect - I can remove the module and the window will open/close just fine.

Actually getMayaWindow() just provides the main Maya window as a QMainWindow object for the new widget to use as it’s parent. The threading issues were only present with the <2011 versions where PyQt was running outside of Maya’s main UI event loop. Now that we have a QApplication instance running, our widgets can execute under that seamlessly (Yay!) without problems.
The window will open fine without it but you will notice that it can lose focus and drop BEHIND the main Maya window (Which is not the behavior of Maya’s own sub windows, nor desirable) Any widget created in Qt without a parent widget is considered to be a “Top Level” widget, and is maintained at the desktop level for focus.

Thanks for the explanation, I see now that the getMayaWindow is needed and I’ve re-added it. I’ve been able to add a Background (used a pixMap on a labelButton)but still have a problem with the buttons and just more and more problems with UIC. For one, the window would only occasionally move to cursor position, and secondly my release commands didnt work because when UIC is called the title of the window decides to not show up in the script editor to be able to even delete with the release command :curses::curses::curses:

I feel like abandoning the UIC because its a huge PITA. Im definetly very interested in UI design, but I’ve come to the conclusion that I have to do it within Blender so I can know whats really going on :scared:

And with commands from the buttons, I’ve watched GDC 2011 pyQt interface lecture (this is how I got bg working) and also cmiVFX Python for maya vol 2 which covered pyqt. I was unsuccessful in getting any kind of button response to happen after I had loaded it with uic. I tried the GDC method and the signal and slot (cmivfx) method but no luck. I also tried setting the command in designer…

In both videos they were able to get commands to work with UIC with a .ui

I then went back to what I had working and tried to have the window close if it existed by pressing F1 by using an if statement. However, this only works if the window is in focus, otherwise nothing even shows up in script editor........I tried two methods.



```
import maya.cmds as cmds
import sip
from PyQt4 import QtGui, QtCore


winLauncher1 = 'LaunchyWin1'
for i in cmds.lsUI(windows=True):
	if winLauncher1 in i:
		cmds.deleteUI(LaunchyWin1)

Launcher1 = cmds.loadUI (f='C:/Users/aleks/Desktop/PyQt proj/UI/ScriptLauncher1addBG.ui')

pos = QtGui.QCursor.pos()
cmds.showWindow(Launcher1)

cmds.window('LaunchyWin1', e=True, tlc=[pos.y(), pos.x()])

```


and 


```
if cmds.window('LaunchyWin1', q=True, exists=True):
	cmds.deleteUI(LaunchyWin1)

```




```
import maya.cmds as cmds
import maya.OpenMayaUI as apiUI
import sip
import os
from pymel.core import *
import pymel.core as pm
from PyQt4 import QtGui, QtCore, uic
   
   
#Path to Designer UI file
uiFilename = 'C:/GDCpy/gdcControls.ui'
form_class, base_class = uic.loadUiType(uiFilename)

pos = QtGui.QCursor.pos()
#makeSphereCommand = cmds.polySphere()

def getMayaWindow():
	ptr = apiUI.MQtUtil.mainWindow()
	ptr = long(ptr) #Ensure type
	return sip.wrapinstance(long(ptr), QtCore.QObject)


 
#********************************************************************
#LauncherWindow Class
#********************************************************************
 
class LauncherWindow(base_class, form_class):
    def __init__(self, parent=getMayaWindow()):
        super(base_class, self).__init__(parent)
        self.setupUi(self)
        self.setObjectName('MovetoThis')
        self.setDockNestingEnabled(True)
        
        
#    def connect_bottons(self):
#        self.btn_sphere = QtGui.QPushButton
#        QtCore.QObject.connect(self.btn_sphere, QtCore.SIGNAL('clicked()'), self.makeSphere)
        
#    def makeSphere(self):
#        cmds.polySphere()
        #makeSphereCommand

#********************************************************************
#Main
#********************************************************************
       
def main():
    global ui
    ui = LauncherWindow()
    #ui.background.setPixmap(QtGui.QPixmap('C:/GDCpy/Desert.jpg'))
    
    ui.background.setPixmap(QtGui.QPixmap('C:/Users/Public/Pictures/Sample Pictures/Jellyfish.jpg'))
    ui.show()
    cmds.window('MovetoThis', e=True, tlc=[pos.y(), pos.x()])

    
main()
```

For one, the window would only occasionally move to cursor position, and secondly my release commands didnt work because when UIC is called the title of the window decides to not show up in the script editor to be able to even delete with the release command

Make sure you don’t mix cmds and PyQt when it comes to editing controls, that’s why your window isn’t positioning consistently. Use self.move() on the window to move it into position.

Also don’t use cmds.deleteUI() to clean it up, since you have it in a global variable, just close the last one before opening a new one.

And with commands from the buttons, I’ve watched GDC 2011 pyQt interface lecture (this is how I got bg working) and also cmiVFX Python for maya vol 2 which covered pyqt. I was unsuccessful in getting any kind of button response to happen after I had loaded it with uic. I tried the GDC method and the signal and slot (cmivfx) method but no luck. I also tried setting the command in designer…

Your signal/slot you had commented out was done with the old-style method. That way of doing it is very error prone (Along with being hard to read), I switched it out for the new-style way.

I then went back to what I had working and tried to have the window close if it existed by pressing F1 by using an if statement. However, this only works if the window is in focus, otherwise nothing even shows up in script editor…I tried two methods.

Yeah, hotkeys are unreliable because they only activate for the currently active window. If you really need a global application hotkey and want to override the Maya hotkeys you can install an eventFilter on the QApplication, but I don’t recommend it.

import maya.cmds as cmds
import maya.OpenMayaUI as apiUI
import sip
import os
#Sip api versions
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)

from PyQt4 import QtGui, QtCore, uic

#Path to Designer UI file
uiFilename = 'C:/gdcControls.ui'
form_class, base_class = uic.loadUiType(uiFilename)

def getMayaWindow():
	ptr = apiUI.MQtUtil.mainWindow()
	ptr = long(ptr) #Ensure type
	return sip.wrapinstance(long(ptr), QtCore.QObject)

 
#********************************************************************
#LauncherWindow Class
#********************************************************************
class LauncherWindow(base_class, form_class):
    def __init__(self, parent=getMayaWindow()):
        super(LauncherWindow, self).__init__(parent)
        self.setupUi(self)
        self.setDockNestingEnabled(True)
        self.setWindowFlags( QtCore.Qt.FramelessWindowHint | QtCore.Qt.Popup )
        #New-style signal/slot connect
        #widget.signalName.connect ( callable )
        self.btn_sphere.clicked.connect(self.makeSphere)
    
    def makeSphere(self):
        cmds.sphere()
        #Close after doing action
        self.close()
        
    def showAtMouse(self):
        self.show()
        pos = QtGui.QCursor.pos()
        self.move(pos.x()-(self.width()/2), pos.y()-(self.height()/2))

def main():
    global ui
    try: #Close already open version if it exists
        ui.close()
    except: pass
    
    ui = LauncherWindow()
    ui.showAtMouse()

main()

Nathan-

First, thanks for sharing your extensive QT knowledge. I think it has really been a great help in getting a lot of us up and running!

I’m trying to run your code above, with the exception of inserting one of my own ui files, and I’m getting an error:

Error: AttributeError: file <maya console> line 32: ‘LauncherWindow’ object has no attribute ‘btn_sphere’

So I added a line to first create a push button and declare self.btn_sphere:
self.btn_sphere = QtGui.QPushButton

And then I get the error:

Error: AttributeError: file <maya console> line 33: ‘PyQt4.QtCore.pyqtSignal’ object has no attribute ‘connect’

Then I actually linked the button up to a button I had in my UI by inserting the button name:
self.btn_sphere = QtGui.QPushButton(‘myButtonName’)

And then my UI finally loads, but clicking on the button does nothing. Any ideas?

I assume this is happening:
you have a button in your designer file, but not named btn_sphere (Hence LauncherWindow has no attribute ‘btn_sphere’ error).
you are creating a button for btn_sphere, but not adding it to the layout.

when the ui loads, you are seeing the one from designer (Which is not connected) and clicking it. the one you created is connected fine, but not visible…

Just open designer and set the object name for your pushbutton to btn_sphere (or rename the variable to match the buttons object name) and then remove the code to create the button from your init function.

Sweet! That worked. Things are starting to make a bit more sense now.

Thanks!

If you really need a global application hotkey and want to override the Maya hotkeys you can install an eventFilter on the QApplication, but I don’t recommend it.

What is the reason for avoiding that behavior? Performance?

What about installing an eventFilter to the main MayaWindow? Is that also not recommended?

I was playing around with trying to get a nuke-style node creator for the hyperShade, and the only way I was able to install an eventFilter on the hyperShade was to have one on the main MayaWindow looking for a ChildPolished event.
While that is a superfluous tool, I wondered if I was using bad practices.

[QUOTE=capper;15711]What is the reason for avoiding that behavior? Performance?

What about installing an eventFilter to the main MayaWindow? Is that also not recommended?

I was playing around with trying to get a nuke-style node creator for the hyperShade, and the only way I was able to install an eventFilter on the hyperShade was to have one on the main MayaWindow looking for a ChildPolished event.
While that is a superfluous tool, I wondered if I was using bad practices.[/QUOTE]

Performance would be a fairly minor concern, the main thing is that your hotkey would override the Maya hotkeys which could be confusing (For an application level key filter) as the main Maya window would never receive those events.

Also you would have to be very careful to filter the matching release event for any press event you block, as intercepting one without the other is a big no-no.

I’m not saying never to do it, just be careful with it, and design around it if possible :slight_smile: We have a global application filter here that intercepts paste events into the script editor, so it’s definitely do-able. (Although Maya should just fix their stupid script editor to accept paste contents from IDE’s)