How to add to default context menu in Python textbox using PyQt5? - python

I'm trying to add a functionality to a text box where a user can highlight a word, right click and be able to choose whether to define or get synonyms for the highlighted word. I coded a context menu but it only appears when I click outside of the textbox. Is there anyway I can add functions to the default context menu that includes Copy, Paste, etc.? Here is my code for the context menu.
self.setContextMenuPolicy(Qt.ActionsContextMenu)
defineAction = QtWidgets.QAction("Define", self)
defineAction.triggered.connect(lambda: self.define(event))
self.addAction(defineAction)
synonymAction = QtWidgets.QAction("Find Synonyms", self)
synonymAction.triggered.connect(lambda: self.synonym(event))
self.addAction(synonymAction)

You'll need to subclass the text edit widget and override createStandardContextMenu(point).
In your overridden method, cal the base call implementation to get the standard context menu object (it returns QMenu). Modify this menu with custom actions and then return the menu.
The function will be called when the user requests a context menu.
See http://doc.qt.io/qt-5/qplaintextedit.html#createStandardContextMenu for more details
EDIT: You can subclass like this
class MyTextEdit(QLineEdit):
def createStandardContextMenu(self, menu):
#as above, reimplement this method
Then you use that class instead of QLineEdit when you make your GUI.
Alternatively I've remembered there is a signal called customContextMenuRequested. You use this instead like this
#assume you have the textbox in a variable called self.my_textbox
self.my_textbox.setContextMenuPolicy(Qt.CustomContextMenu)
self.my_textbox.customContextMenuRequested.connect(self.generate_context_menu)
and then add a method to the class that generates the GUI like:
def generate_context_menu(self, location):
menu = self.my_textbox.createStandardContextMenu()
# add extra items to the menu
# show the menu
menu.popup(self.mapToGlobal(location))

Related

How to Set and Get "comment" text using setText?

I am trying to store string data within a QAbstractButton.text().
Why?
I want to display the short name in the text() itself, but be able to call the long name via the text() "comment" through code.
You are able to write "comments" within QT Designer, but I have been unable to replicate this in Python.
Looking at the code in notepad, it appears the "comment" text is created within the text string itself:
<property name="text">
<string extracomment="toast">Select object and click here</string>
What I currently have in python is:
Xsl = cmds.ls(sl=1)[0]
Xbutton.setText(Xsl)
How can I also set and get the comment part of this text?
Any advice would be appreciated!
If you want to add extra data to a widget why not just subclass it and create your own?
class MyCustomButton(QtWidgets.QPushButton):
def __init__(self, parent=None):
super(MyCustomButton, self).__init__(parent)
self.my_variable = None
Now you can continue using MyCustomButton just like a normal button, and also add whatever you like to my_variable.
I have found that every object contains a variable for windowTitle. If this isn't the main window, the window title is generally left blank, therefore I can store data here.
Granted, this probably isn't the cleanest approach, but it'll serve for now.
Green Cell's subclassing is most likely the best way to resolve this issue. However, I am mainly building the UI using Qt Designer, and want to primarily keep any edits within that wrapper.
def store_selected_node_on_button(self):
"""
Changes the text of a given button to store an object's name
As the button isn't a window, I can set the window title to store the long name of the selected object.
:return: None
"""
button = self.sender()
sl = cmds.ls(sl=1, long=True)
if not sl:
button.setText("Select object and click here")
button.setWindowTitle("")
else:
button.setText(sl[0].split("|")[-1])
button.setWindowTitle(sl[0])
return

odoo return wizard from onchange method

From what i read you cannot return an action from an onchange function. But I want to show a dialog of some sort when a user changes the product within an orderline. The goal is to check if the product contains has_newproduct = True. In this case I want to ask the user if they want to delete the origin product (since it will not be needed).
I am overriding the onchange function of purchase order line like this:
class api_advanced(models.Model):
_inherit = "purchase.order.line"
has_newproduct = fields.Boolean("New Product")
#api.onchange('product_id')
def onchange_product_id(self):
_logger.debug("------------------- OLD PRODUCT ID ----------------")
_logger.debug(self._origin.product_id)
_logger.debug(self._origin.has_newproduct)
_logger.debug("------------------- NEW PRODUCT ID ----------------")
_logger.debug(self.product_id)
_logger.debug(self.has_newproduct)
# Code to fire wizard is here but it does nothing...
How would i have to acomplish something similar? Or do i have to do something totally different? I could just delete the product without asking but that's not really the goal here...
How you open wizard. when you click on button odoo calls a method,
method return a dictionary to the client side. the client side will
examine the returned value. client side know that this is an action to open a view.
What i'm trying to say this behavior is defined in javascript so if you
want some thing like that you should extends the form view to handle
the returned value from onchange event to open a wizard.
Or you can just add a button to the tree view with text delete orignal product
that button will appear only if has_newproduct = True (use related field to add it to tree so you can use it in attrs). The user will know that he can delete the orignal
product if he see that button and you can add confirm message to explain what
are the consequence of that operation.

Python : Get gtk.treeview selection from another widget

