How do I make a PyQt QLineEdit widget narrower than the default size of 17 x's when using QT Designer? - layout

When I put Qt widgets in a QHBoxLayout layout using QT Designer, there seems to be a minimum width for QLineEdit widgets of 17 x's, at least if this is the current source code:
https://github.com/qt/qtbase/blob/dev/src/widgets/widgets/qlineedit.cpp
I cannot find a way to make PyQt5 lay those widgets out so they will be narrower than this default, but still change size if the font is changed.
As an example, a QComboBox will be automatically laid out so that it is just wide enough to display the longest text that is entered as a possible value. If the longest text entered for a combobox in Designer is 5 characters, the combobox will be laid out as quite narrow compared to the minimum QLineEdit width. How can I configure a QLineEdit with a number of characters so that it is always wide enough for that many characters, whatever the font is set to, and no wider, using just QT Designer? I know in Designer I can enter maxLength, which will limit the maximum number of characters that can be entered/displayed, but that setting of course has no effect on the layout.
I have some text boxes that will never have more than 5 characters in them, for example, and the layout with Designer makes them at least 3 times wider than I will ever need. This is using the default "Expanding" horizontal policy, but I have tried many combinations of horizontal policy and values for minimum size or base size. I want to allow for people to have different font sizes, so I cannot safely set a maximum pixel size and I cannot safely set a fixed horizontal size. The handling for QComboBoxes is precisely what I want for QLineEdits.
This is all in python using the latest versions of PyQt5 available on Pip, pyqt5 5.15.6 and pyqt5-qt5 5.15.2.

The size hint of QLineEdit is computed considering various aspects, most of them using private functions that are not exposed to the API, and the "x" count is hardcoded, meaning that this cannot be achieved directly from Designer, and can only be done through subclassing.
While we could try to mimic its behavior to implement a custom character size, I believe it is unnecessary for simple cases, so I simplified the concept by taking the default size hint and adapting the width based on the difference between the custom character hint and the default 17 "x" count.
class CharHintLineEdit(QtWidgets.QLineEdit):
_charHint = 17
#QtCore.pyqtProperty(int)
def charHint(self):
return self._charHint
#charHint.setter
def charHint(self, chars):
chars = max(1, chars)
if self._charHint != chars:
self._charHint = chars
self.updateGeometry()
def changeEvent(self, event):
super().changeEvent(event)
if event.type() in (event.FontChange, event.StyleChange):
self.updateGeometry()
def sizeHint(self):
hint = super().sizeHint()
if self._charHint != 17:
# the 17 char width is hardcoded in Qt and there is no way to
# retrieve it, it might change in the future, so, just to be safe,
# we always set an arbitrary minimum based on half the height hint
charSize = self.fontMetrics().horizontalAdvance('x')
hint.setWidth(max(hint.height() // 2, hint.width() +
charSize * (self._charHint - 17)))
return hint
if __name__ == '__main__':
import sys
from random import randrange
app = QtWidgets.QApplication(sys.argv)
test = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(test)
for i in range(5):
charHint = randrange(5, 15)
le = CharHintLineEdit(charHint=charHint, placeholderText=str(charHint))
layout.addWidget(le)
test.show()
sys.exit(app.exec())
With the above code, you can use the custom widget in Designer by adding a standard QLineEdit and promoting it with the class name and relative python file (without the file extension) as header. You can also set the charHint as a dynamic property in Designer, and it will be properly set for the widget when the UI is loaded.

Related

Add a QWidget inside a QFrame

