GTK ComboBox has style property "arrow-size" (link). I want to set it to 0.
Unfortunately next snippet doesn't work. No error messages reported and arrow appears in default size (=15)
import pygtk
pygtk.require('2.0')
import gtk
def the_dialog():
dialog = gtk.Dialog("Title", None, gtk.DIALOG_MODAL)
liststore = gtk.ListStore(str)
for a in ["one","two","three"]:
liststore.append([a])
rc_str = """
style 'no_arrow_style' {
GtkComboBox::arrow-size = 0
}
widget_class '*' style 'no_arrow_style'
"""
gtk.rc_parse_string(rc_str)
combo_box = gtk.ComboBox()
cell = gtk.CellRendererText()
combo_box.pack_start(cell)
combo_box.add_attribute(cell, 'text', 0)
combo_box.set_model(liststore)
combo_box.get_cells()
dialog.vbox.pack_start(combo_box)
dialog.show_all()
dialog.run()
the_dialog()
Combo box with default "v"-shaped arrow:
GtkComboBox::arrow-size actually means "minimum arrow size". To see the difference set it to 100. The example snippet did work.
Related
I am trying to bound an interactive box into a numerical value only (something similar to the BoundedFloatText object). This is an example of my code - an interactive box with a button. I want this box to be limited to numerical values only. Does anyone know if it is possible?
import ipywidgets as widgets
from ipywidgets import interactive, interact_manual, Layout, Button, ButtonStyle
def diff(MeaP):
MeaP = float(MeaP);
diff = abs(MeaP)
print(f"Difference in the population = {round(diff,7)}")
style = {'description_width': 'initial'}
Z2 = interactive(diff, {'manual': True}, MeaP="0")
Z2.children[0].layout = Layout(width='200px', padding = "0px 25px 0px 0px")
Z2.children[0].style = style
Z2.children[0].description = "<b> Difference </b>"
Z2.children[1].style.button_color="lightblue"
Z2.children[1].description="Click the button"
Z2.children[1].style.font_weight = "bold"
Z2.children[1].layout = Layout(width='250px')
display(Z2)
The challenge:
How can you change the color for backround, font etc for widgets.SelectMultiple() and other widgets for that matter? Here's a simple setup for widgets.SelectMultiple()
Snippet / Cell 1:
# settings
%matplotlib inline
# imports
from ipywidgets import interactive, Layout
from IPython.display import clear_output
import ipywidgets as widgets
from IPython.display import display
# widget 1
wdg = widgets.SelectMultiple(
options=['Apples', 'Oranges', 'Pears'],
value=['Oranges'],
#rows=10,
description='Fruits',
disabled=False
)
display(wdg)
Widget 1:
What I've tried:
I thought i was onto something with Layout and style and was hoping the following setup with layout=Layout(width='75%', height='80px') would let me change colors somehow as well and not only width and height:
Snippet / Cell 2:
wdg2 = widgets.SelectMultiple(
options=['Apples', 'Oranges', 'Pears'],
value=['Oranges'],
description='Fruits',
layout=Layout(width='75%', height='80px'),
disabled=False
)
display(wdg2)
Widget2:
But to my huge disappointment it seems that you can't change colors in a similar way. According to the ipywidgets docs, properties of the style attribute are specific to each widget type. You can get a list of the style attributes for a widget with the keys property. And wdg2.style.keys returns this:
['_model_module',
'_model_module_version',
'_model_name',
'_view_count',
'_view_module',
'_view_module_version',
'_view_name',
'description_width']
And since there are noe color attributes there, is it impossible to change the colors for widgets.SelectMultiple()? For other widgets, like Button, you'll find an attribute button_color as well.
The short answer is: You can't do that without creating your own "custom widget".
Those attributes of style and layout objects are hard-coded in both the server-side and client-side libraries of ipywidgets.
There is a dirty way to get a similar effect though, by mixing the ButtonStyle with SelectMultiple.
# Tested on JupyterLab 0.35.3 with Python 3.6 kernel
import ipywidgets as widgets
from ipywidgets.widgets import widget_serialization, trait_types
from traitlets import Unicode, Instance, CaselessStrEnum
class MySelectMultiple(widgets.SelectMultiple):
style=trait_types.InstanceDict(widgets.ButtonStyle).tag(sync=True, **widget_serialization)
wdg2 = MySelectMultiple(
options=['Apples', 'Oranges', 'Pears'],
value=['Oranges'],
description='Fruits',
layout=widgets.Layout(width='75%', height='80px'),
style= {'button_color':'red'},
disabled=False
)
wdg2
wdg2.style.button_color = 'green'
Another dirty way is to inject a CSS rule into the notebook which affects all select widget.
%%html
<style>
.widget-select > select {background-color: red;}
</style>
Custom widget
The ultimate solution is to make your own custom widget.
Unfortunately you need to write both server- and client side codes for it.
For classical jupyter notebook, the client side code (JavaScript) can be put in a cell.
But this feature may be dropped in the "next-generation" of Jupyter, i.e. JupyterLab, for security reasons.
Cell 1
%%javascript
require.undef('myselectmultiple');
define('myselectmultiple', ["#jupyter-widgets/base"], function(widgets) {
class selectmultipleView extends widgets.SelectMultipleView {
render () {
super.render();
this.mycolor_changed();
this.model.on('change:mycolor', this.mycolor_changed, this);
}
mycolor_changed () {
var mycolor = this.model.get('mycolor')
this.el.childNodes[1].style.backgroundColor = mycolor;
}
}
return {
myselectmultipleview : selectmultipleView
};
});
Cell 2
class MySelectMultipleC(widgets.SelectMultiple):
_view_name = Unicode('myselectmultipleview').tag(sync=True)
_view_module = Unicode('myselectmultiple').tag(sync=True)
_view_module_version = Unicode('0.1.0').tag(sync=True)
mycolor = Unicode('white', help='background color').tag(sync=True)
wdg3 = MySelectMultipleC(
options=['Apples', 'Oranges', 'Pears'],
value=['Oranges'],
description='Fruits',
mycolor = 'green',
disabled=False
)
wdg3
Cell 3
wdg3.mycolor = 'red'
JupyterLab uses a completely different framework. To make the above custom widget working in the "Lab" interface, the client-side code should be translated to TypeScript, and then be compiled, built and installed on the Lab server.
Late to the party, but here is my simple solution, for the case where the color will be used to encode simple two (or a number of) states: use unicode!
sample:
code (in python 3... :) )
from ipywidgets import interactive, Layout
from IPython.display import clear_output
import ipywidgets as widgets
from IPython.display import display
c_base = int("1F534",base=16)
# widget 1
options=['Apples', 'Oranges', 'Pears']
state = [False,True,True]
colored_options = ['{} {}'.format(chr(c_base+s), o) for s,o in zip(state,options)]
wdg = widgets.SelectMultiple(
options=colored_options,
description='Fruits',
disabled=False
)
display(wdg)
Try searching with this code if you need more colours...:
for i in range (10):
ii = int('0x1f7e0',base=16)+i
print('{:>15}'.format('[{}: {}] '.format(hex(ii),chr(ii))),end='')
if i%7==6:
print()
jp_proxy_widget makes it easy to do anything you can do in HTML5/javascript in a widget. For example here is a colorized multiple select:
Find it here: https://github.com/AaronWatters/jp_proxy_widget
How to justify the values listed in drop-down part of a ttk.Combobox? I have tried justify='center' but that seems to only configure the selected item. Could use a resource link too if there is, I couldn't find it.
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
import tkinter.ttk as ttk
except ImportError:
import Tkinter as tk
import ttk
if __name__ == '__main__':
root = tk.Tk()
cbb = ttk.Combobox(root, justify='center', values=(0, 1, 2))
cbb.pack()
root.mainloop()
I have a one-liner solution. Use the .option_add() method after declaring ttk.Combobox. Example:
cbb = ttk.Combobox(root, justify='center', values=(0, 1, 2)) # original
cbb.option_add('*TCombobox*Listbox.Justify', 'center') # new line added
Here's one pure Python way that gets close to what you want. The items in the dropdown list all get justified to fit within the Combobox's width (or a default value will be used).
Update
Part of the reason the initial version of my answer wasn't quite right was because the code assumed that a fixed-width font was being used. That's not the case on my test platform at least, so I've modified the code to actually measure the width of values in pixels instead of whole characters, and do essentially what it did originally, but in those units of string-length measure.
import tkinter as tk
import tkinter.font as tkFont
from tkinter import ttk
class CenteredCombobox(ttk.Combobox):
DEFAULT_WIDTH = 20 # Have read that 20 is the default width of an Entry.
def __init__(self, master=None, **kwargs):
values = kwargs.get('values')
if values:
entry = ttk.Entry(None) # Throwaway for getting the default font.
font = tkFont.Font(font=entry['font'])
space_width = font.measure(' ')
entry_width = space_width * kwargs.get('width', self.DEFAULT_WIDTH)
widths = [font.measure(str(value)) for value in values]
longest = max(entry_width, *widths)
justified_values = []
for value, value_width in zip(values, widths):
space_needed = (longest-value_width) / 2
spaces_needed = int(space_needed / space_width)
padding = ' ' * spaces_needed
justified_values.append(padding + str(value))
kwargs['values'] = tuple(justified_values)
super().__init__(master, **kwargs)
root = tk.Tk()
ccb = CenteredCombobox(root, justify='center', width=10, values=('I', 'XLII', 'MMXVIII'))
ccb.pack()
root.mainloop()
(Edit: Note that this solution works for Tcl/Tk versions 8.6.5 and above. #CommonSense notes that some tkinter installations may not be patched yet,
and this solution will not work).
In Tcl ( I don't know python, so one of the python people can edit the question).
A combobox is an amalgamation of an 'entry' widget and a 'listbox' widget. Sometimes to make the configuration changes you want, you need to access the internal widgets directly.
Tcl:
% ttk::combobox .cb -values [list a abc def14 kjsdf]
.cb
% pack .cb
% set pd [ttk::combobox::PopdownWindow .cb]
.cb.popdown
% set lb $pd.f.l
.cb.popdown.f.l
% $lb configure -justify center
Python:
cb = ttk.Combobox(value=['a', 'abc', 'def14', 'kjsdf'])
cb.pack()
pd = cb.tk.call('ttk::combobox::PopdownWindow', cb)
lb = cb.tk.eval('return {}.f.l'.format(pd))
cb.tk.eval('{} configure -justify center'.format(lb))
Some caveats. The internals of ttk::combobox are subject to change.
Not likely, not anytime soon, but in the future, the hard-coded .f.l
could change.
ttk::combobox::PopdownWindow will force the creation of the listbox when it is called. A better method is to put the centering adjustment into
a procedure and call that procedure when the combobox/listbox is mapped.
This will run for all comboboxes, you will need to check the argument
in the proc to make sure that this is the combobox you want to adjust.
proc cblbhandler { w } {
if { $w eq ".cb" } {
set pd [ttk::combobox::PopdownWindow $w]
set lb $pd.f.l
$lb configure -justify center
}
}
bind ComboboxListbox <Map> +[list ::cblbhandler %W]
After digging through combobox.tcl source code I've come up with the following subclass of ttk.Combobox. JustifiedCombobox justifies the pop-down list's items almost precisely after1 pop-down list's been first created & customized and then displayed. After the pop-down list's been created, setting self.justify value to a valid one will again, customize the justification almost right after the pop-down list's first been displayed. Enjoy:
try: # In order to be able to import tkinter for
import tkinter as tk # either in python 2 or in python 3
from tkinter import ttk
except:
import Tkinter as tk
import ttk
class JustifiedCombobox(ttk.Combobox):
"""
Creates a ttk.Combobox widget with its drop-down list items
justified with self.justify as late as possible.
"""
def __init__(self, master, *args, **kwargs):
ttk.Combobox.__init__(self, master, *args, **kwargs)
self.justify = 'center'
def _justify_popdown_list_text(self):
self._initial_bindtags = self.bindtags()
_bindtags = list(self._initial_bindtags)
_index_of_class_tag = _bindtags.index(self.winfo_class())
# This dummy tag needs to be unique per object, and also needs
# to be not equal to str(object)
self._dummy_tag = '_' + str(self)
_bindtags.insert(_index_of_class_tag + 1, self._dummy_tag)
self.bindtags(tuple(_bindtags))
_events_that_produce_popdown = tuple([ '<KeyPress-Down>',
'<ButtonPress-1>',
'<Shift-ButtonPress-1>',
'<Double-ButtonPress-1>',
'<Triple-ButtonPress-1>',
])
for _event_name in _events_that_produce_popdown:
self.bind_class(self._dummy_tag, _event_name,
self._initial_event_handle)
def _initial_event_handle(self, event):
_instate = str(self['state'])
if _instate != 'disabled':
if event.keysym == 'Down':
self._justify()
else:
_ = self.tk.eval('{} identify element {} {}'.format(self,
event.x, event.y))
__ = self.tk.eval('string match *textarea {}'.format(_))
_is_click_in_entry = bool(int(__))
if (_instate == 'readonly') or (not _is_click_in_entry):
self._justify()
def _justify(self):
self.tk.eval('{}.popdown.f.l configure -justify {}'.format(self,
self.justify))
self.bindtags(self._initial_bindtags)
def __setattr__(self, name, value):
self.__dict__[name] = value
if name == 'justify':
self._justify_popdown_list_text()
def select_handle():
global a
_selected = a['values'][a.current()]
if _selected in ("left", "center", "right"):
a.justify = _selected
if __name__ == '__main__':
root = tk.Tk()
for s in ('normal', 'readonly', 'disabled'):
JustifiedCombobox(root, state=s, values=[1, 2, 3]).grid()
a = JustifiedCombobox(root, values=["Justify me!", "left", "center", "right"])
a.current(0)
a.grid()
a.bind("<<ComboboxSelected>>", lambda event: select_handle())
root.mainloop()
1 It basically makes use of bindtag event queue. This was mostly possible thanks to being able to creating a custom bindtag.
I keep getting an error AttributeError: 'BigText' object has no attribute 'rows' when trying to have a BigText at the top of a ListBox. I understand that the BigText is a "fixed" widget, while ListBox expects a "flow" widget, but I can't seem to get my program to take the BigText no matter what I try. Here's an exhaustive example of what I've attempted:
head_title = urwid.BigText(('banner', u'Header'), urwid.HalfBlock5x4Font())
head = urwid.Filler(head_title)
# head = urwid.AttrMap(head, 'banner')
# head = urwid.AttrMap(head, 'streak')
head = urwid.BoxAdapter(head, 3)
print head
# this gives me `<BoxAdapter flow widget <Filler box widget <BigText fixed widget>> height=3>`
body = [head, urwid.Divider()]
return urwid.ListBox(body)
Thanks!
BigText is a of 'fixed' sizing. That means that both the width and the height of the widget is defined by the widget. ListBox only accepts widgets of 'flow' sizing. This means that the width will be decided by the container (in this case the ListBox). So, you have to first convert he 'fixed' widget to a 'flow' widget. This can be done with the Padding decoration widget by setting the width property to 'clip'.
See here for a full example:
import urwid
def show_or_exit(key):
if key in ('q', 'Q'):
raise urwid.ExitMainLoop()
return key
head = urwid.ListBox(urwid.SimpleFocusListWalker([
urwid.Padding(
urwid.BigText(('banner', "Hello world"), urwid.HalfBlock5x4Font()),
width='clip')
]))
loop = urwid.MainLoop(head, unhandled_input=show_or_exit)
loop.run()
I have the following code in pygtk:
....
rendererText = gtk.CellRendererText()
self.columns["hour"] = gtk.TreeViewColumn("Uur", rendererText, text=0)
self.columns["hour"].set_sort_column_id(0)
self.treeview.append_column(self.columns["hour"])
self.columnControls["ond"] = gtk.CellRendererToggle()
self.columns["ond"] = gtk.TreeViewColumn("ond", self.columnControls["ond"], active=1)
self.columns["ond"].set_sort_column_id(1)
self.treeview.append_column(self.columns["ond"])
....
So, I'd personally expect that if I click the checkbox that appears in the column entrys would be "togglable", but it isn't. Is this because of my OS (Mac OS X 10.8), or is there some kind of property I forgot to set?
You have to bind a callback function, which may reverse the value in that GtkListStore/GtkTreeStore, to toggled signal. Like this:
def on_cellrenderertoggle_toggled(self, cellrenderertoggle, path):
# self.liststore is the tree modal of that treeview
self.liststore[path][1] = not self.liststore[path][1]
A full tutorial can be found here: http://python-gtk-3-tutorial.readthedocs.org/en/latest/cellrenderers.html#cellrenderertoggle