It looks like the only way to get the selected item of a gtk.TreeView() is to actually click on it :
tree_selection = self.treeview.get_selection()
tree_selection.connect('changed', self.my_callback)
self.treeview.connect('row-activated', self.my_other_callback)
But what if I'm listing files in my treeview, and need a "file properties" menu item?
Or a play button, that needs to access selected file to pass the filename to a player class / method ?
Bonus question : How to call my_other_callback from tree_selection.connect('changed', ...) (that does not seem to return all the row data..?) or in other words, how to pass treeview and path to the callback?
To get the selection of a tree view, call the get_selected_rows method of the gtk.TreeSelection object. You can call it at any place from which you can access the tree view.
It is unclear why you want to pass the tree view to my_other_callback since it, being a method on your class, can access it as self.treeview. But if you want to do it anyway, you can add the tree view (or any other Python object) as an additional argument to connect:
tree_selection.connect('changed', self.my_other_callback, self.treeview)
For an even finer-grained control of how the callback is invoked, use a lambda:
tree_selection.connect('changed', lambda *args: self.my_other_callback(self.treeview))
This allows you to use the same handler for multiple signals without having to declare the handler as accepting *args.

Getting multiple wx widget values with Event Handling

CODE: http://pastebin.com/W4uXmazw
I would like to memorize how to get values from any wx widget with event handling after clicking a wx.Button.
In my program i have two fields, the new filename and the contents.
What are the steps i have to take in order to get the values from each field?
From there, i can use pythons f.open and f.write methods to complete my application.
Thanks!
If you want to get value of a widget, then you need to make that widget accessible throughout the entire class. To do that, you need to make the variable for the widget into an instance variable. So instead of adding the text control directly to the sizer, you'll want to do something like this:
self.newfilename = wx.TextCtrl(panel,-1), 0, wx.TOP, 5)
self.contents = wx.TextCtrl(panel,-1,size=(390,150),style = wx.TE_MULTILINE|wx.TE_PROCESS_TAB)
Then in your button's event handler, you can just do something like this:
valueOne = self.newfilename.GetValue()
contents = self.contents.GetValue()
The other way to do it would be to use your panel. If you use "self.panel", then you could grab all its children via its GetChildren method and then iterate over the list and use Python's "isinstance" builtin to check what kind of widget you're accessing. If you have set the widget's name, you can check that too.

Refreshing panel contents in wxPython

What is the approach to update widgets in a wxPanel based on events from other controls on same panel?
Scenario 1 is updating the list of a comboBox based on what has been selected from another comboBox , where both are in same panel.
Scenario 2 is showing a new control/widget in a panel based on an event.
Basically creating new controls is easy but I dont know how to refresh/update my panel so immedialtly shows them.
Scenario 1
To change the choices of a combobox self.cbx you can use any of the following methods:
self.cbx.SetItems(choices) where choices is the full list of choices.
self.cbx.SetString(n, string) that sets the string at position n.
InsertItems(items, pos) Inserts the list of strings in the items argument into the list box before the position in the pos argument.
Note that the method Set(choices) of listboxes does not exist for the list in comboboxes. You must use SetItems(choices) instead (this is not clearly indicated in some textbooks).
If you want these changes to occur as a result of a selection in another combobox self.cbx_1 , just get the event (self.Bind(wx.EVT_COMBOBOX, on_combo_1, self.cbx_1)) of the first combobox, process your data as you like in the corresponding self.on_combo method and use one of the above methods to modify the second combobox.
For example:
def on_combo_1(self, evt):
"append cbx_1 selection to cbx if not already in cbx"
selection = self.cbx_1.GetStringSelection()
cbx_choices = self.cbx.GetItems()
if selection not in cbx_choices:
cbx_choices.append(selection)
self.cbx.SetItems(cbx_choices)
The fact the comboboxes are in the same or different panel is irrelevant for that.
Scenario 2
Normally you put your widgets inside sizers. To hide or made visible elements on the sizer you call the methods Show, Hide or Layout:
Show(self, item, show=True, recursive=false)
Shows or hides an item managed by the sizer. To make a sizer item disappear or reappear, use Show followed by Layout. The item parameter can be either a window, a sizer, or the zero-based index of the item. Use the recursive parameter to show or hide an item in a subsizer. Returns True if the item was found.
Hide(self, item, recursive)
A convenience method for Show (item, False, recursive).
Layout(self)
This method will force the recalculation and layout of the items controlled by the sizer using the current space allocated to the sizer. Normally this is called automatically from the owning window's EVT_SIZE handler, but it is also useful to call it from user code when one of the items in a sizer change size, or items are added or removed.
References: wxPython in Action, Noel Rappin and Robin Dunn
For scenario one, you'd do something like the following (assuming the first combobox is bound to its EVT_COMBOBOX:
value = self.cboOne.GetValue()
if value == "something":
self.cboTwo.SetItems(someList)
For showing a new widget, you could create it and then use Show()/Hide() as necessary. If the widget is in a sizer, then use the Sizer's Append or Insert methods. It also has a Detach method that can be used to hide widgets or you just call Hide itself. See the documentation for more information: http://www.wxpython.org/docs/api/wx.Sizer-class.html

Categories