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()
Related
I am building a wx.ComboCtrl with a wx.ListCtrl attached. The reason for doing this is because I want to set the foreground colour of the choices (the colour shows the user the status of the item). I want these colours to show when the box is dropped down and when a user has made a selection.
The problem I run into is that on Linux (Ubuntu 20.04), after a selection was made, the background colour of the wx.ComboCtrl remains blue (and the foreground colour remains white), even if I move focus to another widget. It doesn't matter which colour I set for the text to be displayed on the ComboCtrl, it remains white text with a blue background. See screenshot.
I can only get it to show me the default (gray) background with my selected foreground colour if I move the focus to another window and then back to my own window.
In Windows this doesn't happen: after selecting an item, the background colour of the ComboCtrl is default (gray), however it does show a little dotted line around the selection. See screenshot.
Here is the modified demo code that I am using to reproduce the issue. The comments in the code are left overs from some of the things I tried.
#!/usr/bin/env python
import wx
import os
#----------------------------------------------------------------------
#----------------------------------------------------------------------
# This class is used to provide an interface between a ComboCtrl and the
# ListCtrl that is used as the popoup for the combo widget.
class ListCtrlComboPopup(wx.ComboPopup):
def __init__(self):
wx.ComboPopup.__init__(self)
self.lc = None
def AddItem(self, txt, _colour):
self.lc.InsertItem(self.lc.GetItemCount(), txt)
_entry = self.lc.GetItem(self.lc.GetItemCount() - 1)
_entry.SetTextColour(_colour)
#_entry.SetItemTextColour(_colour)
self.lc.SetItem(_entry)
def OnMotion(self, evt):
item, flags = self.lc.HitTest(evt.GetPosition())
if item >= 0:
self.lc.Select(item)
self.curitem = item
def OnLeftDown(self, evt):
self.value = self.curitem
self.Dismiss()
# The following methods are those that are overridable from the
# ComboPopup base class. Most of them are not required, but all
# are shown here for demonstration purposes.
# This is called immediately after construction finishes. You can
# use self.GetCombo if needed to get to the ComboCtrl instance.
def Init(self):
self.value = -1
self.curitem = -1
# Create the popup child control. Return true for success.
def Create(self, parent):
self.lc = wx.ListCtrl(parent, style=wx.LC_SINGLE_SEL | wx.SIMPLE_BORDER | wx.LC_REPORT | wx.LC_NO_HEADER)
self.lc.InsertColumn(0, '')
self.lc.Bind(wx.EVT_MOTION, self.OnMotion)
self.lc.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
return True
# Return the widget that is to be used for the popup
def GetControl(self):
return self.lc
# Called just prior to displaying the popup, you can use it to
# 'select' the current item.
def SetStringValue(self, val):
idx = self.lc.FindItem(-1, val)
if idx != wx.NOT_FOUND:
self.lc.Select(idx)
# Return a string representation of the current item.
def GetStringValue(self):
if self.value >= 0:
return self.lc.GetItemText(self.value)
return ""
# Called immediately after the popup is shown
def OnPopup(self):
wx.ComboPopup.OnPopup(self)
# Called when popup is dismissed
def OnDismiss(self):
print (self.GetStringValue())
wx.ComboPopup.OnDismiss(self)
# This is called to custom paint in the combo control itself
# (ie. not the popup). Default implementation draws value as
# string.
def PaintComboControl(self, dc, rect):
wx.ComboPopup.PaintComboControl(self, dc, rect)
# Receives key events from the parent ComboCtrl. Events not
# handled should be skipped, as usual.
def OnComboKeyEvent(self, event):
wx.ComboPopup.OnComboKeyEvent(self, event)
# Implement if you need to support special action when user
# double-clicks on the parent wxComboCtrl.
def OnComboDoubleClick(self):
wx.ComboPopup.OnComboDoubleClick(self)
# Return final size of popup. Called on every popup, just prior to OnPopup.
# minWidth = preferred minimum width for window
# prefHeight = preferred height. Only applies if > 0,
# maxHeight = max height for window, as limited by screen size
# and should only be rounded down, if necessary.
def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
return wx.ComboPopup.GetAdjustedSize(self, minWidth, prefHeight, maxHeight)
# Return true if you want delay the call to Create until the popup
# is shown for the first time. It is more efficient, but note that
# it is often more convenient to have the control created
# immediately.
# Default returns false.
def LazyCreate(self):
return wx.ComboPopup.LazyCreate(self)
#----------------------------------------------------------------------
class MyTestPanel(wx.Panel):
def __init__(self, parent, log):
self.log = log
wx.Panel.__init__(self, parent, -1)
txt = wx.TextCtrl(self, wx.ID_ANY, pos=(100,100))
comboCtrl = wx.ComboCtrl(self, wx.ID_ANY, "Third item", (10,10), size=(200,-1), style=wx.CB_READONLY)
popupCtrl = ListCtrlComboPopup()
# It is important to call SetPopupControl() as soon as possible
comboCtrl.SetPopupControl(popupCtrl)
# Populate using wx.ListView methods
popupCtrl.AddItem("First Item", [255, 127, 0])
popupCtrl.AddItem("Second Item", [192, 127, 45])
popupCtrl.AddItem("Third Item", [25, 223, 172])
#popupCtrl.GetAdjustedSize(100, 35, 100)
#comboCtrl.SetTextColour(_colour)
comboCtrl.SetForegroundColour(wx.Colour(235, 55, 55))
#----------------------------------------------------------------------
def runTest(frame, nb, log):
win = MyTestPanel(nb, log)
return win
#----------------------------------------------------------------------
overview = """<html><body>
<h2><center>wx.combo.ComboCtrl</center></h2>
A combo control is a generic combobox that allows a totally custom
popup. In addition it has other customization features. For instance,
position and size of the dropdown button can be changed.
</body></html>
"""
if __name__ == '__main__':
import sys,os
import run
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
Question 1:
How can I make it so that once an item has been selected the appropriate text colour (the one I programmatically set) and default (gray) background colour is shown.
Question 2:
When dropping down the ComboCtrl, it is showing the ListCtrl, which has a single column only. You can see that the "Second item" on the list is not displayed entirely because the column is too narrow. How can I make it so that the column is always the same width as the widget itself, even when the ComboCtrl resizes (as a result of resizing the parent window)?
Question 3:
Not overly important, but while we are on the subject: is there a way to get rid of the little dotted box that is shown around the selected item when running this in Windows?
In advance, thank you very much for your thoughts and ideas on this.
Marc.
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.
In my application, I have about 500 buttons which all update their labels and colors when specific actions are taken. I was running into crashes and performance issues when I noticed (by using cProfile and pdb) that the problem was caused by changing the button color:
self.button.modify_bg(gtk.STATE_PRELIGHT, color)
self.button.modify_bg(gtk.STATE_NORMAL, color)
500 calls like this need an eternity of 5 seconds (which also freezes GUI) and it gets even slower the longer the application runs. In case someone wonders, I have a powerful processor and lots of free memory.
Previously I was trying to use EventBox as recommended in the docs. However this only changes the color behind the button, not on its surface:
import gtk
win = gtk.Window()
win.connect("destroy", gtk.main_quit)
btn = gtk.Button("test")
eb = gtk.EventBox()
eb.add(btn)
eb.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse("red"))
win.add(eb)
win.show_all()
gtk.main()
Result:
I also tried the alternative which involves retrieving and modifying the the style. This led to the same slowness as with modify_bg. In addition I also got random crashes at random places, usually with low level memory allocation errors such as double freeing from gtk.
import gtk
win = gtk.Window()
win.connect("destroy", gtk.main_quit)
btn = gtk.Button("test")
#copy the current style and replace the background
style = btn.get_style().copy()
style.bg[gtk.STATE_NORMAL] = gtk.gdk.color_parse("red")
#set the button's style to the one you created
btn.set_style(style)
win.add(btn)
win.show_all()
gtk.main()
It seems that the color of the button is managed by the operating system and I can't find a way around it without slowness, crashes or undesired results. I badly need to convey by color important information about the button.
So how do I change the button color properly?
I ended up implementing my own Button by using a gtk.EventBox which holds a gtk.Label inside of its widget tree. Unlike with buttons, setting label color seems not to conflict with the operating system.
I also implemented a couple of convenience functions such as set_label()
modify_bg is still too slow, but it doesn't lead to crashes. By checking if current color is the same as the one I want to set, I also saved a lot of computation time for buttons that don't change.
My code is very sketchy but it works for my purposes. Feel free to make it more robust and/or flexible:
import gtk
class ColoredButton(gtk.EventBox):
'''
This class implements a simple unanimated button
whose color can be changed
'''
def __init__(self, widget = gtk.Label()):
'''
widget must be a gtk.Label
this is not checked in this simple version
'''
#initialize superclass EventBox
super(ColoredButton, self).__init__()
#embed widget inside vbox and hbox
self.widget = widget
self.vbox = gtk.VBox(homogeneous=False, spacing=0)
self.hbox = gtk.HBox(homogeneous=False, spacing=0)
self.hbox.pack_start(self.vbox, expand = True, fill=False)
self.vbox.pack_start(self.widget, expand = True, fill = False)
#draws a frame around the entire construct to make everything look more like a button
self.frame = gtk.Frame()
self.frame.add(self.hbox)
#add the final "button" to this EventBox in order to handle events
self.add(self.frame)
#define which events should be reacted to, those constants can be found in pygtk docs
self.add_events(gtk.gdk.BUTTON_RELEASE_MASK)
self.add_events(gtk.gdk.BUTTON_PRESS_MASK)
self.add_events(gtk.gdk.ENTER_NOTIFY_MASK)
self.add_events(gtk.gdk.LEAVE_NOTIFY_MASK)
#activate focus
self.set_can_focus(True)
#align the "button" text in the middle of the box
self.widget.set_alignment(xalign=0.5, yalign=0.5)
def show(self):
super(ColoredButton, self).show()
self.hbox.show()
self.vbox.show()
self.frame.show()
self.widget.show()
def set_label(self, label):
self.set_text(label)
def set_text(self, text):
self.widget.set_text(text)
def changeColor(self, color, state = gtk.STATE_NORMAL):
if color is not None:
currentcolor = self.style.bg[state]
#too lazy to look up in docs if color != currentcolor also works
if color.red != currentcolor.red or color.green != currentcolor.green or color.blue != currentcolor.blue:
self.modify_bg(state, color)
def changeTextColor(self, color, state = gtk.STATE_NORMAL):
if color is not None:
currentcolor = self.style.bg[state]
if color.red != currentcolor.red or color.green != currentcolor.green or color.blue != currentcolor.blue:
self.widget.modify_fg(gtk.STATE_NORMAL, color)
def onButtonClick(widget, event = None):
if event.button == 1:
widget.set_label("left click")
elif event.button == 2:
widget.set_label("middle click")
elif event.button == 3:
widget.set_label("right click")
import gtk
w = gtk.Window()
w.connect('destroy', gtk.main_quit)
coloredbutton=ColoredButton(widget = gtk.Label("Hello there"))
coloredbutton.changeColor(gtk.gdk.color_parse("black"))
coloredbutton.changeTextColor(gtk.gdk.color_parse("yellow"))
coloredbutton.set_size_request(width = 100, height = 50)
coloredbutton.connect("button-release-event", onButtonClick)
w.add(coloredbutton)
w.show_all()
gtk.main()
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())
How could I have a scrollbar inside a gtk.Layout.
For example, in my code I have:
import pygtk
pygtk.require('2.0')
import gtk
class ScrolledWindowExample:
def __init__(self):
self.window = gtk.Dialog()
self.window.connect("destroy", self.destroy)
self.window.set_size_request(300, 300)
self.scrolled_window = gtk.ScrolledWindow()
self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
self.window.vbox.pack_start(self.scrolled_window, True, True, 0)
self.layout = gtk.Layout()
self.scrolled_window.add(self.layout)
self.current_pos = 0
self.add_buttom()
self.window.show_all()
def add_buttom(self, widget = None):
title = str(self.current_pos)
button = gtk.ToggleButton(title)
button.connect_object("clicked", self.add_buttom, None)
self.layout.put(button, self.current_pos, self.current_pos)
button.show()
self.current_pos += 20
def destroy(self, widget):
gtk.main_quit()
if __name__ == "__main__":
ScrolledWindowExample()
gtk.main()
What I really want is to find some way to make the scroll dynamic. See the example that I put above, when you click any button, another button will be added. But the scrollbar doesn't work.
What can I do to get the scroll bars working?
Does it works if you either use gtk.Window() instead of gtk.Dialog(); or execute self.window.run() after self.window.show_all()?
The difference between Dialog and common Window is that Dialog has its own loop which processes events. As you do not run its run() command, this loop never gets the chance to catch the events, so ScrolledWindow does not receives them, and does not change its size.