GtkOverlay hides GtkTextView - python

I'm trying to develop an application using Gtk, and I have run into a problem using GtkOverlay. If I have a GtkOverlay with a GtkTextView that was added using the standard container add method, the text is hidden. However, all other widgets, say for example, buttons, appear just fine. Even more odd is the fact that this behavior is only present if at least one widget was adding using add_overlay.
#!/usr/bin/env python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
USE_OVERLAY = False
win = Gtk.Window()
text_view = Gtk.TextView()
overlay = Gtk.Overlay()
top_button = Gtk.Button()
bottom_button = Gtk.Button()
top_container = Gtk.VBox()
bottom_container = Gtk.VBox()
overlay_str = "( USE_OVERLAY = " + str(USE_OVERLAY) + ")"
win.set_title(overlay_str)
top_button.set_label("I'm a button on top!")
bottom_button.set_label("I'm a button on bottom!")
text_view.get_buffer().set_text("This should be visible")
win.add(overlay)
overlay.add(bottom_container)
bottom_container.pack_start(bottom_button, False, False, 0)
bottom_container.pack_end(text_view, True, True, 0)
if USE_OVERLAY:
overlay.add_overlay(top_container)
top_container.pack_end(top_button, False, False, 0)
win.connect("delete-event", Gtk.main_quit)
overlay.show_all()
win.show_all()
Gtk.main()
I have reason to believe that this is not a python problem, as the actual application is written using haskell-gi, however I figured more people would be familiar with python.

I don't know on what system your a running this example but it is working fine for me. The only caveat is that the top button appears over the bottom button and the TextView widget so I have to manually resize the Window to see the text. You can see a screen cast of my situation in this video: https://youtu.be/xoAH4OuEM0E
Now depending on what you really want there may be few different answers. What I would suggest is putting the TextView inside a ScrolledWindow. This way the TextView will be at least visible before you would need the resize the window. It would also have the consequence to provide scrollbars if the text overflow the visible area.
It could look like this:
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
USE_OVERLAY = True
win = Gtk.Window()
text_view = Gtk.TextView()
overlay = Gtk.Overlay()
top_button = Gtk.Button()
bottom_button = Gtk.Button()
top_container = Gtk.VBox()
bottom_container = Gtk.VBox()
overlay_str = "( USE_OVERLAY = " + str(USE_OVERLAY) + ")"
win.set_title(overlay_str)
top_button.set_label("I'm a button on top!")
bottom_button.set_label("I'm a button on bottom!")
text_view.get_buffer().set_text("This should be visible")
# This is where the text_view is inserted in a ScrolledWindow
scrolled_window = Gtk.ScrolledWindow()
scrolled_window.add(text_view)
win.add(overlay)
overlay.add(bottom_container)
bottom_container.pack_start(bottom_button, False, False, 0)
# The scrolled_window is inserted in the bottom_container
bottom_container.pack_end(scrolled_window, True, True, 0)
if USE_OVERLAY:
overlay.add_overlay(top_container)
top_container.pack_end(top_button, False, False, 0)
win.connect("delete-event", Gtk.main_quit)
overlay.show_all()
win.show_all()
Gtk.main()
You can also see the result on the aforementioned screencast. The only drawback is that the top button won't be able to overlay as much the bottom layer as in your script. But maybe it won't bother you.

Related

How to fix GTK Scrolled Window not updating scroll bars after contents have changed?