I'm developing a desktop software using Python3 and QtDesigner for the Graphic User Interface.
My problem is the seguent: i'm trying to automate the creation of many QRadioButtons over a QFrame (The RadioButtons must stay inside the frame [as...children?]).
Now, i see that i can only create new widgets inside a Layout (e.g. "MyLayout.addWidget(QRadioButton")) and it's not possible to do something like "MyFrame.addWidget(QRadioButton)". I need these widgets inside the frame cause then i can place them in the correct position with "MyRB.move(X,Y)".
With QtDesigner is possible to place many Widgets (like RadioButtons) in a frame that has a 'broken layout' so i can choose X,Y coordinates but i need to create and place a variable number of those.
Is it possible to create Qwidgets inside a QFrame?
[EDIT]
according to musicamante's comment, i got that's a parent problem.
I tried to insert a Label and a RadioButton in the main window:
def __init__(self):
super().__init__()
uic.loadUi('DSS_GUI2.ui',self) # i load the GUI with QtDesigner
LB1 = QLabel('MyLabel',self)
RB1 = QRadioButton('MyRadioButton',self)
...
This very simple example works fine but when i try to add a Label through a function
def myFunction(self):
LB1 = QLabel('MyLabel')
LB1.setObjectName('LABEL_1')
LB1.setParent(self.myFrame)
the Widget is inserted but it is not visible, in fact adding this lines to check his presence
WidgetList = self.myFrame.findChildren(QLabel)
for item in WidgetList:
print(item.objectName())
i see in the console that the Label is there.
Do you know why it's not visible?
Try
def myFunction(self):
LB1 = self.sender()
LB1.QLabel('MyLabel')
LB1.setObjectName('LABEL_1')
LB1.setParent(self.myFrame)
You can call self.myFunction() in parent.
If you wanted to pass label, you could:
def myFunction(self, label):
LB1 = self.sender()
LB1.QLabel(label)
LB1.setObjectName(label)
LB1.setParent(self.myFrame)

Maximize figures before saving

