Get list of existing QShortcuts - pyqt

I would like to create various objects with associated keyboard shortcuts. The following code works fine. However, it doesn't prevent multiple objects from being assigned the same shortcut keys.
I'd like the create_shortcut function to check if there are existing shortcuts with the same keys and return an error if there are.
I've looked over the documentation for QWidget but haven't found any way to list existing shortcuts.
Any idea how to do this?
class MyClass(QObject):
def __init__(self, parent, shortcut_keys: str):
super().__init__(parent)
self.create_shortcut(shortcut_keys)
def create_shortcut(self, shortcut_keys) -> None:
parent = self.parent()
self.shortcut = QShortcut(QKeySequence(shortcut_keys), parent)
self.shortcut.activated.connect(self.do_stuff)
def do_stuff(self):
...

QShortcut inherits from QObject, meaning that it is always shown in the list of children of the parent for which it was created.
Assuming that the shortcuts have always been created with a parent, just use findChildren(className):
def create_shortcut(self, shortcut_keys) -> None:
seq = QKeySequence(shortcut_keys)
for other in self.parent().findChildren(QShortcut):
if seq == other.key():
# already exists, ignore
return
# create new shortcut
shortcut = QShortcut(QKeySequence(shortcut_keys), parent)
shortcut.activated.connect(self.do_stuff)
Note: creating persistent instance attributes is pointless whenever those attributes will be most certainly overwritten.
Alternatively, just create a list as instance attribute, append new shortcuts to it and always check for them before creating others.

Related

Kivy Mapview: distinguish buttons pressed

Sorry for this noob question, im still learning and I find little documentation for the mapview module.
So for one screen page I have this:
class Mapspage(Screen):
def __init__(self, **kwargs):
self.aboutname="hi"
super(Mapspage, self).__init__(**kwargs)
gl = GridLayout(cols=1)
mapview = MapView(zoom=12, lat=55.6712674, lon=12.5938239)
self.buttons=[]
self.nums=range(0,len(df["name_en"]))
for i in self.nums:
name=list(df["name_en"])[i]
marker = MapMarkerPopup(lat=list(df["latitude"])[i], lon=list(df["longitude"])[i])
self.buttons.append(button(text=list(df["name_en"])[i],on_press=partial(self.pressbutton,num=self.nums[i]),size=(len((df["name_en"])[i])*7*1.05,15),size_hint=(None,None)))
marker.add_widget(self.buttons[i])
mapview.add_marker(marker)
gl.add_widget(mapview)
self.add_widget(gl)
def pressbutton(self,num, *args):
global aboutname
aboutname=(df["name_en"])[num]
chatapp.screenmanager.current = "About"
where I use from functools import partial.
The problem with my approach, despite I made sure the buttons are stored in a different variable (elements of the self.buttons list), and even made sure the numbers them self are stored in a separate list, I cant get to pass the number variable, that distinguishes the buttons (that appear when clicking the maps point), on to the pressbutton function. When I run my attempt I recieve the error, TypeError: pressbutton() got multiple values for argument 'num' where I think all buttons passed on their num variable.
The problem is with the definition of your pressbutton() method and its handling of keyword arguments. You can handle keywords by defining pressbutton() as:
def pressbutton(self, button_instance, num=99):
which defines num as a keyword argument and provides a default value.
Another option is something like:
def pressbutton(self, button_instance, **kwargs):
num = kwargs.pop('num', 99)
which does the same thing.

Python and GTK+3: widget for choosing a keyboard shortcut

I'm looking for a way to add a shortcut chooser widget on a dialog with Python and GTK+3.
I tried to search through all available widgets and don't seem to find any out-of-the-box solution. What would be my best call in that respect? Should I use a GtkEntry and intercept a key press?
Even though it seems like a pretty common use case, I failed to find any working example of that.
There is no out-of-the-box solution, but you can probably find an example to adapt in the Keyboard panel of GNOME Control Center.
I have implemented this myself using a separate dialog. There's a regular button displaying the current assignment, which, when clicked, opens a KeyboardShortcutDialog implemented as follows:
class KeyboardShortcutDialog(Gtk.Dialog):
"""Dialog that allows to grab a keyboard shortcut."""
def __init__(self, parent):
Gtk.Dialog.__init__(self, _("Keyboard shortcut"), parent, 0)
self.shortcut = None
self.set_border_width(32)
# Add a label
label = Gtk.Label(xalign=0.5, yalign=0.5)
label.set_markup(
_('Press the desired key combination, <b>Backspace</b> to remove any shortcut, or <b>Esc</b> to cancel.'))
self.get_content_area().pack_start(label, True, True, 0)
self.connect('key-press-event', self.on_key_press)
self.show_all()
def on_key_press(self, widget, event: Gdk.EventKey):
"""Signal handler: key pressed."""
keyval = event.get_keyval()[1]
name = Gdk.keyval_name(keyval)
# For some reason event.is_modifier == 0 here, even for modifier keys, so we need to resort to checking by name
if name not in [
'Shift_L', 'Shift_R', 'Control_L', 'Control_R', 'Meta_L', 'Meta_R', 'Alt_L', 'Alt_R', 'Super_L',
'Super_R', 'Hyper_L', 'Hyper_R']:
logging.debug('Key pressed: state=%s, keyval=%d', event.state, keyval)
self.shortcut = (
keyval,
event.state &
(Gdk.ModifierType.META_MASK | Gdk.ModifierType.SUPER_MASK | Gdk.ModifierType.HYPER_MASK |
Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.MOD1_MASK))
self.response(Gtk.ResponseType.ACCEPT)
return True
def run(self):
"""Show the dialog and block until it's closed.
:return: tuple (keyval, state) of the key captured or None if the dialog has been closed."""
super().run()
return self.shortcut
The dialog's run() method returns a tuple specifying the pressed key combination.

