How to create an interactive text menu in Python? - python

I'm not sure what to call it. I want to know how to make one of those menus where you use the arrow keys to highlight your options and press enter to accept it.

The questioner acknowledged being unsure how to clearly state what they're after (I know the feeling!) and it is a little while after the question was posted, but in light of their comment suggesting they were after text, I believe something like python-prompt-toolkit, which is used to provide text-based autocomplete in a number of Python projects, may offer a solution.
There is documentation here which has this example of using a WordCompleter:
from prompt_toolkit import prompt
from prompt_toolkit.completion import WordCompleter
html_completer = WordCompleter(['<html>', '<body>', '<head>', '<title>'])
text = prompt('Enter HTML: ', completer=html_completer)
print('You said: %s' % text)
That produces output like this:
The example above is still relatively like a ComboBox merely rendered in text, but there are ways to produce other styles of menu as shown in the gallery here.
If that isn't sufficient then another option is looking into something that wraps ncurses, like https://bitbucket.org/npcole/npyscreen or http://urwid.org/ .

The answer to your question including the examples you provided is curses. This package is very much dependent on the underlying operating system. So if platform independence is key then you will get issues. There is for example a Windows port UniCurses but your implementation has to handle this switch if necessary.
There are also tools built on top of curses. Four example Urwid.
I personally have some experience with curses and if you have Linux as the underlying system this can be fun if your requirements are robust and simple. Like your menu requirement. I have to say though that the learning curve is pretty steep.
Hope this helps. But given the broad question thus is the only level of detail to provide at this stage.

I think you mean Combobox.
Here is a short example based on this and PyQt:
from PyQt4 import QtGui, QtCore
import sys
class CheckableComboBox(QtGui.QComboBox):
def __init__(self):
super(CheckableComboBox, self).__init__()
self.view().pressed.connect(self.handleItemPressed)
self.setModel(QtGui.QStandardItemModel(self))
def handleItemPressed(self, index):
item = self.model().itemFromIndex(index)
if item.checkState() == QtCore.Qt.Checked:
item.setCheckState(QtCore.Qt.Unchecked)
else:
item.setCheckState(QtCore.Qt.Checked)
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.toolbutton = QtGui.QToolButton(self)
self.toolbutton.setText('Select Categories ')
self.toolmenu = QtGui.QMenu(self)
for i in range(3):
action = self.toolmenu.addAction("Category " + str(i))
action.setCheckable(True)
self.toolbutton.setMenu(self.toolmenu)
self.toolbutton.setPopupMode(QtGui.QToolButton.InstantPopup)
myBoxLayout.addWidget(self.toolbutton)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
combo = Dialog_01()
combo.show()
combo.resize(480,320)
sys.exit(app.exec_())

Related

How PySide6 paintEvent function for a PushButton works?