The question about how to do maximize a window before saving has been asked several times and has several questions (still no one is portable, though), How to maximize a plt.show() window using Python
and How do you change the size of figures drawn with matplotlib?
I created a small function to maximize a figure window before saving the plots. It works with QT5Agg backend.
import matplotlib.pyplot as plt
def maximize_figure_window(figures=None, tight=True):
"""
Maximize all the open figure windows or the ones indicated in figures
Parameters
----------
figures: (list) figure numbers
tight : (bool) if True, applies the tight_layout option
:return:
"""
if figures is None:
figures = plt.get_fignums()
for fig in figures:
plt.figure(fig)
manager = plt.get_current_fig_manager()
manager.window.showMaximized()
if tight is True:
plt.tight_layout()
Problems:
I have to wait for the windows to be actually maximized before using the plt.savefig() command, otherwise it is saved with as not maximized. This is a problem if I simply want to use the above function in a script
(minor problems:)
2. I have to use the above function twice in order to get the tight_layout option working, i.e. the first time tight=True has no effect.
The solution is not portable. Of course I can add all the possible backend I might use, but that's kind of ugly.
Questions:
how to make the script wait for the windows to be maximized? I don't want to use time.sleep(tot_seconds) because tot_seconds would be kind of arbitrary and makes the function even less portable
how to solve problem 2 ? I guess it is related to problem 1.
is there a portable solution to "maximize all the open windows" problem?
-- Edit --
For problem 3. #DavidG suggestion sounds good. I use tkinter to automatically get width and height, convert them to inches, and use them in fig.set_size_inches or directly during the figure creation via fig = plt.figure(figsize=(width, height)).
So a more portable solution is, for example.
import tkinter as tk
import matplotlib.pyplot as plt
def maximize_figure(figure=None):
root = tk.Tk()
width = root.winfo_screenmmwidth() / 25.4
height = root.winfo_screenmmheight() / 25.4
if figure is not None:
plt.figure(figure).set_size_inches(width, height)
return width, height
where I allow the figure to be None so that I can use the function to just retrieve width and height and use them later.
Problem 1 is still there, though.
I use maximize_figure() in a plot function that I created (let's say my_plot_func()) but still the saved figure doesn't have the right dimensions when saved on file.
I also tried with time.sleep(5) in my_plot_func() right after the figure creation. Not working.
It works only if a manually run in the console maximize_figure() and then run my_plot_func(figure=maximized_figure) with the figure already maximized. Which means that dimension calculation and saving parameters are correct.
It does not work if I run in the console maximize_figure() and my_plot_func(figure=maximized_figure) altogether, i.e. with one call the the console! I really don't get why.
I also tried with a non-interactive backend like 'Agg', so that the figure doesn't get actually created on screen. Not working (wrong dimensions) no matter if I call the functions altogether or one after the other.
To summarize and clarify (problem 1):
by running these two pieces of code in console, figure gets saved correctly.
plt.close('all')
plt.switch_backend('Qt5Agg')
fig = plt.figure()
w, h = maximize_figure(fig.number)
followed by:
my_plot_func(out_file='filename.eps', figure=fig.number)
by running them together (like it would be in a script) figure is not saved correctly.
plt.close('all')
plt.switch_backend('Qt5Agg')
fig = plt.figure()
w, h = maximize_figure(fig.number)
my_plot_func(out_file='filename.eps', figure=fig.number)
Using
plt.switch_backend('Agg')
instead of
plt.switch_backend('Qt5Agg')
it does not work in both cases.

Pyglet hello world example doesn't show label until a key is pressed

import pyglet
window = pyglet.window.Window()
label = pyglet.text.Label("Hello World!",
font_name="Times New Roman",
color=(255,255,255,255),
font_size=36,
x=window.width//2, y=window.height//2,
anchor_x="center", anchor_y="center")
#window.event
def on_draw():
window.clear()
label.draw()
pyglet.app.run()
This code is taken from the pyglet tutorial at https://pyglet.readthedocs.io/en/pyglet-1.2-maintenance/programming_guide/quickstart.html but when run it doesn't draw the label until any key is pressed. I added the color as I thought the text may have defaulted to black.
Am I missing something really obvious as to why this behaviour is happening?
OK, having had my memory jogged by the comments, I installed the MS Fonts and it now works in python 2.x but I still need to press a key to see the text in python 3. Maybe the font thing is a red-herring and there is some incompatibility with python 3.
As mentioned in the comments.
Most examples found on the internet (even most guides) assume a Windows platform.
If there's a font= declaration with a Windows font but you're running Linux, make sure you got the proper fonts installed or revert to a font you've got installed.
$ fc-list
Also not declaring a font will work too:
label = pyglet.text.Label("Hello World!",
color=(255,255,255,255),
font_size=36,
x=window.width//2, y=window.height//2,
anchor_x="center", anchor_y="center")
Because Pyglet will default to sans-serif:
If you do not particularly care which font is used, and just need to display some readable text, you can specify None as the family name, which will load a default sans-serif font (Helvetica on Mac OS X, Arial on Windows XP)
Your issue is probably that you don't manually update the screen after you drew something. Normally when you press a key, it forces a window.flip(), which basically means update the screen content.
window.clear() also triggers this behavior, but x.draw() does not. Why? Well the thing that takes time in computer graphics is not really the calculations, it's the updating them to the screen that takes time.. There for .draw() doesn't update the screen, it just puts the stuff in the graphical buffer ("page"), you decide when to flip the page and show the new buffer on the screen.
Try this out:
#window.event
def on_draw():
window.clear()
label.draw()
window.flip()
This might be a overkill solution, but it will probably solve the problem.
This overrides the default loop of pyglet and the default draw behavior, it's also one of the classes i use the most in my pyglet projects since it gives me the option to create my own framework.
import pyglet
class Window(pyglet.window.Window):
def __init__(self):
super(Window, self).__init__(vsync = False)
self.sprites = {}
self.sprites['testlabel'] = label = pyglet.text.Label("Hello World!",
color=(255,255,255,255),
font_size=36,
x=self.width//2, y=self.height//2, #self here, being pyglet.window.Window that we've inherited and instanciated with super().
anchor_x="center", anchor_y="center")
self.alive = 1
def on_draw(self):
self.render()
def render(self):
self.clear()
for sprite_name, sprite_obj in self.sprites.items():
sprite_obj.draw()
self.flip()
def on_close(self):
self.alive = 0
def run(self):
while self.alive:
self.render()
# This is very important, this queries (and empties)
# the pyglet event queue, if this queue isn't cleared
# pyglet will hang because it can't input more events,
# and a full buffer is a bad buffer, so we **NEED** this!
event = self.dispatch_events()
win = Window()
win.run()
It's extremely basic, but you can add more sprites, objects and render them in def render(self):.

How do I set the minimum size of a Gtk ButtonBox child?

I'm trying to set the minimum size of the buttons in this GtkButtonBox. Currently they seem to be fixed - approx 85 pixels I think.
Is this possible?
If not, is there another way in Gtk to get two small sized buttons to snuggle together like in the above picture rather than having them appear to be two separate buttons? For example GtkStackSwitcher may be something I could use but there doesn't appear to be a way to respond to click events for a button.
I've used this test program to create the above (Ubuntu 14.04, Gtk+3.10 and Python3):
from gi.repository import Gtk
import sys
class MyWindow(Gtk.ApplicationWindow):
def __init__(self, app):
Gtk.Window.__init__(self, title="example", application=app)
self.set_default_size(350, 200)
self.set_border_width(10)
hbox = Gtk.ButtonBox.new(Gtk.Orientation.HORIZONTAL)
hbox.set_layout(Gtk.ButtonBoxStyle.EXPAND)
button = Gtk.Button(label="a")
hbox.add(button)
button2 = Gtk.Button(label="b")
hbox.add(button2)
self.add(hbox)
class MyApplication(Gtk.Application):
def __init__(self):
Gtk.Application.__init__(self)
def do_activate(self):
win = MyWindow(self)
win.show_all()
def do_startup(self):
Gtk.Application.do_startup(self)
app = MyApplication()
exit_status = app.run(sys.argv)
sys.exit(exit_status)
With regards to a question about the desktop environment I'm using.
I've tried Mate, Unity and Gnome-Shell. All work the same way. I've removed the title and those controls. Still the same thing happens. To me this looks more like a GTK issue.
I believe that GtkButtonBox imposes some layout constraints on its buttons that you may not want here. Try using buttons in just a regular GtkGrid, but give them the GTK_STYLE_CLASS_LINKED CSS class.
For each button, do:
button.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED)