Combining Tkinter Widgets Into Single Class Limits Accessibility?

I am working on a project that involves creating many instances of Tkinter Labels and Entry widgets that will always be aligned next to one another. To try and save myself time, I created a custom class that I am showing below:
class labelEntry(Label,Entry):
def __init__(self,parent,label,row,column,bg_color):
Label.__init__(self,parent)
self['text']=label
self['justify']='right'
self['bg']=bg_color
self.grid(row=row,column=column, sticky=E)
Entry.__init__(self,parent)
self['width']="10"
self.grid(row=row,column=column+1)
This creates the configuration I want and is easy enough to arrange (I have them stored in a frame). The problem is I don't know how to access the Entry widgets that I have created as they are part of this new class.
I have a desire to read and delete the entries from the entry widgets. My best guess at clearing them was with this button that was being fed into the same frame:
class clearAllEntry(Button):
def clearAll(self,targetFrame):
targetFrame.labelEntry.Entry.delete(0,END)
def __init__(self,parent,targetFrame):
Button.__init__(self,parent,text='Clear All Entries',bg='black',fg='white')
self['command']= "clearAll(targetFrame)"
I have also looked at grid_slave as an approach but am having the same issue.
Any advice/help would be greatly appreciated.
First off, if you're creating a new class that contains two objects of different classes, you should not be using inheritance. Instead, use composition.
Second, to be able to access the entry widget, save it to an instance variable.
For example:
class LabelEntry():
def __init__(self, parent, label, row, column, bg_color):
self.label = Label(parent, text=label, justify='right', bg=bg_color)
self.entry = Entry(parent, width=10)
self.label.grid(row=row, column=column, sticky="e")
self.grid(row=row,column=column+1)
Later, you can reference these attributes like you can any other attribute:
le1 = LabelEntry(root)
...
print(le1.entry.get())

Need to call class method from different class without initialization of the first class or some other way around it

I have a small problem with my code.
There are two classes. First one creates a window with a Options button. Upon clicking the button, the second class is called and creates another window with an Ok button. Let's say there is also a checkbox, which changes the background color to black or something like that. After clicking the button, whatever changes were made in the options are stored into a file and the second window is closed.
All of this works fine. My problem is that now I need to call method update_init from the first class that will apply those changes to the MainWindow. The code below shows my first solution to this problem, but from what I understand, by using second mainloop I create second thread, which should be avoided.
class MainWindow:
def __init__(self, master):
self.master = master
self.options_btn = tk.Button(self.master, text="Options", command=self.open_options)
self.options_btn.pack()
self.options_window = None
def open_options(self):
options_master = tk.Toplevel()
self.options_window = OptionsWindow(options_master)
options_master.mainloop()
lst = meta_load() # loads changes from a file
self.update_init(lst)
def update_init(self, lst):
#code
class OptionsWindow:
def __init__(self, master):
self.master = master
self.ok_btn = tk.Button(self.master, text="OK", command=self.update_meta)
self.ok_btn.pack()
def update_meta(self):
meta_save(12) # saves changes into a file
self.master.destroy()
main_master = tk.Tk()
main_master.minsize(width=1280, height=720)
b = MainWindow(main_master)
main_master.mainloop()
My second solution was to just put both classes into one, but the code is quite messy if I do so.
Can I somehow call the method update_init (which is in the MainWindow class) from the OptionsWindow class without initializing new MainWindow class window? Or is there any other way to deal with this? I would appreciate any help.
I am sorry if this is too specific, I've tried to make it as general as possible, but it's a very specific problem and I couldn't find much information about it anywhere on the internet.
In general you can call a class method from anywhere you want and pass anything to it without initialisation of that class's instance, thanks to objective nature of python, but beware of self dependencies! Although, I don't think that's a good practice.
class A:
def __init__(self):
self.foo = 'foo'
def return_foo(self):
return self.foo
class B:
def __init__(self):
self.bar = 'bar'
print('Ha-ha Im inited!')
def return_bar(self):
try:
return self.bar
except AttributeError:
return 'bar'
def test():
a = A()
# b = B()
return_bar = getattr(B, 'return_bar', None)
if callable(return_bar):
print('%s%s' % (a.return_foo(), return_bar(None)))
test()
Links:
getattr
callable