I wish to have an image in my GTK app that continually resizes to fit its parent container.
I've accomplished this by getting the parent container's size inside a size-allocate event callback, and resizing my image according to those dimensions. This works fine when I'm making the window smaller, but when I want to make it bigger, it refuses to resize because it has to be at least as big as the contents (the image).
To overcome that aspect, I've placed the image in a ScrolledWindow so that I can freely resize my window smaller.
The issue lies in that when I switch the image shown to one with different dimensions, the ScrolledWindow doesn't seem to realize it, and I'm left with a ScrolledWindow with the wrong content size and unnecessary scroll bars. But alas, I can hover over the scroll bar and it realizes that it's too big for its content and removes the scroll bars. See the below demonstration.
Can I somehow have this "correction" behavior happen right away instead of when I hover over the scroll bars?
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk
from gi.repository import GdkPixbuf
class Minimal(Gtk.Window):
imageShown = 0
img = Gtk.Image.new()
pixbufRed = GdkPixbuf.Pixbuf.new_from_file("kirby_red.png")
pixbufBlue = GdkPixbuf.Pixbuf.new_from_file("kirby_blue.png")
pixbuf = None
def __init__(self):
Gtk.Window.__init__(self)
self.set_default_size(400,300)
button = Gtk.Button.new_with_label("Swap Image")
button.connect("clicked", self.on_button_click)
self.pixbuf = self.pixbufRed
self.img.set_from_pixbuf(self.pixbuf)
scrolled = Gtk.ScrolledWindow()
scrolled.connect("size-allocate", self.on_size_allocated);
scrolled.add(self.img)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL,spacing=0)
box.pack_start(button, False, False, 0)
box.pack_end(scrolled, True, True, 0)
self.add(box)
#swap image shown using imageShown flag to keep track
def on_button_click(self, button):
if(self.imageShown == 0):
self.pixbuf = self.pixbufBlue
self.imageShown = 1
else:
self.pixbuf = self.pixbufRed
self.imageShown = 0
self.img.set_from_pixbuf(self.pixbuf)
def on_size_allocated(self, widget, allocation):
scaledPixbuf = Minimal.scale_image_from_allocation_keep_aspect(self.pixbuf, allocation)
self.img.set_from_pixbuf(scaledPixbuf)
#staticmethod
def scale_image_from_allocation_keep_aspect(pixbuf, allocation):
imgWidth = pixbuf.get_width()
imgHeight = pixbuf.get_height()
parentWidth = allocation.width
parentHeight = allocation.height
aspectWidth = parentWidth/imgWidth
aspectHeight= parentHeight/imgHeight
aspect=0
if(aspectWidth < aspectHeight):
aspect = aspectWidth
else:
aspect = aspectHeight
newWidth = imgWidth*aspect
newHeight = imgHeight*aspect
return pixbuf.scale_simple(newWidth, newHeight, GdkPixbuf.InterpType.BILINEAR)
win = Minimal()
win.show_all()
Gtk.main()
size-allocate isn't really the right place to be changing the contents of your widget (like changing the image widget's pixbuf), and it usually doesn't work correctly if you try to use it like that. It's intended more for custom container widgets to layout their children once the size is already determined.
In GTK 3, I usually solve the problem of making images fill the available space by creating a very simple custom widget, like this:
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GdkPixbuf, Gdk
class ScaleImage(Gtk.DrawingArea):
def __init__(self, pixbuf):
Gtk.DrawingArea.__init__(self)
self.pixbuf = pixbuf
def do_draw(self, cr):
alloc, baseline = self.get_allocated_size()
factor = min(alloc.width / self.pixbuf.get_width(), alloc.height / self.pixbuf.get_height())
cr.scale(factor, factor)
Gdk.cairo_set_source_pixbuf(cr, self.pixbuf, 0, 0)
cr.paint()
win = Gtk.Window()
img = ScaleImage(GdkPixbuf.Pixbuf.new_from_file("file.png"))
win.add(img)
win.show_all()
Gtk.main()
I haven't tried it yet, but in GTK 4 you should be able to use Gtk.Picture to get the same effect without a custom widget.

Can't get Gtk.ListBox() object to add new Gtk.ListBoxRow() objects during runtime in Python

