How do I resize an array of squished PyQt5 widgets? [duplicate] - pyqt5

I have a QScrollArea Widget, which starts empty;
It has a vertical layout, with a QGridLayout, and a vertical spacer to keep it at the top, and prevent it from stretching over the whole scroll area;
Elsewhere in the program, there is a QTextEdit, which when changed, has its contents scanned for "species" elements, and then they are added to the QGridLayout. Any species elements which have been removed are removed too. This bit works;
I have turned the vertical scrollbar on all the time, so that when it appears it does not sit on top of the other stuff in there. Note that the scroll bar is larger than the scroll box already though, despite not needing to be.
This is the problem. The scroll area seems to be preset, and i cannot change it. If i add more rows to the QGridLayout, the scroll area doesn't increase in size.
Instead, it stays the same size, and squeezes the QGridLayout, making it look ugly (at first);
And then after adding even more it becomes unusable;
Note that again, the scroll bar is still the same size as in previous images. The first two images are from Qt Designer, the subsequent 3 are from the program running.
If I resize the window so that the QScrollArea grows, then I see this:
Indicating that there's some layout inside the scroll area that is not resizing properly.
My question is; what do I need to do to make the scrollable area of the widget resize dynamically as I add and remove from the QGridLayout?

If you're coming here from Google and not having luck with the accepted answer, that's because you're missing the other secret invocation: QScrollArea::setWidget. You must create and explicitly identify a single widget which is to be scrolled. It's not enough to just add the item as a child! Adding multiple items directly to the ScrollArea will also not work.
This script demonstrates a simple working example of QScrollArea:
from PySide.QtGui import *
app = QApplication([])
scroll = QScrollArea()
scroll.setWidgetResizable(True) # CRITICAL
inner = QFrame(scroll)
inner.setLayout(QVBoxLayout())
scroll.setWidget(inner) # CRITICAL
for i in range(40):
b = QPushButton(inner)
b.setText(str(i))
inner.layout().addWidget(b)
scroll.show()
app.exec_()

The documentation provide an answer :
widgetResizable : bool
This property holds whether the scroll area should resize the view widget.
If this property is set to false (the default), the scroll area honors the size of its widget.
Set it to true.

Why don't you use a QListView for your rows, it will manage all the issues for you? Just make sure that after you add it you click on the Class (top right window of designer) and assign a layout or it wont expand properly.
I use a QLIstWidget inside a QScrollArea to make a scrollable image list
Try this for adding other objects to the list, this is how I add an image to the list.
QImage& qim = myclass.getQTImage();
QImage iconImage = copyImageToSquareRegion(qim, ui->display_image->palette().color(QWidget::backgroundRole()));
QListWidgetItem* pItem = new QListWidgetItem(QIcon(QPixmap::fromImage(iconImage)), NULL);
pItem->setData(Qt::UserRole, "thumb" + QString::number(ui->ImageThumbList->count())); // probably not necessary for you
QString strTooltip = "a tooltip"
pItem->setToolTip(strTooltip);
ui->ImageThumbList->addItem(pItem);

Update on Artfunkel's answer:
Here's a PySide6 demo that uses a "Populate" button to run the for loop adding items to the scroll area. Each button will also delete itself when clicked.
from PySide6.QtWidgets import *
app = QApplication([])
scroll = QScrollArea()
scroll.setWidgetResizable(True) # CRITICAL
inner = QFrame(scroll)
inner.setLayout(QVBoxLayout())
scroll.setWidget(inner) # CRITICAL
def on_remove_widget(button):
button.deleteLater()
def populate():
for i in range(40):
b = QPushButton(inner)
b.setText(str(i))
b.clicked.connect(b.deleteLater)
inner.layout().addWidget(b)
b = QPushButton(inner)
b.setText("Populate")
b.clicked.connect(populate)
inner.layout().addWidget(b)
scroll.show()
app.exec()

Related

Fit to frame with vertical justification options

