I have a simple QSplitter with a few widgets thrown in:
...
actions_splitter = QSplitter(
Qt.Horizontal,
frameShape=QFrame.StyledPanel,
frameShadow=QFrame.Plain
)
actions_splitter.addWidget(self.systems)
actions_splitter.addWidget(self.events)
actions_splitter.addWidget(self.compass_widget)
actions_splitter.setChildrenCollapsible(False)
...
The resulting splitters function perfectly fine, in that I can hover my mouse over their position, see the cursor change, and drag them around and change the sizes of the neighboring widgets.
The problem is that the handles themselves, the QSplitterHandles, absolutely refuse to have any visual modifications stick to them.
I have tried style sheets:
handle = actions_splitter.handle(0)
handle.setStyleSheet("{background-color: red}")
I have tried adding layouts with widgets to them:
handle = actions_splitter.handle(0)
layout = QHBoxLayout()
widget = QPushButton("HANDLE")
layout.addWidget(widget)
handle.setLayout(layout)
And absolutely nothing is doing anything to change how these handles appear. The only "visual" thing I can change is their width, and that's via QSplitter.setHandleWidth().
Someone please point out the painfully, stupidly obvious thing I'm missing here so I can get on with my life.
Premise
QSplitterHandle is one of the few very specific widgets that don't seem to follow generic QWidget "rules"; the reason is pretty simple: its aim is normally limited to the usage it's intended for, which is being a QSplitter child.
Then, you have to consider that, while pretty "liberal", the stylesheet syntax follows basic but important rules, it depends on the underlying QStyle in use and its behavior might change whenever a child widget is very dependant on its parent, as in this case.
First of all, there's a conceptual problem in your approach: the stylesheet syntax is wrong, as you're trying to use a selector-like syntax (using {braces}) without specifying a selector. With a syntax like that, the stylesheet won't be applied anyway, even with much more "standard" widgets.
But that's not enough. The QSplitter handle() documentation explains an important point (emphasis mine):
Returns the handle to the left of (or above) the item in the splitter's layout at the given index, or nullptr if there is no such item. The handle at index 0 is always hidden.
Even if your syntax were correct, it wouldn't have worked anyway since you tried to apply the stylesheet to the first (hidden) handle.
So, theoretically, the correct syntax should've been the following:
self.splitter.handle(1).setStyleSheet("background-color: red;")
even better:
self.splitter.handle(1).setStyleSheet(
"QSplitterHandle {background-color: red;}")
or, for all handles:
self.splitter.setStyleSheet(
"QSplitterHandle {background-color: red;}")
On the other hand, since QSplitterHandle is a very peculiar widget, its color properties are applied in different ways according to the currently applied QStyle, even when a correct syntax is used. Remember that stylesheets are overrides to the current QStyle, which means that the QStyles states the painting rules, and applies the eventual stylesheet according to those rules.
For instance, while the "windows" style uses the background color rule of the stylesheet to draw the actual background of the handle, the fusion style uses the background color to draw the "dots" of the splitter only.
As with other complex widgets (like QComboBox or QAbstractItemView), you need to use specific selectors for sub-controls, as clearly explained in the QSplitter stylesheet documentation; using the selector you ensure that the color rule is applied to the handle as a whole, no matter the style in use:
self.splitter.setStyleSheet("QSplitter::handle {background-color: red}")
Related
The QToolBar method insertWidget(QAction, QWidget) does correctly insert the widget at the intended place, but QToolBar.children() (actually QObject.children()) shows the output list of children ordered according to the order in which they were added or inserted, not in the order of their left-to-right appearance; the children() doc says that explicitly. That's probably not unreasonable, since children() is a method of QObject, and QObject knows nothing about its derived toolbar.
This may be related to a perplexing bug (IMO) in which QToolBar returns incorrect action objects on a mouse press event after an insertWidget(). I believe the system is counting the position of the clicked widget and returning its action object based on the (false) assumption that all widgets were added with addWidget(). But rather than dwell on this possible bug, my question is: is there a way to shuffle the output of children() such that it reflects the appearance of the widgets in the toolbar, rather than the order in which they were added or inserted? Brute force solutions are always possible, but I'd prefer something pythonic.
Edit:
(I think there used to be a link on this site, "answer your own question". I can't find it, so I'll post it here.)
It is not possible to shuffle the output of children() or its variants. However, a workaround is possible. The reason for wanting the toolbar items in order of appearance is so that they may be saved (e.g., in a ConfigParser file) and restored in the same order.
If any toolbar items have been added with insertWidget(), QToolbar.children() or its variants will not reflect the order of their appearance in the toolbar. IOW, code that looks like this is useless in finding widgets in toolbar order:
fieldlst = self.toolbar.findChildren(QPushButton,'', Qt.FindChildOption.FindDirectChildrenOnly)
However, a toolbar contains a layout which is implicity added and can be retrieved with QToolbar.layout(). Items can then be retrieved in the order of their appearance with code like this:
lt = self.toolbar.layout()
fieldlst = [lt.itemAt(k).widget() for k in range(lt.count())]
Cruder solutions are possible without using the layout, such as reading the enclosing rectangle of widgets returned by a children() variant, and then sorting on the coordinate of the left edge, but the solution given above seems simplest.
I worked somewhere in the past where since we had multiple sessions of Maya open, the background color could be randomly changed so when you switched from a session quickly it was easy to sort out which window belonged to what Maya session.
And so far, I can change the bgc of the main UI by using:
window -e bgc 0.5 0.5 0.5 $gMainWindow;
After searching for other global variables, I found $AllWindows, $CommandWindow, among others since the docs state that 'bgc' is a windows only flag. I can not get any of the colors to change on any window besides the $gCommandWindow, which popped up and I do not recall seeing it before.
I'm hoping to at least change the Script Editor window in addition to MainWindow if anyone knows whether it's possible or not? It is not mission critical, but now I'm interested in seeing if it can be done.
Thanks!
Since Maya's interface is using Qt, you can use the power of PySide to tweak any widget you want. Usually the only tricky part is actually finding the proper widget to modify.
Here's how you can tweak the Script Editor to give it a yellow border:
import shiboken2
from maya import cmds
from maya import OpenMayaUI
from PySide2 import QtWidgets
panels = cmds.getPanel(scriptType="scriptEditorPanel") # Get all script editor panel names.
if panels: # Make sure one actually exists!
script_editor_ptr = OpenMayaUI.MQtUtil.findControl(panels[0]) # Grab its pointer with its internal name.
script_editor = shiboken2.wrapInstance(long(script_editor_ptr), QtWidgets.QWidget) # Convert pointer to a QtWidgets.QWidget
editor_win = script_editor.parent().parent().parent().parent() # Not very pretty but found that this was the best object to color with. Needed to traverse up its parents.
editor_win.setObjectName("scriptEditorFramePanel") # This object originally had no internal name, so let's set one.
editor_win.setStyleSheet("#scriptEditorFramePanel {border: 3px solid rgb(150, 150, 45);}") # Set its styleSheet with its internal name so that it doesn't effect any of its children.
OpenMayaUI.MQtUtil gives you the awesome ability to find any control by name, so as long as you know the name of the widget you want to modify, you can find it (tough part is finding it sometimes!). In this case I had to traverse up a few parents to find one that worked best to outline the whole window. You can fool around with this and color, let's say, only the text area. And since this is PySide's style sheets you can do whatever your heart desires, like effect the background color, the thickness of the outline, and so on.
Since we're only effecting the style sheet this also won't save with the preferences and will revert to what it was on a new session.
Hope that helps.
I set a QMainWindow with two layouts, called A and B, in a Pyqt4 app written in Python 3.5
They usually display as shown in this schema:
But at some point, something in Layout B makes wider. Therefore Layout B makes wider too, overlapping Layout A, which remain partially hidden:
Could anybody please give me a hand to find a solution to this behaviour?
I would like to lock Layout A and B widths somehow to avoid this issue.
Without seeing your code, I suspect you add both layouts directly to your main window, without putting them into a main layout that would control their resizing. This way you're creating unnecessary problems, as you need to manage the layout's position and size manually. Read more in the Qt docs.
To solve this, you just add a QHBoxLayout to the main window, then the child layouts to that using addLayout() with an appropriate stretch factor. Here's an example in C++ (untested) to illustrate the necessary steps:
QHBoxLayout* mainLayout = new QHBoxLayout();
QGridLayout* childLayout0 = new QGridLayout();
QGridLayout* childLayout1 = new QGridLayout();
//Add widgets to the child layouts
mainLayout->addLayout(childLayout0, 2);
mainLayout->addLayout(childLayout1, 1);
mainWindow->setLayout(mainLayout);
I'm using Gtk.Fixed and a viewport putting images that, sometimes, overlaps. How can I set the z-index like in html?
Gtk.Fixed doesn't support setting the z-order. It's not meant for overlapping widgets, so the z-order is arbitrary and probably depends on what order the widgets were added in.
It's not clear from your question what your intention is; if you are purposely overlapping widgets, use Gtk.Overlay instead. If you are not intending to overlap them, then you should use a more versatile container such as Gtk.Grid which will take the widgets' sizes into account.
Like ptomato said, I had to use the Gtk.Overlay. I used an overlay with 3 Gtk.Layout, like layers and it works fine.
I only used the C bindings, but I'll try my best to hopefully answer it correctly for the Python bindings as well.
Generally, since GTK4 you can move around your widget using
widget.insert_before (sibling, parent)
Where you might want to obtain the sibling (which will be in front of your widget) and it's parent first.
The function expects the parent to be the parent of the sibling.
So it can be assumed that:
parent = sibling.get_parent ()
At least within a GtkFixed (but possibly other containers as well), siblings are sorted bottom to top, so the first child is the one that's the furthest to the back. "Inserting before" thereby moves your widget behind.
Be aware though, that according to a maintainer, this should only be used within a custom widget and not on an application scale. This is also mentioned in the API of the functions.
From the documentation however, it's not clear to me if this is considered legal. I doubt that this way is supported. However I also doubt, that GtkFixed keeps any track regarding the children order and therefore, I assume that moving children around within a GtkFixed is fine, although illegal.
Moving the widgets around like this also made my application crash, as long as the inspector is open, just as a heads up.
To iterate through the widgets, bottom to top, you can use sibling = parent.get_first_child() as well as sibling = parent.get_next_sibling()
or alternatively use the iterator defined on Widget.
So I currently have a ScrolledPanel that contains a number of TextCtrls that are placed in a vertical BoxSizer programmatically. The reason I'm doing this instead of just appending lines to one big scrolled TextCtrl is so that I can also add other controls in between the TextCtrl, such as images or stylized expand/contract folding stuff.
However, this particular implementation is causing a problem - namely that it is impossible for the user to select text across multiple TextCtrls. Is there a way to do this that will be fast, clean, idiomatic, and not especially kludgy? Is my best bet to write a pointer-location text selection algorithm that essentially reinvents the wheel for the text selection stuff of the underlying native libraries, or is there an easier way to embed other controls inside a multiline scrollable TextCtrl, or even select text across multiple TextCtrls natively?
I would stay away from trying to reimplement text selection controls if at all possible, since that is bound to turn very messy very fast. Another way you could tackle this issue would be to use a single multi-line textctrl widget with the other widgets tacked on over it. This is also messy, but less so.
You can place the other widgets over the textctrl simply by placing them directly over the same position as the textctrl, so long as the other widgets have the same parent as the textctrl. This should work, so long as you don't overlap with the vscrollbar (or, better yet, remove it entirely with style=wx.TE_NO_VSCROLLBAR).
The next thing you'll need to do is pre-fill and space your textctrl so that the user has control of text only right after the position of each widget. You should have each line of text with a different spacing setting, set with the spacing options of wx.TextAttr (the more generic versions of double-spacing, etc), which you calculate based on the particular widget spacing you've given your app. This is necessary to force the user to type only exactly where you want them to.
Next, you'll need to set up a binding to the textctrl newline character that recalculates the spacing needed for each line. Once you've figured out how to handle spacing, this shouldn't be too difficult.
Finally, after you select the text, just reset everything to the same spacing, or whatever else suits your fancy, so that you don't get awkward linebreaks when you paste it back in elsewhere.
I know this is a complicated answer, but it's a complicated issue you raised. This is, I believe, the most efficient way to solve it, and avoids all the bugs that would arise from completely overhauling the textctrl, but it does involve messing around with auto-correcting linebreaks and spacings, which can be a little tricky at first.