I am using Gtk 3.18 on Windows 10 through Python 3.4. I wrote the following script to press a button to add a new Gtk.ListBoxRow() to the self.listbox in my MyWindow class. It happens in the function add_item(self, whatevs).
I clearly mention there
self.listbox.add(row)
Then why doesn't it work still? The function is being invoked when I press the "Add Item" button. But the list does not get another row. Why so any idea?
Nothing happens. Not even any error is displayed. Can someone please tell me what it is I am doing wrong here? Any help is appreciated.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Win win win")
self.set_border_width(3)
self.set_default_size(300, 250)
box = Gtk.Box(orientation = Gtk.Orientation.VERTICAL, spacing = 6)
self.add(box)
self.listbox = Gtk.ListBox()
self.listbox.set_selection_mode(Gtk.SelectionMode.SINGLE)
box.pack_start(self.listbox, True, True, 0)
row = Gtk.ListBoxRow()
hbox = Gtk.Box()
row.add(hbox)
hbox.pack_start(Gtk.Label("Here is an Item"), True, True, 0)
self.listbox.add(row)
hbox = Gtk.Box()
button_add_item = Gtk.Button(label = "Add Item", valign = Gtk.Align.CENTER)
button_add_item.connect("clicked", self.add_item)
hbox.pack_start(button_add_item, True, True, 0)
button_remove_item = Gtk.Button(label = "Remove Item", valign = Gtk.Align.CENTER)
button_remove_item.connect("clicked", self.remove_item)
hbox.pack_start(button_remove_item, True, True, 0)
box.pack_start(hbox, False, True, 0)
def add_item(self, whatevs):
row = Gtk.ListBoxRow()
hbox = Gtk.Box()
row.add(hbox)
item_label = Gtk.Label("Here is another Item")
hbox.pack_start(item_label, True, True,0)
self.listbox.add(row)
def remove_item(self, whatevs):
self.listbox.remove(self.listbox.remove.get_row_at_y(-1))
win = MyWindow()
win.connect('delete-event', Gtk.main_quit)
win.show_all()
Gtk.main()
By the way can someone tell me why it is that I have to include that other parameter, the one other than "self" in the "connected" functions to the buttons? Any idea how I could use them?
I found out what was wrong. I was supposed to use the Gtk.ListBox.show_all() function of the self.listbox object in my program. Hop this helps someone in the future.
As #shaan-repswal already said, you can call Gtk.ListBox.show_all() after adding the widget(s) to the list.
Remember although that if your row contains widgets that you want to keep not visible, you have to, depending on how you manage the widget:
call your_widget.set_no_show_all(True);
set the attribute props.no_show_all of the widget to True.
add the line
<property name="no_show_all">True</property>
to the object tag of your widget in the glade file.
source: https://stackoverflow.com/a/29130873/9402438
I was originally looking for this problem, regarding C. However I stumbled across this question, so the respective function in C is called:
gtk_widget_show_all(your_list_box);

How can I enable a Gtk.SpinButton in a Gtk.FileChooser?