Working with InDesign script I am using
TF.fit(FitOptions.frameToContent). //TF as a textFrame
to fit the content into the textframe and remove the extra space
But when I have a condition for vertical justification options set to Align: Bottom i am not able to fit the content.
Assuming this to be my TextFrame (Image 1)
Here are the properties of the text frame (Image 2)
When I do FitOptions.frameToContent i get this (Image 3)
But when the text frame has the align property set to the bottom I want this to fit it like this(this was achieved by double-clicking on the top center alignment as shown in the red indicator below. (Image 4) (Image 5)
I tried all the available options at http://jongware.mit.edu/idcs4js/pe_FitOptions.html but I had no luck making it work. Please help me to achieve the same using scripting.
Cross-reference: https://community.adobe.com/t5/indesign/fit-to-frame-with-vertical-justification-options/td-p/11120458?page=1
================== First try ==================
var tf = app.selection[0];
var tfp = tf.textFramePreferences;
tfp.autoSizingReferencePoint = AutoSizingReferenceEnum.BOTTOM_CENTER_POINT;
tfp.autoSizingType = AutoSizingTypeEnum.HEIGHT_ONLY;
tf.fit(FitOptions.frameToContent)
This still gives me the same result(image 3) but expected result is image 4.
================== Second try ==================
This is the auto-sizing property.
This command indeed seems to work only with the top edge as a reference point, just as it does in the UI (if you actually use the menu command, not clicking any borders).
What you could do to work around this, is to temporarily enable auto-sizing (height only) and setting the reference point for auto-sizing to the bottom:
var tf = app.selection[0];
var tfp = tf.textFramePreferences;
tfp.autoSizingReferencePoint = AutoSizingReferenceEnum.BOTTOM_CENTER_POINT;
tfp.autoSizingType = AutoSizingTypeEnum.HEIGHT_ONLY;
tfp.autoSizingType = AutoSizingTypeEnum.OFF;
Note, the last line turns off the auto sizing again and is optional; if you don't mind that the frame keeps auto sizing you don't need to turn it off.

How to stretch inline frame size in panel stretch layout in ADF?

I have .jsff page that contain command button and inline frame. What I want to do is, I want to make command button remain static at the same place and only inline frame can move when scroll the page.
Currently what I do is I set some panel stretch layout(StyleClass:AFStetchWidth). I put the command button at the top. Inline frame in scroll panel group at the center.
Here is my structure:
af:panelStetchLayout(StyleClass:AFStretchWidth)
>
Panel Stretch Layout facets
bottom
center
af:panelGroupLayout-scroll
af:inlineFrame (StyleClass:AFStretchWidth)
end
start
top
af:panelGroupLayout-horizontal
af:commandButton-back
When I run this page: command button remain static at the top. This is correct, but the size of the inline frame is small. Is there a way to make an inline frame to be stretch?
set the (StyleClass:AFStretchWidth) on the af:panelGroupLayout-scroll.
Did you try putting this attribute :
sizing="preferred"
I have used it and it works pretty well inside panel stretch layout.
just i solve it as following:
<af:panelGroupLayout id="pgl1" halign="center">
<af:inlineFrame id="if1" source="/index.html" styleClass="AFStretchWidth" inlineStyle="height:100%;"/>
</af:panelGroupLayout>
inlineStyle="height:100%;"
on default af:panelGroupLayout
thats all.
I was getting the same issue. Here I have solved it as below:
I have used panel splitter for inline frame.
<af:panelSplitter styleClass="AFStretchWidth" inlineStyle="height:500px;" id="ps3" dimensionsFrom="parent"
positionedFromEnd="false">
<f:facet name="first">
<af:panelGroupLayout id="pgl11" >
<af:inlineFrame styleClass="AFStretchWidth" partialTriggers="cb3" source="#{pdfHandler.servletString}"
id="if1" visible="true" binding="#{pdfHandler.inLineFrame}" shortDesc=" "
inlineStyle="height:500px;"/>
</af:panelGroupLayout>
</f:facet>
Here as shown above,
I have added inlineStyle for height in both panelSplitter and in inlineFrame.
Added dimensionFrom for panelSplitter as 'parent'
Surround inlineFrame with panelGroupLayout.
With this, It worked for me.

GTKmm - unable to set fixed size to Gtk::Scale widget

I am writing a simple photo viewer in C++ using gtkmm and I can not sort out how to set widget size. In the bottom of main window I have Gtk::Box with 3 buttons, a label and a Gtk::Scale widget. I would like to set fixed size to Gtk::Scale widget and buttons, and give rest of the space to label. I have only managed to set fixed size to buttons, and divide extra space evenly between label and scale widgets, by adding widgets like this:
bottom_box->pack_start(*left_button, false, false);
bottom_box->pack_start(*right_button, false, false);
bottom_box->pack_start(*filename_label, true, true);
bottom_box->pack_start(*image_zoom, true, true);
bottom_box->pack_start(*fit_button, false, false);
When I try to set both expand and fill to false while adding image_zoom to bottom_box, the widget is way to small, and set_size_request() makes no change. Is there another way to do it?
Code responsible for creating the window is here (the rest is in the repository): https://github.com/jjkrol/ZPR/blob/master/src/gui.cpp
Thank you very much in advance.
I've sorted this out, I think the problem was associated with fact that I've called set_size_request() before adding Gtk::Scale to Gtk::Box.

Use Dojo Drag and Drop together with Dojo Moveable

I'm using Dojo.dnd to transfer items between to areas. The problem is: the items will snap into place once I drop them, but I'd like to have them stay where I drop them, but only for one area.
Here's a little code to explain this better:
<div id="dropZone" class="dropZone">
<div id="itemNodes"></div>
<div id="targetZone" dojoType="dojo.dnd.Source"></div>
</div>
"dropZone" is a DIV that contains two dojo.dnd.Source-areas, "itemNodes" (created programmatically) and "targetZone". Items (DIVs with images) should be dragged from a simple list out of "itemNodes" into "targetZone" and stay where they are dropped. As soon as they are dragged out of "targetZone" they should snap back to the list inside "itemNodes".
Here's the code I use to create the items:
var nodelist = new dojo.dnd.Source("itemNodes");
{Smarty-Loop}
nodelist.insertNodes(false, ['<img class="dragItem" src="{$items->info.itemtext}" alt="{$items->info.itemtext}" border="0" />']);
{/Smarty-Loop}
But this way I just have two lists of items, the items dropped into "targetZone" won't stay where I dropped them. I've tried a loop dojo.query(".dojoDndItem").forEach(function(node) to grab all items and change them to a "moveable"-type:
using dojo.dnd.move.constrainedMoveable will change the items so they can always be moved around (even in "itemNodes")
using dojo.dnd.move.boxConstrainedMoveable and defining the "box" to the borders of "targetZone" makes it possible to just move the items around inside "targetZone", but as soon as I drop them, I can't grab and move them back out. (Strange: dojo.connect(node, "onMoved" doesn't work here, the even won't fire, no matter what.)
So here's the question: is it possible to create two dnd.Sources where I can move items back and forth and let the items be "moveable" only in one of the sources?Or is there a workaround like making the items moveable and if they're not dropped into "targetZone" they'll be moved back to the list in "itemNodes" automatically?
Once the page is submitted, I have to save the position of every item that has been placed into "targetZone". (The next step will be positioning the items inside "targetZone" on page load if the grid has already been filled before, but I'd be happy to just get the thing working in the first place.)
Any hint is appreciated.
Greetings, Select0r
There is no direct support for such features. It can be done with a custom code, e.g., by subclassing a Source and overriding its insertNodes().
Here's a quick workaround to get this working:
I ended up using only one DIV which is a dojo.dnd.Source and contains the items that should be dropped into a "dropZone" and moved around in it while snapping back to the item-list when placed outside the dropZone.
All items are a dojo.dnd.move.parentConstrainedMoveable to make them movable in the originating DIV. Connecting to onMoveStop will give me the opportunity to decide whether the "drop" has occured in the dropZone or somewhere else.
if (coordX >= (dropCoords.l + dropAreaX) &&
coordX <= (dropCoords.l + dropAreaX + dropAreaW) &&
coordY >= (dropCoords.t + dropAreaY) &&
coordY <= (dropCoords.t + dropAreaY + dropAreaH))
{
// OK!
}
else
{
// outside, snap back to list
}
dropAreaX and dropAreaY contain the coordinates where the dropZone starts, dropAreaW and dropAreaH contain its width and height.
If "OK!", the items will be saved into an array, so I know which items have been dropped. Otherwise the item will be removed from that array (if it's in there) and the item will be placed back into the list (via CSS "left: 0"). The number of elements in the array will tell me how many elements are left in the list, so I can "stack" them in a loop using "top: numberOfElement * heightOfElement px").
There's more to it as I need the items coordinates written to hidden fields, but I guess this should get anybody who's working on a similar problem on the right track.

PyGTK: dynamic label wrapping

It's a known bug/issue that a label in GTK will not dynamically resize when the parent changes. It's one of those really annoying small details, and I want to hack around it if possible.
I followed the approach at 16 software, but as per the disclaimer you cannot then resize it smaller. So I attempted a trick mentioned in one of the comments (the set_size_request call in the signal callback), but this results in some sort of infinite loop (try it and see).
Does anyone have any other ideas?
(You can't block the signal just for the duration of the call, since as the print statements seem to indicate, the problem starts after the function is left.)
The code is below. You can see what I mean if you run it and try to resize the window larger and then smaller. (If you want to see the original problem, comment out the line after "Connect to the size-allocate signal", run it, and resize the window bigger.)
The Glade file ("example.glade"):
<?xml version="1.0"?>
<glade-interface>
<!-- interface-requires gtk+ 2.16 -->
<!-- interface-naming-policy project-wide -->
<widget class="GtkWindow" id="window1">
<property name="visible">True</property>
<signal name="destroy" handler="on_destroy"/>
<child>
<widget class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">In publishing and graphic design, lorem ipsum[p][1][2] is the name given to commonly used placeholder text (filler text) to demonstrate the graphic elements of a document or visual presentation, such as font, typography, and layout. The lorem ipsum text, which is typically a nonsensical list of semi-Latin words, is a hacked version of a Latin text by Cicero, with words/letters omitted and others inserted, but not proper Latin[1][2] (see below: History and discovery). The closest English translation would be "pain itself" (dolorem = pain, grief, misery, suffering; ipsum = itself).</property>
<property name="wrap">True</property>
</widget>
</child>
</widget>
</glade-interface>
The Python code:
#!/usr/bin/python
import pygtk
import gobject
import gtk.glade
def wrapped_label_hack(gtklabel, allocation):
print "In wrapped_label_hack"
gtklabel.set_size_request(allocation.width, -1)
# If you uncomment this, we get INFINITE LOOPING!
# gtklabel.set_size_request(-1, -1)
print "Leaving wrapped_label_hack"
class ExampleGTK:
def __init__(self, filename):
self.tree = gtk.glade.XML(filename, "window1", "Example")
self.id = "window1"
self.tree.signal_autoconnect(self)
# Connect to the size-allocate signal
self.get_widget("label1").connect("size-allocate", wrapped_label_hack)
def on_destroy(self, widget):
self.close()
def get_widget(self, id):
return self.tree.get_widget(id)
def close(self):
window = self.get_widget(self.id)
if window is not None:
window.destroy()
gtk.main_quit()
if __name__ == "__main__":
window = ExampleGTK("example.glade")
gtk.main()
Here's a one-line variation on killown's solution:
label.connect('size-allocate', lambda label, size: label.set_size_request(size.width - 1, -1))
The above will make sure that the label takes on the width allocated to it, so that word-wrapping is happy.
Not sure why there's a "-1" for the width, but it seems harmless!
VMware's libview has a widget called WrapLabel which should do what you want, but it's in C++. A Python translation is available in the Meld repository (separated out from busybox.py).
example for resize and wrap the label dynamically:
EDIT:
import gtk
class DynamicLabel(gtk.Window):
def __init__(self):
gtk.Window.__init__(self)
self.set_title("Dynamic Label")
self.set_size_request(1, 1)
self.set_default_size(300,300)
self.set_position(gtk.WIN_POS_CENTER)
l = gtk.Label("Dynamic Label" * 10)
l.set_line_wrap(True)
l.connect("size-allocate", self.size_request)
vbox = gtk.VBox(False, 2)
vbox.pack_start(l, False, False, 0)
self.add(vbox)
self.connect("destroy", gtk.main_quit)
self.show_all()
def size_request(self, l, s ):
l.set_size_request(s.width -1, -1)
DynamicLabel()
gtk.main()
You can use this. Not sure where it came from originally. Create your label and then call label_set_autowrap(label)
def label_set_autowrap(widget):
"Make labels automatically re-wrap if their containers are resized. Accepts label or container widgets."
# For this to work the label in the glade file must be set to wrap on words.
if isinstance(widget, gtk.Container):
children = widget.get_children()
for i in xrange(len(children)):
label_set_autowrap(children[i])
elif isinstance(widget, gtk.Label) and widget.get_line_wrap():
widget.connect_after("size-allocate", _label_size_allocate)
def _label_size_allocate(widget, allocation):
"Callback which re-allocates the size of a label."
layout = widget.get_layout()
lw_old, lh_old = layout.get_size()
# fixed width labels
if lw_old / pango.SCALE == allocation.width:
return
# set wrap width to the pango.Layout of the labels
layout.set_width(allocation.width * pango.SCALE)
lw, lh = layout.get_size() # lw is unused.
if lh_old != lh:
widget.set_size_request(-1, lh / pango.SCALE)
In GTK 3, this is done automatically using height-for-width and width-for-height size requests.
I just wanted to share how I made Kai's solution work with PyGtk and Glade-3 using wraplabel.py.
I didn't want to have to modify Glade catalogs to get WrapLabel in Glade and I'm not sure if that would work anyway with a PyGtk component. I was however pleasantly surprised to find that simply by putting the WrapLabel class in the python environment before calling into gtk.Bilder() it will load the class as a component.
So now the only problem was to get the WrapLabels into the glade file. First I changed the names of all the labels I wanted to wrap to wlabel###, where ### is some number. Then I used a sed expression to replace the classes, but since I didn't want to add extra processing to the build system I ended up adding the following in python:
import re
import gtk
from wraplabel import WrapLabel
. . .
# Filter glade
glade = open(filename, 'r').read()
glade = re.subn('class="GtkLabel" id="wlabel',
'class="WrapLabel" id="wlabel', glade)[0]
# Build GUI
builder = gtk.Builder()
builder.add_from_string(glade)
I'm sure there are more elegant ways to do the substitution but this worked well. However, I found I had one more problem. When I opened one of the dialogs with the wrapped labels some of the text was not visible. Then when I resized the window with the mouse, even a little bit, everything would snap in to place. Some how the labels were not getting the right sizes when initialized. I fixed this with another work around. When opening one of the dialogs I run this code:
def open_dialog(self, dialog):
# Hack to make WrapLabel work
dims = dialog.get_size()
dialog.resize(dims[0] + 1, dims[1] + 1)
dialog.present()
dialog.resize(*dims)
This just sets the size one point too big, presents the window and then resets to the correct size. This way the WrapLabels get the signal to resize after the dialog layout is complete.
There is still one small glitch. When you open the dialog sometimes you can see the text snapping in to place. Otherwise, it seems to work.
NOTE 1) All the variations of calling label.set_size_request(size.width - 1, -1) on size-allocate caused the GUI to lockup for me. Probably depends on the parent widgets.
NOTE 2) Another solution is to use TextView's and disable editing, the cursor and sensitivity. However, TextViews have a different color than the background which is difficult to fix in the face of Gtk themes. The other problem with this solution is that TextViews capture mouse scroll events. This makes mouse scrolling a box with these TextViews inside it very erratic. I tried many things to solve the mouse scroll problem but never did figure it out. Otherwise using TextViews does work. So you might consider this if your text labels are not inside a scroll pane and the WrapLabel solution doesn't work for you.
I have modified the code that was in the other answers to get a callback that behaved a little better:
def on_label_size_allocate(self, label, allocation, *args):
""" Callback that re-allocates the size of a label to improve word wrap. """
layout = label.get_layout()
layout.set_width((allocation.width-20) * pango.SCALE)
_, lh = layout.get_pixel_size()
label.set_size_request(-1, lh+6)
The -20 and +6 numbers were obtained by trial and error. It would be nice to get them from somewhere in the widgets, but I couldn't find any relationship to the widgets. This makes the label resize fine both in growing and shrinking and lines are not cut.