Python GTK3 How to bring widgets to the front? - python

I have an application (actually a plugin for another application) that presents a GTK notebook. Each tab contains a technical drawing of an operation, with a set of SpinButtons that allow you to alter the dimensions of the operation.
If you need more context, it's here: https://forum.linuxcnc.org/41-guis/26550-lathe-macros?start=150#82743
As can be seen above, this all worked fine in GTK2. The widgets (first iteration in a GTK_Fixed, then moved to a GTK_Table) were pre-positioned and the image (a particular layer of a single SVG) was plonked in behind.
Then we updated to GTK3 (and Python 3) and it stopped working. The SVG image now appears on top of the input widgets, and they can no-longer be seen or operated.
I am perfectly happy to change the top level container[1], if that will help. But the code that used to work (and now doesn't) is:
def on_expose(self,nb,data=None):
tab_num = nb.get_current_page()
tab = nb.get_nth_page(tab_num)
cr = tab.get_property('window').cairo_create()
cr.set_operator(cairo.OPERATOR_OVER)
alloc = tab.get_allocation()
x, y, w, h = (alloc.x, alloc.y, alloc.width, alloc.height)
sw = self.svg.get_dimensions().width
sh = self.svg.get_dimensions().height
cr.translate(0, y)
cr.scale(1.0 *w / sw, 1.0*h/sh)
#TODO: gtk3 drawing works, but svg is drawn over the UI elements
self.svg.render_cairo_sub(cr = cr, id = '#layer%i' % tab_num)
[1] In fact I will probably go back to GTK_Fixed and move the elements about in the handler when the window resizes, scaled according to the original position. The GTK_Table (deprecated) version takes over 2 minutes to open in the Glade editor.
Unless there is a more elegant way to do this too?

Related

Remove empty space at bottom of QTableWidget

I'm using PySide6 6.4.1 to build a table widget that automatically resizes to the number of rows. Here's a minimal example:
from PySide6.QtWidgets import *
class MW(QWidget):
def __init__(self):
super().__init__()
self.button = QPushButton("Test")
self.table = QTableWidget(self)
self.table.setColumnCount(1)
self.table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.setLayout(QVBoxLayout(self))
self.layout().addWidget(self.button)
self.layout().addWidget(self.table)
self.button.clicked.connect(self.test)
return
def test(self):
self.table.insertRow(0)
self.table.setItem(0, 0, QTableWidgetItem("new item"))
self.table.adjustSize()
self.adjustSize()
return
app = QApplication()
mw = MW()
mw.show()
app.exec()
Somehow this always leaves a bit of empty space at the bottom of the table. How do I get rid of this space without doing manual resizing?
(Nevermind the weird font size, it's a known bug when using UI scaling. I've adjusted the font size manually as well and it doesn't get rid of this problem.)
Qt item views inherit from QAbstractScrollArea, which has some peculiar size related aspects:
it has an Expanding size policy that tells the parent layout it can use as much space as possible, possibly increasing the available space at initialization;
it has a minimumSizeHint() that always includes a minimum reasonable size allowing showing the scroll bars (even if they are not visible);
if the sizeAdjustPolicy is AdjustToContents it's also based on the viewport size hint;
It's also mandatory to consider a fundamental aspect about scroll areas: size management is a tricky subject, and some level of compromise is necessary most of the times. This is the case whenever the scroll bars potentially change the available size of the viewport (the part of the widget that is able to scroll), which is the default behavior of Qt in most systems, unless the scroll bars are always hidden/visible or they are transient (they "overlay" above the viewport without affecting its available visible size).
To clarify this aspect, consider a scroll area with content that has a minimum size of 100x100 and scroll bars that have a default extent (width for the vertical one, height for the horizontal) of 20: if the height hint of the content is changed to 110, then you'd theoretically need an area of 100x110. But Qt needs to know the hints before laying out widgets and setting their geometries. This means that you cannot know if the scroll bars have to be shown before the widget is finally laid out, but that hint is required to lay out the widget itself. So, recursion.
Qt layout management is a system that is far from perfect, but I doubt that there is one, at least considering normal UI management (don't consider web layouts: their concept is based on different assumption, most importantly the fact that the whole "window" has potentially infinite dimensions). This is an aspect that must be always considered, especially if the shown contents are set to adapt their size based on the contents; it's the case of fitInView() of QGraphicsView or the known issues of QLayout with rich text based widgets.
Qt doesn't provide "foolproof" solutions for these aspects, because its layout management doesn't allow it as it has been implemented primarily considering performance and usability: the UI has to work and be responsive before being "fancy".
It's one of the reasons for which it's almost impossible to have real fixed-aspect-ratio widgets or windows. You can work around it, but at some point you'll have some inconsistencies, and you have to live with that. Also consider that this kind of behavior is generally not very UX-friendly. UI elements that resize themselves (and, consequentially, alter the whole layout) at anytime are usually annoying and very user-unfriendly, especially if they displace their or other contents: it's like having a car that constantly moves the driving controls depending on the amount of passengers.
That said, it's not impossible to have a partially working solution.
The requirements are to:
override minimumSizeHint(), so that a minimal reasonable size is always returned;
override sizeHint() that is used to adjust the widget (and parents) based on the contents of the view;
change the vertical size policy of the table to Preferred, which will tell the layout manager that the height of the size hint will be considered as default, still allowing it to expand in case other items in the layout don't use the remaining space, and eventually shrink it if required;
eventually do the same for the horizontal policy in order to adapt it to the actual horizontal header size, otherwise use self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch), but be aware that this might complicate things (see the note after the code);
class TableWidget(QTableWidget):
def sizeHint(self):
hHeader = self.horizontalHeader()
vHeader = self.verticalHeader()
f = self.frameWidth() * 2
# the simple solution is to get the length, but this might be a problem
# whenever *any* section of the header is set to Stretch
targetWidth = width = f + hHeader.length()
# a possible alternative (but still far from perfect):
width = f
for c in range(self.columnCount()):
if hHeader.isSectionHidden(c):
continue
width += self.sizeHintForColumn(c)
targetWidth = width
if not vHeader.isHidden():
width += vHeader.width()
hpol = self.horizontalScrollBarPolicy()
height = f + vHeader.length() + hHeader.height()
if (
hpol != Qt.ScrollBarAlwaysOff
and not self.horizontalScrollBar().isHidden()
and (
hpol == Qt.ScrollBarAlwaysOn
and hHeader.length() + f < targetWidth
)
):
height += self.horizontalScrollBar().sizeHint().height()
return QSize(width, height)
def minimumSizeHint(self):
hint = self.sizeHint()
minHint = super().minimumSizeHint()
return QSize(
min(minHint.width(), hint.width()),
min(super().minimumSizeHint().height(), hint.height())
)
class MW(QWidget):
def __init__(self):
# ...
pol = self.table.sizePolicy()
pol.setVerticalPolicy(QSizePolicy.Preferred)
self.table.setSizePolicy(pol)
Be aware that the above doesn't solve all problems. It might work fine for a QTableView having just one column or when using the default interactive (or fixed) section resize mode, but whenever you set different resize modes for each column the result may be wrong.
In order to provide a finer resize, you'll need to do much complex computations that take into account each section resize mode for the horizontal header, the default/minimum/maximum and eventually the hint based on the content.
Further notes: 1. calling adjustSize() on the parent is normally enough, it's not necessary to do it on the children; 2. self.setLayout(QVBoxLayout(self)) is pointless, the self argument already sets the layout; just use layout = QVBoxLayout(self) and use that as a local variable to add widgets; 3. in Python the return at the end of a function is always implicit, you shall not add it as it's useless, redundant and distracting.

How to get window origin (position) in mate applet?

I try to get window origin in a mate panel applet.
To be more precise I want to know the position of my applet (x and y) on the screen because I have a button which show/hide a Gtk.window but I need to move that window next to my button (above, below, right, left depending on where the mate panel is)
The only way that I found is to call get_origin but there is a problem. It should return a tuple x,y but instead like the c function it require two integers and since python use pass by value of course it doesn't work.
This code is valid but useless:
window = self.get_window()
x = 0
y = 0
window.get_origin(x, y)
All other "way to use" get_origin (that you can found in any doc) does not work because it require 3 args (I don't know why)
So I'm looking for a way to get the position of my applet (even if it's not accurate) or to move my window next to my button.
I found an alternative get_root_coords
window = self.get_window()
x,y = window.get_root_coords(0, 0)
It works even with multiple screens and I'm able to move my Gtk.Window next to my button with it.

WxPython wxSplitterWindow.SplitVertically always shows uneven split

I'm trying to understand how to use the wxSplitterWindow class but can't seem to get a vertical split to be even (i.e. left and right panes each take up the same amount of space). According to the wxPython 3.03 documentation, wxSplitterWindow.SplitVertically has the following signature:
SplitVertically(self, window1, window2, sashPosition=0)
The last parameter sashPosition has the following description:
sashPosition - The initial position of the sash. If this value is positive, it specifies the size of the left pane. If it is negative, it is absolute value gives the size of the right pane. Finally, specify 0 (default) to choose the default position (half of the total window width).
From the description, I gather that passing a 0 for sashPosition splits the window in half with the left and right panes taking up equal space. However, when I run the following example program, I get a window with an uneven split where almost all of the left panel is hidden.
import wx
class SplitterFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='SplitterWindow example')
# Create the main splitter window (to be split vertically)
self.splitter = wx.SplitterWindow(self)
self.rightPanel = wx.Panel (self.splitter)
self.rightPanel.SetBackgroundColour(wx.BLUE)
self.leftPanel = wx.Panel (self.splitter)
self.leftPanel.SetBackgroundColour(wx.RED)
# Expecting an even split with this call
self.splitter.SplitVertically (self.leftPanel, self.rightPanel, 0)
if __name__ == '__main__':
app = wx.App(0)
frame = SplitterFrame()
frame.Show()
app.MainLoop()
I'm running Python v2.7.10 using wxPython v3.0 toolkit on a Windows 8.1 machine.
I had the same problem, by trial, and error I get that this modification solved the problem
self.splitter.SplitHorizontally(self.leftPanel, self.rightPanel, 100)
But this is dependent on the size of the frame.
I'm using wxpython version 3.0.0.0
I know this thread is ancient, but in case anyone else has this issue:
From the wiki: https://wxpython.org/Phoenix/docs/html/wx.SplitterWindow.html#wx.SplitterWindow.SetSashGravity
Notice that when sash gravity for a newly created splitter window, it is often necessary to explicitly set the splitter size using SetSize to ensure that is big enough for its initial sash position. Otherwise, i.e. if the window is created with the default tiny size and only resized to its correct size later, the initial sash position will be affected by the gravity and typically result in sash being at the rightmost position for the gravity of 1
Either set the size manually with splitter.SetInitialSize(...) (obviously frame size dependent)
or use splitter.SetSashGravity(.5) so they grow evenly.

tkinter winfo_screenwidth() when used with dual monitors

With tkinter canvas, to calculate the size of the graphics I display, I normally use the function winfo_screenwidth(), and size my objects accordingly.
But when used on a system with two monitors, winfo_screenwidth() returns the combined width of both monitors -- which messes up my graphics.
How can I find out the screen width in pixels of each monitor, separately?
I have had this problem with several versions of Python 3.x and several versions of tkinter (all 8.5 or above) on a variety of Linux machines (Ubuntu and Mint).
For example, the first monitor is 1440 pixels wide. The second is 1980 pixels wide. winfo_screenwidth() returns 3360.
I need to find a way to determine the screenwidth for each monitor independently.
Thanks!
It is an old question, but still: for a cross-platform solution, you could try the screeninfo module, and get information about every monitor with:
import screeninfo
screeninfo.get_monitors()
If you need to know on which monitor one of your windows is located, you could use:
def get_monitor_from_coord(x, y):
monitors = screeninfo.get_monitors()
for m in reversed(monitors):
if m.x <= x <= m.width + m.x and m.y <= y <= m.height + m.y:
return m
return monitors[0]
# Get the screen which contains top
current_screen = get_monitor_from_coord(top.winfo_x(), top.winfo_y())
# Get the monitor's size
print current_screen.width, current_screen.height
(where top is your Tk root)
Based on this slightly different question, I would suggest the following:
t.state('zoomed')
m_1_height= t.winfo_height()
m_1_width= t.winfo_width() #this is the width you need for monitor 1
That way the window will zoom to fill one screen. The other monitor's width is just wininfo_screenwidth()-m_1_width
I also would point you to the excellent ctypes method of finding monitor sizes for windows found here. NOTE: unlike the post says, ctypes is in stdlib! No need to install anything.

gtk.MessageDialog with images and background image

I came across a function to insert image in a gtk dialog box. but there is some issue with it and not working.
messagedialog = gtk.MessageDialog(parent=None, flags=0, type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_OK,\
message_format="Congratulations..!!")
messagedialog.set_image('scoreimg') #line 3
action_area = messagedialog.get_content_area()
lbl2=gtk.Label("Awesome")
action_area.pack_start(lbl2)
messagedialog.show_all()
messagedialog.run()
messagedialog.destroy()
The code is not working due to #line 3. Yes, the image is available to this code.
I guess, set_image is used to set background of a dialog box. I want to add some images in dialog box (not background this time).
Also, I am trying to eliminate the "bulb" from the dialog that appears based on type=gtk.MESSAGE_INFO though I need an "OK" button.
Any idea about how I can proceed with this?
here is Gtk3, which is basically the same.
messagedialog = Gtk.MessageDialog (None, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO,\
Gtk.ButtonsType.OK, "Congratulations..!!")
""" Assume you have it """
scoreimg = Gtk.Image ()
scoreimg.set_from_file ("yourpathhere") #or whatever its variant
messagedialog.set_image (scoreimg) #without the '', its a char
action_area = messagedialog.get_content_area()
lbl2=Gtk.Label("Awesome")
action_area.add(lbl2)
messagedialog.show_all()
messagedialog.run()
messagedialog.destroy()
A note: "set-image" property override the bulb icon (from gnome-hicolor)

Categories