Am working with PyQt5, and I have a window app, I already have some functions and classes, and I implemented that when the user click Mouse Right-click it shows a context menu.
What I need is: I want to make a unit test for the code, so I want to test the right click which will open the context menu and after that I must test if the user click on it, it should do something like removing the points.
But my problem is I can't make an event that will click on that context menu and take action, because what I think is it is not in the widget so I can't make an event click on the point that the context menu are in, I tried it but it doesn't work.
So any ideas, how to make an event click on or pass or anything just made the action taken and run the code after context menu opened?
NOTE: I'm using QEvent for testing, Since am working with QWidget and QApllication PyQt5 python 3.7.
Here is the test event that opened the context menu:
point = QPointF(30, 30) #For example this will open the context menu at that point
e = QMouseEvent(QMouseEvent.MouseButtonPress, point, Qt.RightButton, Qt.NoButton, Qt.NoModifier) #Right Click
self.canvas.contextMenuEvent(e)
This is from canvas class:
def contextMenuEvent(self, ev):
"""
overrides the original contextMenuEvent function
:param ev: <QEvent>
:return: None
"""
self.current_tool.context_menu(ev)
And this is from the current tool function:
def context_menu(self, e):
menu = QMenu(self.canvas)
delete_whole_polygon_act = menu.addAction("Delete Area?")
action = menu.exec_(self.canvas.mapToGlobal(e.pos()))
if action == delete_whole_polygon_act:
self.delete_polygon(e.pos())
Related
So i am making a pygame program and for that i have written a GUI.py module. One of the classes is Button and one of the methods of that button is clicked, which basically checks if the button is being pressed. Initially i used pygame.mouse.get_pressed() to check for mouse press, but the problem i had with that is that it registered multiple presses within a single frame, which is not what i want.
def clicked(self):
mouse_pos = pygame.mouse.get_pos()# Gets the position of the mouse
mouse_pressed = pygame.mouse.get_pressed()# Checks if the mouse is being pressed
# checking if the mouse is already inside the button
if self.mouseover():
# mouse_pressed[0] returns true if the left mouse button is being pressed
if mouse_pressed[0]:
return True
return False
So i need to use events to check for mouse press. However, i am importing GUI.py to other modules, which are then imported to main.py. Becasue of this, i cannot import main.py to GUI.py. But main.py is where pygame.event.get() is being called in the main loop. I could call the method in other modules and pass in events as an argument, but i want to do that every single time i make a button.
Sorry if what i tried to explain is unclear, but here is what the question boils down to. Is there a way to make pygame.event.get() available to all the modules in my program independent of everything else?
The way i solved this in my pygame UI module was to store the previous frame click state, so if the mouse was clicked this frame and not last frame, its a click otherwise its being held down and nothing happens. (my module is a bit more complicated as it makes sure you clicked on it, then let go on it to count as a click)
if click and not previous_frame_click:
#clicked
if you are just using a function and not a class to do this, then maybe create a global variable
last_frame_click = False
def clicked(self):
global last_frame_click
mouse_pos = pygame.mouse.get_pos()# Gets the position of the mouse
mouse_pressed = pygame.mouse.get_pressed()# Checks if the mouse is being pressed
# checking if the mouse is already inside the button
if self.mouseover():
# mouse_pressed[0] returns true if the left mouse button is being pressed
if mouse_pressed[0] and not last_frame_click[0]:
last_frame_click = mouse_pressed
return True
last_frame_click = mouse_pressed
return False
EDIT: just noticed you said one of the classes, dont worry about above code, just use self.last_frame_click
When I had a similar issue I created an event manager module for it. Anything that wanted to be notified about events would register an event handler with the event manager, providing the events the handler was interested in and a event handler callback.
The event manager was handed the events from the event loop and it would check the events against the registered events. If there was a match then the event manager would call the associated event handler callback passing along the event as an argument to the callback.
It sounds more complicated than it really is.
I'm trying to bind my mouse double click to a function which for now just prints the current selection in a Tkinter list box. To be clear the function should only print when the user double clicks on one of the items of a Tkinter list box. What event binding should I use?
You can bind to <Double-Button-1>:
widget.bind('<Double-Button-1>', handler)
There is also <Button-1> for normal mouse clicks and <Triple-Button-1> for a triple mouse click.
For more information on bindings in Tkinter, see Events and Bindings.
You have to realize that there is a hierarchy to all widgets, and this means that for each widget you click, multiple bindings are possible. If you don't override the default action, each hierarchy's default handler gets called, starting at the lowest level (such as your Listbox) and going all the way up to the Tk() or Toplevel() widget. For you, since you want to print only when a listbox item is clicked, you can bind to the listbox widget, as follows:
listboxWidget.bind('<Double-Button-1>', listboxWidget_leftclick_handler)
Then, when you enter the def listboxWidget_leftclick_handler(event) function, you don't have to check the event.widget value to see if it's the name of your Listbox widget. But you could also check at a higher level (bind a handler to a higher-level widget) and check event.widget to see which widget was clicked.
Also note that the only way to prevent the entire hierarchy of event handlers from triggering is by using a return 'break' from your custom handler, but you usually only need to do this if later handlers corrupt what your custom handler has done.
Additional info about default handlers
The other part which I left out is that there is also a "default" handler for most events. If you bind your own handler, once it's finished, if you don't return 'break', the default handler will be called next.
For example, say you want to make your own Entry box into a password entry. By default, when you type alphanumeric chars when the Entry has focus (which means it's getting input from the keyboard), the chars will appear in the Entry. You can bind:
myEntry.bind('<KeyPress>', passworder)
where passworder is your custom handler which grabs the event holding your inputted char and then outputs an asterisk into the Entry instead. But, if you don't use a return "break" at the end of your handler, the Entry widget is still going to see that char that you didn't want shown, because once your handler is done inserting the asterisk, the default handler will simply insert the typed char (like it would normally). But, if you do the return 'break', the default handler won't get called, and the typed char(s) won't appear in the Entry.
As an add-on. In order to distinguish action between a single click and a double click, delay the call to mouse action for a brief period to allow for the double click flag to be set. See below example:
from tkinter import *
def mouse_click(event):
''' delay mouse action to allow for double click to occur
'''
aw.after(300, mouse_action, event)
def double_click(event):
''' set the double click status flag
'''
global double_click_flag
double_click_flag = True
def mouse_action(event):
global double_click_flag
if double_click_flag:
print('double mouse click event')
double_click_flag = False
else:
print('single mouse click event')
root = Tk()
aw = Canvas(root, width=200, height=100, bg='grey')
aw.place(x=0, y=0)
double_click_flag = False
aw.bind('<Button-1>', mouse_click) # bind left mouse click
aw.bind('<Double-1>', double_click) # bind double left clicks
aw.mainloop()
I am creating a gtk (pygtk) based GUI file browser on python using IconView. It has two signals - selection-changed which is triggered when an image is selected by single click and item-activated which is triggered when a double-click is performed . I need both the signals in my program , selection-changed to select folders to cut/copy and item-activated to navigate child folders and files.
However, double-clicking generates two selection-changed signals and one item-activated signal. I only need the item-activated signal to be generated.
Hence the question.
The second single-click and the double-click event should arrive simultaniously.
You can use glib.timeout_add(interval, callback, ...) to execute all the single-clicks after a milli second (or increase it).
If within some time a double-click occurred then you do not execute the single-click event.
Implementation hints: use a variable so save when the last double-click occurred and a variable for the last click using time.time().
If the double-click and single-click are very close, do not execute the singe-click code.
Due to the use of the timeout, the single-click always executes after the double-click.
Why not keep track which item is selected? Ignore selection-changed on second click since selection won't have changed. Here is an example from pytk FAQs modified to handle this case...
import gtk
import gtk.gdk
current_path = []
current_path.append(None)
def on_selection_changed(iconview, current_path):
if iconview.get_selected_items():
if cmp(current_path[0], iconview.get_selected_items()[0]):
print "selection-changed"
current_path[0] = iconview.get_selected_items()[0]
def on_item_activated(iconview, path):
print "item-activated"
# First create an iconview
view = gtk.IconView()
view.connect("selection-changed", on_selection_changed, current_path)
view.connect("item-activated", on_item_activated)
# Create a store for our iconview and fill it with stock icons
store = gtk.ListStore(str, gtk.gdk.Pixbuf)
for attr in dir(gtk):
if attr.startswith('STOCK_'):
stock_id = getattr(gtk, attr)
pixbuf = view.render_icon(stock_id,
size=gtk.ICON_SIZE_BUTTON, detail=None)
if pixbuf:
store.append(['gtk.%s' % attr, pixbuf])
# Connect our iconview with our store
view.set_model(store)
# Map store text and pixbuf columns to iconview
view.set_text_column(0)
view.set_pixbuf_column(1)
# Pack our iconview into a scrolled window
swin = gtk.ScrolledWindow()
swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
swin.add_with_viewport(view)
swin.show_all()
# pack the scrolled window into a simple dialog and run it
dialog = gtk.Dialog('IconView Demo')
close = dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_NONE)
dialog.set_default_size(400,400)
dialog.vbox.pack_start(swin)
dialog.run()
I can't seem to get any mouse clicks in a QTreeWidget. I have tried...
...overriding mousePressEvent, but it never runs at all. Not even to log a message.
...using an event filer. It works for everything but mouse clicks.
...using delegates. Their editor events work fine, but only when over an item, which isn't enough
...making sure everything is being added to layouts. I used QTCreator and the output is using layout.addWidget(). I am also adding the widget instance to a layout in the main window.
I was able to use the answer to register the widget as an event filter for the QTreeWidget like so:
# In __init___
# self.tree is the QTreeWidget
self.tree.viewport().installEventFilter(self)
def eventFilter(self, target, event):
"""
This widget is an event filter for the tree, so this function is triggered
automatically
"""
# Print on right-click
if (event.type() == QEvent.MouseButtonPress and
event.button() == Qt.RightButton):
print("Right Click")
# Don't block/accept the event
return False
because what you can see (and click) on QTreeWidget is actually it's viewport(). You sholud install event filter on it's viewport() instead.
I am trying to adjust the context menu in a QTextEdit. I have succeeded in getting access to and then displaying the default menu with the following code:
class LinkTextBrowser(QTextBrowser):
def contextMenuEvent(self, event):
menu = self.createStandardContextMenu(event.pos())
# do stuff to menu here
menu.popup(event.globalPos())
However, this does not work for location-sensitive clicks. The case in question is the "Copy Link Location" item in a QTextBrowser's right click menu, which is only enabled if you right click on a link, for obvious reasons. I can't get it to ever be enabled. I suspect I am passing the wrong position to createStandardContextMenu, but I can't figure out the correct position to feed it.
I have tried both event.globalPos() and event.pos(), neither of which work. I also looked at the source code for QTextEdit, but didn't get anywhere. What position is it expecting?
Edit: Update: It appears the problem is the scrolling in the TextBrowser; if I scroll to the top of the window and use event.pos() it behaves. I don't have working code yet, but correcting for the scroll is the solution.
(Specifically, I want to disconnect the signal emitted by the Copy Link Location action and connect it to my own function so I can adjust the URL before copying it to the clipboard, allowing me to make links absolute and so forth before copying, and I have no particular desire to re-write the working bits.)
Here is the working transform of the coordinates:
class LinkTextBrowser(QTextBrowser):
def contextMenuEvent(self, event):
self.link_pos = event.pos()
# correct for scrolling
self.link_pos.setX(self.link_pos.x() + self.horizontalScrollBar().value())
self.link_pos.setY(self.link_pos.y() + self.verticalScrollBar().value())
menu = self.createStandardContextMenu(self.link_pos)
# do stuff to menu
menu.popup(event.globalPos())
Try self.mapToGlobal(event.pos()), it should take into account scroll position.
Maybe you can try something like:
QMenu *menu = new QMenu();
menu->addAction(...);
menu->exec(textEdit->mapToGlobal(pos));
It's C++ but I'm sure that you can easy convert it to python.