using layouts in qwidget

I am using qt 4.7 on RHEL 6.0. I am finding very difficut use te layouts. Here is my code.
I have two widgets that are arranged using grid layout.
QWidget *topWidget = new QWidget();
QWidget *bottomwidget = new QWidget();
These two widgets are contained in a QFrame. So now i wanted to add a QPhonon::VideoPlayer widget exactly in b/w the above two widget. For adding am doing like this..
VideoPlayer *vPlayer = new VideoPlayer(Phonon::VideoCategory,this);
vPlayer->setSizePolicy(QSizeHint::Expanding, QSizeHint::Expanding);
QVBoxLayOut *layOut = (QVBoxLayOut*)ui->frame->layout();
layout->insertWidget(1,vPlayer,5,Qt::AlignCenter);
the size of all topWidget, boottomWidget are 768 and 576 which is the size of the D1 video frame. I am playing a video in vPlayer phonon widget. But what I am seeing is vPlayer size is very small and video size also small. Means to say I am not seeing the 768/576 size video. Can somebody help me? Any help would be appreciated.
1)
QVBoxLayOut *layOut = (QVBoxLayOut*)ui->frame->layout();
Do not use C-style casts! Use
dynamic_casr<QVBoxLayout*>(ui->frame->layout())
2)Try to vPlayer->setMinimumSize(QSize(500, 500))
3)And as far as I know Qt, if you want to change GUI or construct complicated ones, then it is preferred to construct GUI's by itself, not with QtDesigner.

Resources