Shallow Thoughts : tags : gtk
Akkana's Musings on Open Source Computing and Technology, Science, and Nature.
Wed, 05 Jun 2019
Lately I've been running with my default python set to Python 3.
Debian still uses Python 2 as the default, which is reasonable, but
adding a ~/bin/python symlink to /usr/bin/python3
helps me preview scripts that might become a problem once Debian
does switch. I thought I had converted most of my Python scripts to
Python 3 already, but this link is catching some I didn't convert.
Python has a nice script called 2to3 that can convert the bulk
of most scripts with little fanfare.
The biggest hassles that 2to3 can't handle are network related
(urllib and urllib2) and, the big one, user interfaces.
PyGTK, based on GTK2 has no Python 3 equivalent; in Python 3,
the only option is to use GObject Introspection (gi) and
GTK3. Since there's almost no documentation on python-gi and gtk3,
converting a GTK script always involves a lot of fumbling and guesswork.
A few days ago I tried to play an MP3 in my little
musicplayer.py
script and discovered I'd never updated it. I have enough gi/GTK3 scripts
by now that I thought something with such a simple user interface
would be easy. Shows how much I know about GTK3!
I got the basic window ported pretty easily, but it looked terrible:
huge margins everywhere, and no styling on the text, like the bold,
large-sized text I had previously use to highlight the name of the
currently playing song. I tried various approaches, but a lot of the
old methods of styling have been deprecated in GTK3; you're supposed to
use CSS. Except, of course, there's no documentation on it, and it turns
out the CSS accepted by GTK3 is a tiny subset of the CSS you can use in
HTML pages, but what the subset is doesn't seem to be documented anywhere.
How to Apply a Stylesheet
The first task was to get any CSS at all working.
The
GNOME Journal: Styling GTK with CSS
was helpful in getting started, but had a lot of information that
doesn't work (perhaps it did once). At least it gave me this basic snippet:
css = '* { background-color: #f00; }'
css_provider = gtk.CssProvider()
css_provider.load_from_data(css)
context = gtk.StyleContext()
screen = Gdk.Screen.get_default()
context.add_provider_for_screen(screen, css_provider,
gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
Built-in Class Names
Great! if all you want to do is turn the whole app red.
But in reality, you'll want to style different widgets differently.
At least some classes have class names:
css = 'button { background-color: #f00; }'
I found other pages suggesting using
'GtkButton in CSS,
but that didn't work for me. How do you find the right class names?
No idea, I never found a reference for that. Just guess, I guess.
User-set Class Names
What about classes -- for instance, make all the buttons in a ButtonBox white?
You can add classes this way:
button_context = button.get_style_context()
button_context.add_class("whitebutton")
If you need to change a class (for instance, turn a red button green),
first remove the old class:
button_context = button.get_style_context()
entry_style_context.remove_class("red")
Widget Names, like CSS ID
For single widgets, you can give the widget a name and
use it like an ID in CSS. Like this:
label = gtk.Label()
label.set_use_markup(True)
label.set_line_wrap(True)
label.set_name("red_label")
mainbox.pack_start(label, False, False, 0)
css = '#red_label { background-color: #f00; }'
[ ... ]
Properties You Can Set
There is, amazingly, a page on
which
CSS properties GTK3 supports.
That page doesn't mention it, but some properties like :hover
are also supported. So you can write CSS tweaks like
.button { border-radius: 15; border-width: 2; border-style: outset; }
.button:hover { background: #dff; border-color: #8bb; }
And descendants work, so you can say somthing like
buttonbox = gtk.ButtonBox(spacing=4)
buttonbox.set_name("buttonbox")
mainbox.pack_end(buttonbox, False, False, 0)
btn = gtk.Button(label="A")
buttonbox.add(btn)
btn = gtk.Button(label="B")
buttonbox.add(btn)
and then use CSS that affects all the buttons inside the buttonbox:
#buttonbox button { color: red; }
No mixed CSS Inside Labels
My biggest disappointment was that I couldn't mix styles inside a label.
You can't do something like
label.set_label('Headline'
'Normal text')
and expect to style the different parts separately.
You can use very simple markup like
<b>bold</b> normal
,
but anything further gives errors like
"error parsing markup: Attribute 'class' is not allowed on the
<span> tag" (you'll get the same error if you try "id").
I had to make separate GtkLabels for each text size and style I wanted,
which is a lot more work. If you wanted to mix styles and have them
reflow as the content length changed, I don't know how (or if)
you could do it.
Fortunately, I don't strictly need that for this little app.
So for now, I'm happy to have gotten this much working.
Tags: programming, python, gtk, gtk3
[
14:49 Jun 05, 2019
More programming |
permalink to this entry |
]
Sun, 24 Dec 2017
Dave and I will be giving a planetarium talk in February
on the analemma and related matters.
Our planetarium, which runs a fiddly and rather limited program called
Nightshade, has no way of showing the analemma. Or at least, after
trying for nearly a week once, I couldn't find a way. But it can
show images, and since I once wrote a
Python
program to plot the analemma, I figured I could use my program
to generate the analemmas I wanted to show and then project them
as images onto the planetarium dome.
But naturally, I wanted to project just the analemma and
associated labels; I didn't want the blue background to
cover up the stars the planetarium shows. So I couldn't just use
a simple screenshot; I needed a way to get my GTK app to create a
transparent image such as a PNG.
That turns out to be hard. GTK can't do it (either GTK2 or GTK3),
and people wanting to do anything with transparency are nudged toward
the Cairo library. As a first step, I updated my analemma program to
use Cairo and GTK3 via gi.repository. Then I dove into Cairo.
I found one C solution for
converting
an existing Cairo surface to a PNG, but I didn't have much luck
with it. But I did find a
Python
program that draws to a PNG without bothering to create a GUI.
I could use that.
The important part of that program is where it creates a new Cairo
"surface", and then creates a "context" for that surface:
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, *imagesize)
cr = cairo.Context(surface)
A Cairo surface is like a canvas to draw on, and it knows how to
save itself to a PNG image.
A context is the equivalent of a GC in X11 programming:
it knows about the current color, font and so forth.
So the trick is to create a new surface, create a context,
then draw everything all over again with the new context and surface.
A Cairo widget will already have a function to draw everything
(in my case, the analemma and all its labels), with this signature:
def draw(self, widget, ctx):
It already allows passing the context in, so passing in a different
context is no problem. I added an argument specifying the background
color and transparency, so I could use a blue background in the user
interface but a transparent background for the PNG image:
def draw(self, widget, ctx, background=None):
I also had a minor hitch: in draw(), I was saving the context as
self.ctx rather than passing it around to every draw routine.
That means calling it with the saved image's context would overwrite
the one used for the GUI window. So I save it first.
Here's the final image saving code:
def save_image(self, outfile):
dst_surface = cairo.ImageSurface(cairo.FORMAT_ARGB32,
self.width, self.height)
dst_ctx = cairo.Context(dst_surface)
# draw() will overwrite self.ctx, so save it first:
save_ctx = self.ctx
# Draw everything again to the new context,
# with a transparent instead of an opaque background:
self.draw(None, dst_ctx, (0, 0, 1, 0)) # transparent blue
# Restore the GUI context:
self.ctx = save_ctx
dst_surface.write_to_png("example.png")
print("Saved to", outfile)
Tags: programming, python, gtk, gtk3, graphics, analemma
[
19:39 Dec 24, 2017
More programming |
permalink to this entry |
]
Sat, 06 Aug 2016
I have a little browser script in Python, called
quickbrowse,
based on Python-Webkit-GTK. I use it for things like quickly calling
up an anonymous window with full javascript and cookies, for when I
hit a page that doesn't work with Firefox and privacy blocking;
and as a quick solution for calling up HTML conversions of doc and pdf
email attachments.
Python-webkit comes with a simple browser as an example -- on Debian
it's installed in /usr/share/doc/python-webkit/examples/browser.py.
But it's very minimal, and lacks important basic features like
command-line arguments. One of those basic features I've been meaning
to add is Back and Forward buttons.
Should be easy, right? Of course webkit has a go_back() method, so
I just have to add a button and call that, right? Ha. It turned out to
be a lot more difficult than I expected, and although I found a fair
number of pages asking about it, I didn't find many working examples.
So here's how to do it.
Add a toolbar button
In the WebToolbar class (derived from gtk.Toolbar):
In __init__()
, after initializing the parent class and
before creating the location text entry (assuming you want your
buttons left of the location bar), create the two buttons:
backButton = gtk.ToolButton(gtk.STOCK_GO_BACK)
backButton.connect("clicked", self.back_cb)
self.insert(backButton, -1)
backButton.show()
forwardButton = gtk.ToolButton(gtk.STOCK_GO_FORWARD)
forwardButton.connect("clicked", self.forward_cb)
self.insert(forwardButton, -1)
forwardButton.show()
Now create those callbacks you just referenced:
def back_cb(self, w):
self.emit("go-back-requested")
def forward_cb(self, w):
self.emit("go-forward-requested")
That's right, you can't just call go_back on the web view, because
GtkToolbar doesn't know anything about the window containing it.
All it can do is pass signals up the chain.
But wait -- it can't even pass signals unless you define them.
There's a __gsignals__
object defined at the beginning
of the class that needs all its signals spelled out.
In this case, what you need is
"go-back-requested": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ()),
"go-forward-requested": (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ()),
Now these signals will bubble up to the window containing the toolbar.
Handle the signals in the containing window
So now you have to handle those signals in the window.
In WebBrowserWindow (derived from gtk.Window), in __init__
after creating the toolbar:
toolbar.connect("go-back-requested", self.go_back_requested_cb,
self.content_tabs)
toolbar.connect("go-forward-requested", self.go_forward_requested_cb,
self.content_tabs)
And then of course you have to define those callbacks:
def go_back_requested_cb (self, widget, content_pane):
# Oops! What goes here?
def go_forward_requested_cb (self, widget, content_pane):
# Oops! What goes here?
But whoops! What do we put there? It turns out that WebBrowserWindow
has no better idea than WebToolbar did of where its content is or
how to tell it to go back or forward.
What it does have is a ContentPane (derived from gtk.Notebook),
which is basically just a container with no exposed methods that
have anything to do with web browsing.
Get the BrowserView for the current tab
Fortunately we can fix that. In ContentPane, you can get the current
page (meaning the current browser tab, in this case); and each page
has a child, which turns out to be a BrowserView.
So you can add this function to ContentPane to help other classes
get the current BrowserView:
def current_view(self):
return self.get_nth_page(self.get_current_page()).get_child()
And now, using that, we can define those callbacks in WebBrowserWindow:
def go_back_requested_cb (self, widget, content_pane):
content_pane.current_view().go_back()
def go_forward_requested_cb (self, widget, content_pane):
content_pane.current_view().go_forward()
Whew! That's a lot of steps for something I thought was going to be
just adding two buttons and two callbacks.
Tags: python, programming, webkit, gtk
[
16:45 Aug 06, 2016
More programming |
permalink to this entry |
]
Sat, 07 May 2016
I recently let Firefox upgrade itself to 46.0.1, and suddenly I
couldn't type anything any more. The emacs/readline editing bindings,
which I use probably thousands of times a day, no longer worked.
So every time I typed a Ctrl-H to delete the previous character,
or Ctrl-B to move back one character, a sidebar popped up.
When I typed Ctrl-W to delete the last word, it closed the tab.
Ctrl-U, to erase the contents of the urlbar, opened a new View Source
tab, while Ctrl-N, to go to the next line, opened a new window.
Argh!
(I know that people who don't use these bindings are rolling their
eyes and wondering "What's the big deal?" But if you're a touch typist,
once you've gotten used to being able to edit text without moving your
hands from the home position, it's hard to imagine why everyone else
seems content with key bindings that require you to move your
hands and eyes way over to keys like Backspace or Home/End that aren't
even in the same position on every keyboard. I map CapsLock to Ctrl
for the same reason, since my hands are too small to hit the
PC-positioned Ctrl key without moving my whole hand. Ctrl
was to the left of the "A" key on nearly all computer keyboards
until IBM's 1986 "101 Enhanced Keyboard", and it made a lot more
sense than IBM's redesign since few people use Caps Lock very often.)
I found a bug filed on the broken bindings, and lots of people
commenting online, but it wasn't until I found out that Firefox 46 had
switched to GTK3 that I understood had actually happened. And adding
gtk3 to my web searches finally put me on the track to finding the
solution, after trying several other supposed fixes that weren't.
Here's what actually worked: edit
~/.config/gtk-3.0/settings.ini
and add, inside the
[Settings]
section, this line:
gtk-key-theme-name = Emacs
I think that's all that was needed. But in case that doesn't do it,
here's something I had already tried, unsuccessfully,
and it's possible that you actually need it in addition to the
settings.ini change
(I don't know how to undo magic Gnome settings so I can't test it):
gsettings set org.gnome.desktop.interface gtk-key-theme "Emacs"
Tags: linux, gtk, gtk3, emacs, firefox
[
18:11 May 07, 2016
More linux |
permalink to this entry |
]
Sun, 26 Sep 2010
Dave was using some old vacation photos to test filesystem performance,
and that made me realize that I had beautiful photos from the same
trip that I hadn't yet turned into desktop backgrounds.
Sometimes I think that my
GIMP Wallpaper
script is the most useful of the GIMP plug-ins I've written.
It's such a simple thing ... but I bet I use it more than any of
my other plug-ins, and since I normally make backgrounds for at least
two resolutions (my 1680x1050 desktop and my 1366x768 laptop),
it certainly saves me a lot of time and hassle.
But an hour into my background-making, I started to have nagging doubts.
I wasn't renaming these images, just keeping the original filenames
from the camera, like pict0828.jpg. What if if some of these
were overwriting images of the same name?
The one thing my script doesn't do is check for that, and
gimp_file_save
doesn't pop up any warnings.
I've always meant to add a check for it.
Of course, once the doubts started, I had to stop generating backgrounds
and start generating code. And I'm happy with the result:
wallpaper-0.4.py warns and won't let you save over an old background
image, but keeps all the logic in one dialog rather than popping up
extra warnings.
Now I can generate backgrounds without worrying that I'm stomping on
earlier ones.
Tags: gimp, programming, gtk
[
22:25 Sep 26, 2010
More gimp |
permalink to this entry |
]
Tue, 15 Sep 2009
I've been getting tired of my various desktop backgrounds, and
realized that I had a lot of trip photos, from fabulous places
like Grosvenor Arch (at right),
that I'd never added to my background collection.
There's nothing like lots of repetitions of the same task to
bring out the shortcomings of a script, and the
wallpaper
script I threw together earlier this year was no exception.
I found myself frequently irritated by not having enough information
about what the script was doing or being able to change the filename.
Then I could have backgrounds named grosvenor.jpg rather
than img2691.jpg.
Alas, I can't use the normal GIMP Save-as dialog, since GIMP doesn't
make that dialog available to plug-ins. (That's a deliberate choice,
though I've never been clear on the reason behind it.) If I wanted
to give that control to the user, I'd have to make my own dialogs.
It's no problem to make a GTK dialog from Python. Just create a
gtk.Dialog, add a gtk.Entry to it, call dialog.run(), then check
the return value and get the entry's text to see if it changed.
No problem, right?
Ha! If you think that, you don't work with computers.
The dialog popped up fine, it read the text entry fine ... but it
wouldn't go away afterward. So after the user clicked OK, the
plug-in tried to save and GIMP popped up the JPEG save dialog
(the one that has a quality slider and other controls, but no
indication of filename) under my text entry dialog, which
remained there.
All attempts at calling dialog.hide() and dialog.destroy() and
similar mathods were of no avail. A helpful person on #pygtk worked
with me but ended up as baffled as I was. What was up?
The code seemed so simple -- something like this:
response = dialog.run()
if response == gtk.RESPONSE_OK :
pathname = pathentry.get_text()
dialog.hide()
dialog.destroy()
pdb.gimp_file_save(newimg, newimg.active_layer, pathname, pathname,
run_mode=0)
In the end, GIMP guru Sven pointed me to the answer.
The problem was that my dialog wasn't part of the GTK main loop. In
retrospect, this makes sense: the plug-in is an entirely different
process, so I shouldn't be surprised that it would have its own main loop.
So when I hide() and destroy(), those events don't happen right away
because there's no loop in the plug-in process that would see them.
The plug-in passes control back to GIMP to do the gimp_file_save().
GIMP's main loop doesn't have access to the hide and destroy signals I
just sent. So the gimp_file_save runs, popping up its own dialog
(under mine, because the JPEG save dialog is transient to the original
image window while my python dialog isn't).
That finishes, returns control to the plug-in, the plug-in exits and
at that point GTK cleans up and finally destroys the dialog.
The solution is to loop over GTK events in the plug-in before calling
gimp_file_save, like this:
response = dialog.run()
if response == gtk.RESPONSE_OK :
pathname = pathentry.get_text()
dialog.hide()
dialog.destroy()
while gtk.events_pending() :
gtk.main_iteration()
pdb.gimp_file_save(newimg, newimg.active_layer, pathname, pathname,
run_mode=0)
That loop gives the Python process a chance to clean up the dialog
before passing control to GIMP and its main loop. GTK in the
subprocess is happy, the user is happy, and I'm happy because now
I have a much more efficient way of making lots of desktop
backgrounds for lots of different machines.
The
updated script, along with a lot more information on how to use
it and how to set up tool presets for it.
Tags: programming, gimp, gtk
[
23:21 Sep 15, 2009
More gimp |
permalink to this entry |
]
Sat, 20 Jun 2009
On my last Mojave trip, I spent a lot of the evenings hacking on
PyTopo.
I was going to try to stick to OpenStreetMap and other existing mapping
applications like TangoGPS, a neat little smartphone app for
downloading OpenStreetMap tiles that also runs on the desktop --
but really, there still isn't any mapping app that works well enough
for exploring maps when you have no net connection.
In particular, uploading my GPS track logs after a day of mapping,
I discovered that Tango really wasn't a good way of exploring them,
and I already know Merkaartor, nice as it is for entering new OSM
data, isn't very good at working offline. There I was, with PyTopo
and a boring hotel room; I couldn't stop myself from tweaking a bit.
Adding tracklogs was gratifyingly easy. But other aspects of the
code bother me, and when I started looking at what I might need to
do to display those Tango/OSM tiles ... well, I've known for a while
that some day I'd need to refactor PyTopo's code, and now was the time.
Surprisingly, I completed most of the refactoring on the trip.
But even after the refactoring, displaying those OSM tiles turned out
to be a lot harder than I'd hoped, because I couldn't find any
reliable way of mapping a tile name to the coordinates of that tile.
I haven't found any documentation on that anywhere, and Tango and
several other programs all do it differently and get slightly
different coordinates. That one problem was to occupy my spare time
for weeks after I got home, and I still don't have it solved.
But meanwhile, the rest of the refactoring was done, nice features
like track logs were working, and I've had to move on to other
projects. I am going to finish the OSM tile MapCollection class,
but why hold up a release with a lot of useful changes just for that?
So here's PyTopo 0.8,
and the couple of known problems with the new features will have to wait
for 0.9.
Tags: programming, python, gtk, pygtk, mapping, GIS, openstreetmap
[
20:49 Jun 20, 2009
More programming |
permalink to this entry |
]
Sat, 14 Mar 2009
When I upgraded to Ubuntu Intrepid recently, I pulled in a newer GTK+,
version 2.14.4. And when I went to open a file in GIMP, I got a surprise:
my "bookmarks" were no longer visible without scrolling down.
In the place where the bookmarks used to be, instead was a list of ...
what are those things? Oh, I see ... they're all the filesystems
listed with "noauto" in my /etc/fstab --the filesystems that
aren't mounted unless somebody asks for them, typically by plugging
in some piece of hardware.
There are a lot of these. Of course there's one for the CDROM drive
(I never use floppies so at some point I dropped that entry).
I have another entry for Windows-formatted partitions that show up on
USB, like when I plug in a digital camera or a thumb drive.
I also have one of those front panel flash card readers with 4 slots,
for reading SD cards, memory sticks, compact flash, smart media etc.
Each of those shows up as a different device,
so I treat them separately and mount SD cards as /sdcard,
memory sticks as /stick and so on.
In addition, there are entries corresponding to
other operating systems installed on this multi-boot machine, and
to several different partitions on my external USB backup drive.
These are all listed in /etc/fstab with entries like this:
/dev/hdd /cdrom udf,iso9660 user,noauto 0 0
/dev/sde1 /pix vfat rw,user,fmask=133,noauto 0 0
The GTK developers, in their wisdom, have realized that what the file
selector really needs to be.
I mean, I was just thinking while opening a file in GIMP the other day,
"Browsing image files on filesystems that are actually mounted
is so tedious.
I wish I could do something else instead, like view my /etc/fstab file
to see a list of unmounted filesystems for which I might decide to
plug in an external device."
Clicking on one of the unmounted filesystems (even right-clicking!)
gives an error:
Could not mount sdcard
mount: special device /dev/sdb1 does not exist
So I guess the intent is that I'll plug in my external drive or camera,
then use the gtk file selector from a program like GIMP as the means to
mount it. Um ... don't most people already have some way of mounting
new filesystems, whether it's an automatic mount from HAL or typing
mount
in a terminal?
(And before you ask, yes, for the time being I have dbus and hal and
fam and gamin and all that crap running.)
The best part
But I haven't even told you the best part yet. Here it is:
If you mount a filesystem manually, e.g. mount /dev/sdb1
/mnt
...
it doesn't show up in the list!
So this enormous list of filesystems that's keeping me from seeing
my file selector bookmarks ... doesn't even include filesystems that
are really there!
Tags: gtk, user interface, humor, linux
[
12:59 Mar 14, 2009
More linux |
permalink to this entry |
]
Sun, 14 Dec 2008
Dave has been fighting for I don't know how many weeks trying to get
a buildable set of gtk libraries installed on his Mac.
He doesn't need them to build GIMP -- the
GIMP on OS X
project (split off from Wilber Loves Apple) provides binaries
complete with all the libraries needed. Alas, it's just a binary
package with no development headers, so if you want to build any other
gtk packages, like pho,
or maybe some GIMP plug-ins, you're in for a much longer adventure.
Mac Ports used to make that easy,
but the Ports version of gtk2 doesn't build on OS X "Tiger".
It's a long story and I don't (want to) know all the hairy details,
but this weekend he finally gave up on it and began downloading all
the gtk2 packages and dependencies (cairo, pango, bonobo, atk etc.)
from their various project sites.
Oddly enough, building them went much more smoothly than Ports
had, and after a little twiddling of --disable flags in configure
and a lot of waiting, he had most of the libraries built.
Even gtk2 itself! Except ... gtk2's make install
failed.
Seems that although gtk is configured to disable building docs by
default (configure --help
shows a --enable-gtk-doc option),
nevertheless make install
calls something
called gtkdoc-rebase
from a lot of the subdirectories.
And gtkdoc-rebase doesn't exist, since it wasn't ever built. So
the whole make install
process fails at that point --
after installing the libraries but before telling pkg-config that
gtk-2.0 is indeed present.
After twiddling configure dependencies all day, Dave was getting
frustrated. "How do I configure it to really disable docs,
so it won't try to run this gtkdoc-rebase thing I don't have?"
I was in the middle of a timed quiz for a class I'm taking.
"I have no idea. You'd think they'd check for that. Um ...
all you need is for gtkdoc-rebase to return success, right?
What if you make a script somewhere in your path that contains
nothing but a shebang line, #! /bin/sh
?
It's a horrible hack, but ..."
"Horrible hacks R us!" he exclaimed, and created the script.
10 minutes later, he had gtk-2.0 installed, pkg-config notified
and pho built.
Sometimes horrible hacks are the best.
The gtk2 package list
Incidentally, for anyone trying to accomplish the same thing,
the packages he needed to download were:
pgk-config gettext glib pango atk jpeg jasper libpng tiff
pixman freetype libxml fontconfig cairo gtk2
and he had to configure gtk2 with --disable-cups
(because it introduced other errors, not because of CUPS itself).
The trickiest dependency was atk, because it wasn't in the place that
gtk.org points to and it wasn't on its own project site either;
he eventually found it by poking around on the
gnome ftp
site.
Tags: hack, gtk, OS X
[
21:44 Dec 14, 2008
More programming |
permalink to this entry |
]
Fri, 12 Oct 2007
On a recent Mojave desert trip, we tried to follow a minor dirt road
that wasn't mapped correctly on any of the maps we had, and eventually
had to retrace our steps. Back at the hotel, I fired up my trusty
PyTopo on the East
Mojave map set and tried to trace the road. But I found that as I
scrolled along the road, things got slower and slower until it
just wasn't usable any more.
PyTopo was taking up all of my poor laptop's memory. Why?
Python is garbage collected -- you're not supposed to have
to manage memory explicitly, like freeing pixbufs.
I poked around in all the sample code and man pages I had available
but couldn't find any pygtk examples that seemed to be doing any
explicit freeing.
When we got back to civilization (read: internet access) I did
some searching and found the key. It's even in the
PyGTK
Image FAQ, and there's also some discussion in a
mailing
list thread from 2003.
Turns out that although Python is supposed to handle its own garbage
collection, the Python interpreter doesn't grok the size of a pixbuf
object; in particular, it doesn't see the image bits as part of the
object's size. So dereferencing lots of pixbuf objects doesn't trigger
any "enough memory has been freed that it's time to run the garbage
collector" actions.
The solution is easy enough: call gc.collect()
explicitly
after drawing a map (or any other time a bunch of pixbufs have been
dereferenced).
So there's a new version of PyTopo, 0.6
that should run a lot better on small memory machines, plus
a new collection format (yet another format from
the packaged Topo! map sets) courtesy of Tom Trebisky.
Oh ... in case you're wondering, the ancient USGS maps from
Topo! didn't show the road correctly either.
Tags: programming, python, gtk, pygtk
[
22:21 Oct 12, 2007
More programming |
permalink to this entry |
]
Fri, 25 Aug 2006
Belated release announcement: 0.5b2 of my little map viewer
PyTopo
has been working well, so I released 0.5 last week with only a
few minor changes from the beta.
I'm sure I'll immediately find six major bugs -- but hey, that's
what point releases are for. I only did betas this time because
of the changed configuration file format.
I also made a start on a documentation page for the .pytopo file
(though it doesn't really have much that wasn't already written
in comments inside the script).
Tags: programming, python, gtk, pygtk, mapping, GIS, pytopo
[
22:10 Aug 25, 2006
More programming |
permalink to this entry |
]
Sat, 03 Jun 2006
A few months ago, someone contacted me who was trying to use my
PyTopo map display script for a different set of map data, the
Topo! National Parks series. We exchanged some email about the
format the maps used.
I'd been wanting to make PyTopo more general
anyway, and already had some hacky code in my local version to
let it use a local geologic map that I'd chopped into segments.
So, faced with an Actual User (always a good incentive!), I
took the opportunity to clean up the code, use some of Python's
support for classes, and introduce several classes of map data.
I called it 0.5 beta 1 since it wasn't well tested. But in the last
few days, I had occasion to do some map exploring,
cleaned up a few remaining bugs, and implemented a feature which
I hadn't gotten around to implementing in the new framework
(saving maps to a file).
I think it's ready to use now. I'm going to do some more testing:
after visiting the USGS
Open House today and watching Jim Lienkaemper's narrated
Virtual
Tour of the Hayward Fault,
I'm all fired up about trying again to find more online geologic
map data.
But meanwhile, PyTopo is feature complete and has the known
bugs fixed. The latest version is on
the PyTopo page.
Tags: programming, python, gtk, pygtk, mapping, GIS
[
18:25 Jun 03, 2006
More programming |
permalink to this entry |
]
Tue, 21 Jun 2005
I updated my Debian sid system yesterday, and discovered today that
gnome-volume-control has changed their UI yet again. Now the window
comes up with two tabs,
Playback and
Capture; the
default tab,
Playback, has only one slider in it,
PCM,
and all the important sliders, like
Volume, are under
Capture. (I'm told this is some interaction with how ALSA
sees my sound chip.)
That's just silly. I've never liked the app anyway -- it takes
forever to come up, so I end up missing too much of any clip that
starts out quiet. All I need is a simple, fast window with
a single slider controlling master volume. But nothing like that
seems to exist, except panel applets that are tied to the panels
of particular window managers.
So I wrote one, in PyGTK. vol is
a simple script which shows a slider, and calls aumix
under the hood to get and set the volume. It's horizontal by
default; vol -h gives a vertical slider.
Aside: it's somewhat amazing that Python has no direct way
to read an integer out of a string containing more than just that
integer: for example, to read 70 out of "70,". I had to write a
function to handle that. It's such a terrific no-nonsense
language most of the time, yet so bad at a few things.
(And when I asked about a general solution in the python channel
at [large IRC network], I got a bunch of replies like "use
int(str[0:2])" and "use int(str[0:-1])".
Shock and bafflement ensued when I pointed out that 5, 100, and -27
are all integers too and wouldn't be handled by those approaches.)
Tags: programming, python, gtk, pygtk
[
15:54 Jun 21, 2005
More programming |
permalink to this entry |
]
Sat, 09 Apr 2005
A few days ago, I mentioned my woes regarding Python sending spurious
expose events every time the drawing area gains or loses focus.
Since then, I've spoken with several gtk people, and investigated
several workarounds, which I'm writing up here for the benefit of
anyone else trying to solve this problem.
First, "it's a feature". What's happening is that the default focus
in and out handlers for the drawing area (or perhaps its parent class)
assume that any widget which gains keyboard focus needs to redraw
its entire window (presumably because it's locate-highlighting
and therefore changing color everywhere?) to indicate the focus
change. Rather than let the widget decide that on its own, the
focus handler forces the issue via this expose event. This may be a
bad decision, and it doesn't agree with the gtk or pygtk documentation
for what an expose event means, but it's been that way for long enough
that I'm told it's unlikely to be changed now (people may be depending
on the current behavior).
Especially if there are workarounds -- and there are.
I wrote that this happened only in pygtk and not C gtk, but I was
wrong. The spurious expose events are only passed if the CAN_FOCUS
flag is set. My C gtk test snippet did not need CAN_FOCUS,
because the program from which it was taken, pho, already implements
the simplest workaround: put the key-press handler on the window,
rather than the drawing area. Window apparently does not have
the focus/expose misbehavior.
I worry about this approach, though, because if there are any other
UI elements in the window which need to respond to key events, they
will never get the chance. I'd rather keep the events on the drawing
area.
And that becomes possible by overriding the drawing area's default
focus in/out handlers. Simply write a no-op handler which returns
TRUE, and set it as the handler for both focus-in and focus-out. This
is the solution I've taken (and I may change pho to do the same thing,
though it's unlikely ever to be a problem in pho).
In C, there's a third workaround: query the default focus handlers,
and disconnect() them. That is a little more efficient (you
aren't calling your nop routines all the time) but it doesn't seem to
be possible from pygtk: pygtk offers disconnect(), but there's no way to
locate the default handlers in order to disconnect them.
But there's a fourth workaround which might work even in pygtk:
derive a class from drawing area, and set the focus in and out
handlers to null. I haven't actually tried this yet, but it may be
the best approach for an app big enough that it needs its own UI classes.
One other thing: it was suggested that I should try using AccelGroups
for my key bindings, instead of a key-press handler, and then I could
even make the bindings user-configurable. Sounded great!
AccelGroups turn out to be very easy to use, and a nice feature.
But they also turn out to have undocumented limitations on what
can and can't be an accelerator. In particular, the arrow keys can't
be accelerators; which makes AccelGroup accelerators less than
useful for a widget or app that needs to handle user-initiated
scrolling or movement. Too bad!
Tags: programming, python, gtk, pygtk
[
21:52 Apr 09, 2005
More programming |
permalink to this entry |
]
Wed, 06 Apr 2005
While on vacation, I couldn't resist tweaking
pytopo
so that I could use it to explore some of the areas we were
visiting.
It seems fairly usable now. You can scroll around, zoom in and out
to change between the two different map series, and get the
coordinates of a particular location by clicking. I celebrated
by making a page for it, with a silly tux-peering-over-map icon.
One annoyance: it repaints every time it gets a focus in or out,
which means, for people like me who use mouse focus, that it
repaints twice for each time the mouse moves over the window.
This isn't visible, but it would drag the CPU down a bit on a
slow machine (which matters since mapping programs are particularly
useful on laptops and handhelds).
It turns out this is a pygtk problem: any pygtk drawing area window
gets spurious Expose events every time the focus changes (whether or
not you've asked to track focus events), and it reports that the
whole window needs to be repainted, and doesn't seem to be
distinguishable in any way from a real Expose event.
The regular gtk libraries (called from C) don't do this, nor
do Xlib C programs; only pygtk.
I filed
bug 172842
on pygtk; perhaps someone will come up with a workaround, though
the couple of pygtk developers I found on #pygtk couldn't think
of one (and said I shouldn't worry about it since most people
don't use pointer focus ... sigh).
Tags: programming, python, gtk, pygtk, mapping, GIS, pytopo
[
17:26 Apr 06, 2005
More programming |
permalink to this entry |
]
Sun, 27 Mar 2005
I couldn't stop myself -- I wrote up a little topo map viewer in
PyGTK, so I can move around with arrow keys or by clicking near the
edges. It makes it a lot easier to navigate the map directory if
I don't know the exact starting coordinates.
It's called PyTopo,
and it's in the same
place as my earlier two topo scripts.
I think CoordsToFilename has some bugs; the data CD also has some
holes, and some directories don't seem to exist in the expected
place. I haven't figured that out yet.
Tags: programming, python, gtk, pygtk, mapping, GIS
[
18:53 Mar 27, 2005
More programming |
permalink to this entry |
]
I've long wished for something like those topographic map packages
I keep seeing in stores. The USGS (US Geological Survey) sells
digitized versions of their maps, but there's a hefty setup fee
for setting up an order, so it's only reasonable when buying large
collections all at once.
There are various Linux mapping applications which do things like
download squillions of small map sections from online mapping sites,
but they're all highly GPS oriented and I haven't had much luck
getting them to work without one. I don't (yet?) have a GPS;
but even if I had one, I usually want to make maps for places I've
been or might go, not for where I am right now. (I don't generally
carry a laptop along on hikes!)
The Topo!
map/software packages sold in camping/hiking stores (sometimes
under the aegis of National Geographic
are very reasonably priced. But of course, the software is
written for Windows (and maybe also Mac), not much help to Linux
users, and the box gives no indication of the format of the data.
Googling is no help; it seems no Linux user has ever
tried buying one of these packages to see what's inside.
The employees at my local outdoor equipment store (Mel Cotton's)
were very nice without knowing the answer, and offered
the sensible suggestion of calling the phone number on the box,
which turns out to be a small local company, "Wildflower Productions",
located in San Francisco.
Calling Wildflower, alas, results in an all too familiar runaround:
a touchtone menu tree where no path results in the possibility of
contact with a human. Sometimes I wonder why companies bother to
list a phone number at all, when they obviously have no intention
of letting anyone call in.
Concluding that the only way to find out was to buy one, I did so.
A worthwhile experiment, as it turned out! The maps inside are
simple GIF files, digitized from the USGS 7.5-minute series and,
wonder of wonders, also from the discontinued but still useful
15-minute series.
Each directory contains GIF files covering the area of one
7.5 minute map, in small .75-minute square pieces,
including pieces of the 15-minute map covering the same area.
A few minutes of hacking with python and
Image Magick
resulted in a script to stitch together all images
in one directory to make one full USGS 7.5 minute map;
after a few hours of hacking, I can stitch
a map of arbitrary size given start and end longitude and latitude.
My initial scripts,
such as they are.
Of course, I don't yet have nicities like a key, or an interactive
scrolling window, or interpretation of the USGS digital elevation
data. I expect I have more work to do. But for now, just
being able to generate and print maps for a specific area is a huge boon,
especially with all the mapping we're doing in Field Geology class.
GIMP's "measure" tool will come in handy for measuring distances
and angles!
Tags: programming, python, gtk, pygtk
[
12:13 Mar 27, 2005
More programming |
permalink to this entry |
]