A long time ago, I wanted to make a logo appear on top of the text in a QPushButton stacked on top of each other, but I couldn't find anyway
I read some stylesheets (couldn't find a single doc to read it all about all styles I can apply to a button)
tried the setLayoutDirection (RightToLeft and LeftToRight were there, but no UpToDown direction)
In my (I wish) last attempt I tried to inherit a QAbstractButton (I didn't find QAbstractPushButton, so I guess QAbstractButton is the answer) and change its paintEvent/paintEngine to draw an image or maybe add a vbox inside it as a layout to draw to components, but I can't find anything in python (specially PySide) which has an example in any possible way close to that. The best thing I found was the analogue clock example which was not very helpful because it was trying to work a QWidget and not a QAbstractButton and I want to keep the feel of a Native looking button.
I like my final product to be something like this.
source of the implemention of that
Python Enaml toolkit supported this feature out of the box (in one of its widgets), and I know it is QT based, so I really wish to know how it is possible?
p.s.: Also, is there a market for qt widgets? e.g.: a plugin system. Because rewriting an android like switch doesn't seem like the correct thing that I should do! even a good tutorial or doc would be appreicated (excluding official doc)
It is easier than you think, you can use QToolButton() like this:
import sys
from PySide6.QtCore import Qt, QSize
from PySide6.QtWidgets import QApplication, QVBoxLayout,QStyle, QWidget,
QToolButton
class Window(QWidget):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
button = QToolButton()
# here you choose the position of the icon and its text
button.setToolButtonStyle(
Qt.ToolButtonStyle.ToolButtonTextUnderIcon)
# here I just use built-in icon by PySide6 for this example
name = 'SP_DialogSaveButton'
pixmapi = getattr(QStyle, name)
icon = self.style().standardIcon(pixmapi)
# here we set text and icon of size 32x32 to the button
button.setIcon(icon)
button.setText("Sample text")
button.setIconSize(QSize(32, 32))
# finally we add our button to the layout
lay = QVBoxLayout(self)
lay.addWidget(button, alignment=Qt.AlignCenter)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = Window()
win.show()
sys.exit(app.exec())

Maya Python: Button always at the center of the window

I'm starting experimenting with Maya python, and I'm trying to do some UI.
I came across to a really strange problem, I can't get a button to stay in the center of the windows.
I've tried different things but nothing seems to work, here is the code:
import maya.cmds as cmds
cmds.window( width=200 )
WS = mc.workspaceControl("dockName", retain = False, floating = True,mw=80)
submit_widget = cmds.rowLayout(numberOfColumns=1, p=WS)
cmds.button( label='Submit Job',width=130,align='center', p=submit_widget)
cmds.showWindow()
this is a simple version but still, I can't get it to work.
can someone help me?
I honestly don't know the answer as anytime I have to dig into Maya's native UI stuff it makes me question my own life.
So I know it's not exactly what you're asking for, but I'll opt with this: Use PySide instead. At first glance it might make you go "woah, that's way too hard", but it's also a million times better (and actually easier). It's much more powerful, flexible, has great documentation, and also used outside of Maya (so actually useful to learn). Maya's own interface uses the same framework, so you can even edit it with PySide once you're more comfortable with it.
Here's a bare-bones example to create a centered button in a window:
# Import PySide libraries.
from PySide2 import QtCore
from PySide2 import QtWidgets
class MyWindow(QtWidgets.QWidget): # Create a class for our window, which inherits from `QWidget`
def __init__(self, parent=None): # The class's constructor.
super(MyWindow, self).__init__(parent) # Initialize its `QWidget` constructor method.
self.my_button = QtWidgets.QPushButton("My button!") # Create a button!
self.my_layout = QtWidgets.QVBoxLayout() # Create a vertical layout!
self.my_layout.setAlignment(QtCore.Qt.AlignCenter) # Center the horizontal alignment.
self.my_layout.addWidget(self.my_button) # Add the button to the layout.
self.setLayout(self.my_layout) # Make the window use this layout.
self.resize(300, 300) # Resize the window so it's not tiny.
my_window_instance = MyWindow() # Create an instance of our window class.
my_window_instance.show() # Show it!
Not too bad, right?

Trouble Understanding Signal Mapper PyQt

So I am generating a menu of options based on some files on my system. I have a list list of objects I need to dynamically generate an option in the menu for and need to be able to let the function that is doing the creation know which object to use. After some research I found the post below. I could not comment as my rep is not high yet: How to pass arguments to callback functions in PyQt
When I run this the signal mapper is not working right. It is not even calling the handleButton correctly. Any ideas as to how to use the signal mapper correctly?
from PyQt4 import QtGui, QtCore
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.mapper = QtCore.QSignalMapper(self)
self.toolbar = self.addToolBar('Foo')
self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly)
for text in 'One Two Three'.split():
action = QtGui.QAction(text, self)
self.mapper.setMapping(action, text)
action.triggered.connect(self.mapper.map)
self.toolbar.addAction(action)
self.mapper.mapped['QString'].connect(self.handleButton)
self.edit = QtGui.QLineEdit(self)
self.setCentralWidget(self.edit)
def handleButton(self, identifier):
print 'run'
if identifier == 'One':
text = 'Do This'
print 'Do One'
elif identifier == 'Two':
text = 'Do That'
print 'Do Two'
elif identifier == 'Three':
print 'Do Three'
text = 'Do Other'
self.edit.setText(text)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(300, 60)
window.show()
sys.exit(app.exec_())
EDIT:
I've found that by using old-style signal/slot connections this is fixed:
#action.triggered.connect(self.mapper.map)
self.connect(action, QtCore.SIGNAL("triggered()"), self.mapper, QtCore.SLOT("map()"))
and
#self.mapper.mapped['QString'].connect(self.handleButton)
self.connect(self.mapper, QtCore.SIGNAL("mapped(const QString &)"), self.handleButton)
Am I using the new-style connections incorrectly?
Based on this post as well as the original link I posted, I thought I was doing things correctly.
The original example code (which I wrote), works perfectly fine for me using either Python2 or Python3 with several different recent versions of PyQt4. However, if I use a really old version of PyQt4 (4.7), the handler no longer gets called.
The reason (and solution) for this is given in the response to the mailing list post you linked to:
It's actually a problem with QSignalMapper.map() being called from a
proxy rather than new-style connections.
The workaround is to explicitly specify a signal that is compatible
with map()...
self.b1.clicked[()].connect(self.mapper.map)
Tonight's PyQt snapshot will be smarter about finding a usable Qt slot
before deciding that it needs to use a proxy so that the workaround
won't be necessary.
There are some signals (like clicked and triggered) which always send a default value unless you explicitly request otherwise. With the old-style signals, you can specify the no default overload it with SIGNAL("triggered()"), but with new-style signals, you have to do it like this:
action.triggered[()].connect(self.mapper.map)
But that is only necessary with very old versions of PyQt4 - the underlying issue was fixed back in 2010 (don't know the exact version, but 4.8 should be okay).

Lock a choice in PyQt4 QComboBox using a QCheckBox

I am beginning to write a GUI using PyQt4. This is my first experience with GUIs (and also oo-programming is somewhat new to me). Part of that GUI will be like 4 to 5 instances of QComboBox. As many choices are to be made, I want the user to be able to lock a choice, such that is not being changed unintenionally later. For one QComboBox I can solve the problem with this code that I wrote:
import sys
from PyQt4 import QtGui, QtCore
class MyGui(QtGui.QWidget):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.resize(250, 50)
# vertical layout for widgets
self.vbox = QtGui.QVBoxLayout()
self.setLayout(self.vbox)
# Create a combo box with some choices
self.combo_color = QtGui.QComboBox()
self.vbox.addWidget(self.combo_color)
items = 'Red Yellow Purple'.split()
self.combo_color.addItems(items)
self.connect(self.combo_color, QtCore.SIGNAL('activated(QString)'), self.use_choice)
# add a checkbox next to the combobox which (un-)locks the the combo-choice
self.checkbox_color = QtGui.QCheckBox('Lock Choice', self)
self.vbox.addWidget(self.checkbox_color)
self.connect(self.checkbox_color, QtCore.SIGNAL('stateChanged(int)'), self.lock_choice)
def use_choice(self, text):
# do something very useful with the choice
print 'The current choice is: {choice}'.format(choice=text)
def lock_choice(self):
if self.checkbox_color.isChecked():
self.combo_color.setEnabled(False)
print 'Choice {choice} locked'.format(choice=self.combo_color.currentText())
else:
self.combo_color.setEnabled(True)
print 'Choice unlocked'
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
mygui = MyGui()
mygui.show()
app.exec_()
This code does what it should, but I am very unhappy with its design, because the method lock_choice is hard-coded to only lock the choice of the QComboBox combo_color. What if I now want to do the same thing for another QComboBox (say combo_name) and a second QCheckBox (say checkbox_name) which could be realized by appending the following code to the classes __init__(self) code block:
# create second combo box with some other choices
self.combo_name = QtGui.QComboBox()
self.vbox.addWidget(self.combo_name)
items = 'Bob Peter'.split()
self.combo_name.addItems(items)
self.connect(self.combo_name, QtCore.SIGNAL('activated(QString)'), self.use_choice)
# add a checkbox next to the combobox which (un-)locks the the combo-choice
self.checkbox_name = QtGui.QCheckBox('Lock Choice', self)
self.vbox.addWidget(self.checkbox_name)
self.connect(self.checkbox_name, QtCore.SIGNAL('stateChanged(int)'), self.lock_choice) # <-- obviously wrong, as it (un-)locks color choice at the moment
Both QComboBoxes can share the method use_choice() right now, but they cannot share the method lock_choice(), as both checkboxes lock the color-choice. I want the checkbox checkbox_name to lock the name-choice, without copy and pasting the current lock_choice()-method and switching the hardcoded combobox.
I am sure there is an easy way, like passing the target-combobox to the method, which i just don't know yet. Help would be appreciated!
Try partial functions-
from functools import partial
with the connect signal, pass the QWidget name:
self.connect(self.checkbox_color, QtCore.SIGNAL('stateChanged(int)'), partial(self.lock_choice, self.combo_color))
And in your method, you may add an argument.
We'll add an argument in the method like below, to handle the QWidget(QComboBox) that we'll pass through the partial function above.
def lock_choice(self, combos):
if combos.isEnabled(True):
combos.setEnabled(False)
print 'Choice {choice} locked'.format(choice=combos.currentText())
The simplest solution would be to use the toggled signal with the setDisabled slot:
self.checkbox_color.toggled.connect(self.combo_color.setDisabled)
(And note how much cleaner the new-style sytax is when making signal connections).
It's also worth pointing out that you can also use a lambda for making inline signal connections, like this:
self.checkbox_color.toggled.connect(
lambda checked: self.combo_color.setDisabled(checked))
This is probably the most idiomatic solution when there are no convenient signal/slot pairings (and of course partial functions can achieve more or less the same thing in different way).

Changing the font in Enthought TraitsUI TextEditor

I would like to change the font in a TextEditor in TraitsUI view. How can I do this?
I read the (excellent) documentation, the API reference docs and asked Google for an answer, but could not find one.
Platform- and toolkit-independence is not a requirement for my application. I work on Windows and use the wx toolkit.
After digging into the source code and some experimenting, I came up with the following solution. To me, this seems to be too complicated (I have to subclass two classes!) to be the simplest or intended way to do this.
If there is a better solution, I would be glad to learn about it.
import wx
from traitsui.wx.text_editor import CustomEditor
from traitsui.editors.text_editor import ToolkitEditorFactory
class _MyTextEditor(CustomEditor):
def init(self, parent):
CustomEditor.init(self, parent)
font = wx.Font(10, wx.FONTFAMILY_MODERN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
self.control.SetFont(font)
class MyTextEditor(ToolkitEditorFactory):
klass = _MyTextEditor
def _get_custom_editor_class(self):
return _MyTextEditor
def _get_simple_editor_class(self):
return _MyTextEditor
if __name__ == "__main__":
from traitsui.api import View, Item
from traits.api import Str, HasTraits
class MyTestcase(HasTraits):
a_string = Str()
traits_view = View(Item('a_string', editor=MyTextEditor()))
w = MyTestcase()
w.configure_traits()
I think Traits uses Qt. So to change the font size, use the style_sheet argument. See example below
Item('a_string', style_sheet='*{font-size:24px}')
If you want to apply multiple font options, separate with a semicolon like this:
Item('a_string', style_sheet='*{font-size:24px; font-style:italic}')
For all of Qt stylesheet options that you can apply, look at
Qt Style Sheets Reference

Categories