In my application, I want to make an save mechanism for images.
For this, I'm using a Gtk.FileChooserDialog to select the location of the new file. It is working fine, but I'd also like to be able to select the size of the image (width and height).
I use Gtk.Spinbutton for this and adding them to the content_area of my dialog, then I force to show it.
The widget is showing fine, but I can't interact like to a normal Gtk.Spinbutton: I can't change the value by scrolling nor by clicking on the increment / decrement buttons.
I can still changing the value by typing into the entry.
code showing the issue:
from gi.repository import Gtk
class Window(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.connect("destroy", self.on_destroy)
self.set_icon_name("applications-development")
self.show_all()
dialog = Gtk.FileChooserDialog(
transient_for = self,
buttons = (
Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_SAVE, Gtk.ResponseType.OK
)
)
spinbutton = Gtk.SpinButton()
spinbutton.set_range(0, 10)
spinbutton.set_value(5)
spinbutton.show()
dialog.get_content_area().pack_start(spinbutton, True, False, 0)
dialog.run()
dialog.hide()
def on_destroy(self, *args):
Gtk.main_quit()
Window()
Gtk.main()
Both in python version 2.7.6 and 3.4.3 this bug occurs.
The steps for the two buttons are not specified. So you can either do:
spinbutton = Gtk.SpinButton()
spinbutton.set_range(0, 10)
spinbutton.set_increments(1, -1)
spinbutton.set_value(5)
spinbutton.show()
or:
spinbutton = Gtk.SpinButton.new_with_range(0, 10, 1)
spinbutton.set_value(5)
spinbutton.show()

How to test if GTK+ dialog has been created?

I've used Wnck to check whether a window has been created like this:
screen = Wnck.Screen.get_default()
screen.force_update() # recommended per Wnck documentation
window_list = screen.get_windows()
for window in window_list:
print(window.get_name())
if window.has_name():
if window.get_name() == self.xld_main_window.get_title():
window_found = True
break
assert window_found, 'The Gtk.Window named {window_name} has not been found.'\
.format(window_name=self.xld_main_window.get_title())
# clean up Wnck (saves resources, check documentation)
window = None
screen = None
However, since dialogs don't show up in the list of tasks, I can't find them that way. What is an appropriate way of checking whether they're displayed (and modal / not modal)?
The Wnck.Screen.get_windows method returns all windows including dialogs. There is no distinction as the function returns any Wnck.Window that is currently mapped. The source goes like this:
* The #WnckScreen represents a physical screen. A screen may consist of
* multiple monitors which are merged to form a large screen area. The
* #WnckScreen is at the bottom of the libwnck stack of objects: #WnckWorkspace
* objects exist a #WnckScreen and #WnckWindow objects are displayed on a
* #WnckWorkspace.
*
* The #WnckScreen corresponds to the notion of
* <classname>GdkScreen</classname> in GDK.
GList*
wnck_screen_get_windows (WnckScreen *screen)
{
g_return_val_if_fail (WNCK_IS_SCREEN (screen), NULL);
return screen->priv->mapped_windows;
}
where screen->priv points to a struct containing some lists of the windows (mapped, stacked), a pointer to the active window, etc. Some WnckWindow can have WNCK_WINDOW_DIALOG set and be a dialog.
The WnckWindow class also provides a function transient_is_most_recently_activated() to know if the focus should go to a transient child window when selected in a WnckTaskList or to minimize the transient window with its parent. For example, to know wether My Application window has a most recently activated transient:
screen = Wnck.Screen.get_default()
screen.force_update() # recommended per Wnck documentation
window_list = screen.get_windows()
for window in window_list:
if window.get_name() == 'My Application':
print(window.transient_is_most_recently_activated())
The script below catches the dialogs as other mapped windows (no matter if they are modal/non-modal or the application they are from).
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Wnck', '3.0')
from gi.repository import Gtk, Wnck
class DialogExample(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0, #or Gtk.DialogFlags.MODAL
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.set_default_size(100, 100)
label = Gtk.Label("This is a dialog to display additional information")
box = self.get_content_area()
box.add(label)
self.show_all()
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
button = Gtk.Button("Open dialog")
button.connect("clicked", self.on_button_clicked)
self.add(button)
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
print("The OK button was clicked")
elif response == Gtk.ResponseType.CANCEL:
print("The Cancel button was clicked")
screen = Wnck.Screen.get_default()
screen.force_update() # recommended per Wnck documentation
window_list = screen.get_windows()
for window in window_list:
print(window.get_name())
window, window_list = (None,)*2
screen = None
dialog.destroy()
win = DialogWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
window = None
screen = None
Gtk.main()
Using Gdk instead of Wnck, you are doing the same thing at a slightly lower level.
Taking partially from this answer by Eye of Hell that says how to get the open windows, you can do this:
from gtk import gdk
name = "MyDialog"
root = gdk.get_default_root_window()
matches = []
for id in root.property_get("_NET_CLIENT_LIST"):
window = gdk.window_foreign_new(id)
if window and window.property_get("WM_NAME")[2] == name:
matches.append(window)
for match in matches:
print(match, match.get_modal_hint())

Gtk3 and Python - Box doesn't resize to fill window

I'm writing an app using Gtk3 and Python. I have a revealer as a sidebar to select the content and a webkit webview to display the main content. When the revealer is hidden the webview doesn't fill the entire window space and I don't know why. Any help would be appreciated.
from gi.repository import Gtk, Gio
from gi.repository import WebKit
HEIGHT = 500
WIDTH = 800
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Resolution")
self.set_border_width(0)
self.set_default_size(WIDTH, HEIGHT)
hb = Gtk.HeaderBar()
hb.props.show_close_button = True
hb.props.title = "Resolution"
self.set_titlebar(hb)
button = Gtk.Button()
icon = Gio.ThemedIcon(name="emblem-system-symbolic")
image = Gtk.Image.new_from_gicon(icon, 1)
button.add(image)
button.connect("clicked", self.sidebarShowHide)
button.set_focus_on_click(False)
hb.pack_start(button)
sidebarbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
toplevelbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
self.add(toplevelbox)
self.sidebar = Gtk.Revealer()
self.sidebar.set_transition_duration(0)
self.sidebar.set_reveal_child(False)
toplevelbox.pack_start(self.sidebar, False, False, 0)
self.sidebar.add(sidebarbox)
self.searchentry = Gtk.SearchEntry()
self.searchentry.connect("search-changed", self.search_changed)
sidebarbox.pack_start(self.searchentry, False, False, 0)
label = Gtk.Label("Contents Selector")
sidebarbox.pack_start(label, True, True, 0)
scroller = Gtk.ScrolledWindow()
content = WebKit.WebView()
scroller.add(content)
toplevelbox.pack_start(scroller, True, True, 0)
content.open("/home/oliver/resolution/placeholder.html")
def sidebarShowHide(self, button):
if self.sidebar.get_reveal_child():
self.sidebar.set_reveal_child(False)
else:
self.sidebar.set_reveal_child(True)
def search_changed(self, searchentry):
pass
win = MainWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
Well, i have done some GtkRevealer few months ago and it works. It drive me nuts to see this piece of code was not.
I opened my project again and look inside where that part is, and it turn out the toplevel container where the Gtk.Revealer resides, has to has Gtk.Orientation.VERTICAL.. if you change your "toplevelbox" orientation to that, it will work, but it wont be sidebar. It will coming from top or bottom. It goes the same if you change GtkBox with GtkGrid. If I were to guess it depends on the children default orientation.
Workaround on that, is to use widget hide/show mechanism (believe me, i ransack your code and it works).
from gi.repository import Gtk, Gio
from gi.repository import WebKit
HEIGHT = 500
WIDTH = 800
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Resolution")
self.set_border_width(0)
self.set_default_size(WIDTH, HEIGHT)
hb = Gtk.HeaderBar()
hb.props.show_close_button = True
hb.props.title = "Resolution"
self.set_titlebar(hb)
button = Gtk.Button()
icon = Gio.ThemedIcon(name="emblem-system-symbolic")
image = Gtk.Image.new_from_gicon(icon, 1)
button.add(image)
button.connect("clicked", self.sidebarShowHide)
button.set_focus_on_click(False)
hb.pack_start(button)
sidebarbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
toplevelbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
self.add(toplevelbox)
self.sidebar = Gtk.Box()
toplevelbox.pack_start(self.sidebar, False, False, 0)
self.sidebar.add(sidebarbox)
self.searchentry = Gtk.SearchEntry()
self.searchentry.connect("search-changed", self.search_changed)
sidebarbox.pack_start(self.searchentry, False, False, 0)
label = Gtk.Label("Contents Selector")
sidebarbox.pack_start(label, True, True, 0)
scroller = Gtk.ScrolledWindow()
content = WebKit.WebView()
scroller.add(content)
toplevelbox.pack_start(scroller, True, True, 0)
content.open("/home/oliver/resolution/placeholder.html")
def sidebarShowHide(self, button):
if self.sidebar.get_visible():
self.sidebar.hide ()
else:
self.sidebar.show ()
def search_changed(self, searchentry):
pass
win = MainWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
This smells like a bug.
As a workaround, you could use
def sidebarShowHide(self, button):
self.sidebarbox.set_visible(not self.sidebarbox.get_visible())
but this does not yield any transition animation, but does remove its allocated space for the time being invisible.
Actually the C demo provided within the git repo resizes as expected, so this might in fact have to do something with the child widgets prefered orientation.

Categories

Resources