Signal/Slot help-- setting a signal to a slot outside of the current class

I'm trying to populate a table (present in the main window) from a slider that's located in a widget in a separate class. I can't seem to get it to work...what's the best way to go about doing this?
Here's my current code:
class Widget(QWidget):
def __init__(self,filename,parent=None):
super(Widget,self).__init__(parent)
self.resize(900,900)
self.layout=QVBoxLayout(self)
frame=Frame(filename)
self.image=pg.ImageView()
self.image.setImage(frame.data)
self.image.setCurrentIndex(0)
fileheader=FileHeader(filename)
self.slider=QSlider(self)
self.slider.setOrientation(Qt.Horizontal)
self.slider.setMinimum(1)
self.slider.setMaximum(fileheader.numframes)
self.slider.sliderMoved.connect(self.sliderMoved)
self.layout.addWidget(self.image)
self.layout.addWidget(self.slider)
def sliderMoved(self,val):
print "slider moved to:", val
fileheader=FileHeader(filename)
idx=val
frame=fileheader.frameAtIndex(idx)
self.image.setImage(frame.data)
class MainWindow(QMainWindow):
def __init__(self, filename, parent=None):
super(MainWindow,self).__init__(parent)
self.initUI(filename)
def initUI(self,filename):
self.filetable=QTableWidget()
self.frametable=QTableWidget()
self.imageBrowser=Widget(filename)
self.imagesplitter=QSplitter(Qt.Horizontal)
self.tablesplitter=QSplitter(Qt.Horizontal)
self.imagesplitter.addWidget(self.imageBrowser)
self.tablesplitter.addWidget(self.imagesplitter)
self.tablesplitter.addWidget(self.filetable)
self.tablesplitter.addWidget(self.frametable)
self.setCentralWidget(self.tablesplitter)
exitAction=QAction(QIcon('exit.png'),'&Exit',self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(qApp.quit)
openAction=QAction(QIcon('open.png'),'&Open',self)
openAction.setShortcut('Ctrl+O')
menubar=self.menuBar()
fileMenu=menubar.addMenu('&File')
fileMenu.addAction(exitAction)
fileMenu.addAction(openAction)
self.fileheader=FileHeader(filename)
self.connect(self.frametable,
SIGNAL("Widget.sliderMoved(idx)"),
self.fileheader.frameAtIndex(idx))
self.frameheader=self.fileheader.frameAtIndex(0)
self.populate()
def populate(self):
self.filetable.setRowCount(len(self.fileheader.fileheader_fields))
self.filetable.setColumnCount(2)
self.filetable.setHorizontalHeaderLabels(['File Header','value'])
for i,field in enumerate(self.fileheader.fileheader_fields):
name=QTableWidgetItem(field)
value=QTableWidgetItem(unicode(getattr(self.fileheader,field)))
self.filetable.setItem(i,0,name)
self.filetable.setItem(i,1,value)
self.frametable.setRowCount(len(self.frameheader.frameheader_fields))
self.frametable.setColumnCount(2)
self.frametable.setHorizontalHeaderLabels(['Frame Header','Value'])
for i,fields in enumerate(self.frameheader.frameheader_fields):
Name=QTableWidgetItem(fields)
Value=QTableWidgetItem(unicode(getattr(self.frameheader,fields)))
self.frametable.setItem(i,0,Name)
self.frametable.setItem(i,1,Value)
I know the "connect" is wrong-- I'm very new to PyQt and Python in general, so I'm not quite sure where to start.
Since self.imageBrowser is your Widget class, it will have the slider attribute which has the sliderMoved signal. You just need a few more dots.
self.imageBrowser.slider.sliderMoved.connect(self.fileheader.frameAtIndex)
The way you have it organized is correct though. Your main window composes your custom widgets and binds the connections together.
Though because you have a data source, and also a QTableWidget that will need to be updated, you probably need to wrap the steps up into a little method:
def initUI(self,filename):
...
self.imageBrowser.slider.sliderMoved.connect(self._handle_slider_moved)
# initialize it the first time during the window set up
self._handle_slider_moved(0)
def _handle_slider_moved(self, val):
# update the data source
self.fileheader.frameAtIndex(val)
# update the second data source
self.frameheader=self.fileheader.frameAtIndex(0)
# now refresh the tables
self.populate()

Resources