import gtk
import cairo
import sys
import os
import unicodedata
from fringwalker import *
from fringrenderer import FringRenderer
from fringutil import *
from fringdata import SumListCollection
CURSOR_NONE = 0
CURSOR_BUSY = 1
CURSOR_LINK = 2
ui = """
"""
class UI( gtk.Window ):
def __init__(self, uri):
self.busy_cursor = 0
self.backgroundColour = (1,1,1)
self.data = None
self.zoomfactor = 1.0
self.renderer = FringRenderer()
self.collection = SumListCollection()
self.walker = FringWalker(self.collection);
self.scan_active = False
self.cursor = None
self.maxrings = 4
# create gui
gtk.Window.__init__(self)
self.set_title("fring");
self.__init_gui()
# walk directory
self.walker.connect("list-changed",self.__event_walker_progress)
self.walker.connect("finished", self.__event_walker_finished)
self.walker.connect("manually-stopped", lambda w: self.__show_busy_cursor(-1))
self.open_folder(uri)
def __init_gui(self):
# create menubar
uimanager = gtk.UIManager()
accelgroup = uimanager.get_accel_group()
self.add_accel_group(accelgroup)
ag_global = gtk.ActionGroup('ag_global')
ag_global.add_actions([
('FRing', None, _("_Folder")),
('View', None, _("_View")),
('Help', None, _("_Help")),
('OpenParent', gtk.STOCK_GO_UP, _("Open _Parent"), "Up", _("Open the parent folder"), self.open_parent),
('SelectFolder', gtk.STOCK_OPEN, _("_Open Folder..."), None, _("Open Folder..."), self.dialog_openfolder),
('Quit', gtk.STOCK_QUIT, _("_Quit"), None, _("Quit"), self.close),
('Zoom In', gtk.STOCK_ZOOM_IN, _("Zoom _In"), "Up", _("Zoom In"), lambda w: self.zoom(w,0.2)),
('Zoom Out', gtk.STOCK_ZOOM_OUT, _("Zoom _Out"), "Down", _("Zoom Out"), lambda w: self.zoom(w,-0.2)),
('AddRing', gtk.STOCK_ADD, _("_Add Ring"), "plus", _("Add Ring"), lambda w: self.zoom_rings(1)),
('RemRing', gtk.STOCK_REMOVE, _("_Remove Ring"), "minus", _("Remove Ring"), lambda w: self.zoom_rings(-1)),
('Save Image...', gtk.STOCK_SAVE_AS, _("_Save Image..."), None, _("Save Image..."), self.save_image),
('Refresh', gtk.STOCK_REFRESH, _("_Refresh Tree"), "R", _("Refresh Tree"), self.refresh_tree),
('About', gtk.STOCK_ABOUT, _("_About"), None, "About", self.dialog_about),
])
action = gtk.ToggleAction("Show Hidden Files", _("Show Hidden Files"), None, None)
action.set_active(True)
action.connect("toggled",self.__event_togglehidden)
ag_global.add_action(action)
uimanager.insert_action_group(ag_global, 0)
uimanager.add_ui_from_string(ui)
menubar = uimanager.get_widget('/MenuBar')
menubar.show()
# create toolbar
toolbar = gtk.HBox()
b = gtk.Button()
img = gtk.image_new_from_stock(gtk.STOCK_GO_UP, gtk.ICON_SIZE_BUTTON)
b.set_property("image", img)
uimanager.get_action("/MenuBar/FRing/OpenParent").connect_proxy(b)
toolbar.pack_start( b, False )
b.connect("enter-notify-event",self.__event_parentbutton_enter)
b.connect("leave-notify-event",self.__event_parentbutton_leave)
b = gtk.FileChooserButton('Select a Folder')
b.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
b.set_local_only(False)
h = b.connect("current-folder-changed", lambda w: self.__set_uri(w.get_uri()))
toolbar.pack_start( b, True )
self.filechooserbutton = b
self.filechooserbutton.handler_folderchanged = h
# create canvas
self.eventbox = gtk.EventBox()
self.image = gtk.Image()
self.eventbox.add(self.image)
self.eventbox.add_events( gtk.gdk.EXPOSURE_MASK
| gtk.gdk.LEAVE_NOTIFY_MASK
| gtk.gdk.BUTTON_PRESS_MASK
| gtk.gdk.POINTER_MOTION_MASK )
self.eventbox.connect("scroll_event", self.__event_scroll)
self.eventbox.connect("button-press-event",self.__event_click)
self.eventbox.connect("motion-notify-event",self.__event_move)
# Create label
self.label = gtk.Label()
self.label.set_alignment(0, .5)
self.label.set_padding(6, 0)
self.label.set_text("")
# aligh menubar, toolbar and canvas vertically
vbox = gtk.VBox();
vbox.pack_start(menubar,False,False)
vbox.pack_start(toolbar,False,False)
f = gtk.Frame()
f.set_shadow_type(gtk.SHADOW_IN)
f.add(self.eventbox)
vbox.pack_start(f)
vbox.pack_start(self.label,False,False,6)
self.add(vbox)
# show everything
# Note: With set_default_size, the image can't be downsized.
self.set_size_request(100,100) # minimum size, needed!
self.resize(500,350)
self.show_all()
self.__create_canvas()
self.eventbox.connect("size-allocate",self.__event_resized)
self.connect("delete_event", self.close)
def dialog_about(self,widget):
d = gtk.AboutDialog()
d.set_name("fring");
d.set_license(LICENSE);
c = unicodedata.lookup('COPYRIGHT SIGN')
e = unicodedata.lookup('LATIN SMALL LETTER E WITH ACUTE')
d.set_copyright(u"Copyright "+c+" 2006 Lennart Poettering, Fr"+e+"d"+e+"ric Back")
d.set_comments(_("Visual disk usage analyser"))
d.run()
d.destroy()
def dialog_openfolder(self,widget):
""" Open a dialog to select a folder """
d = gtk.FileChooserDialog(None, self,
gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
(gtk.STOCK_CANCEL,0,gtk.STOCK_OK,1))
if d.run() == 1: self.open_folder(d.get_uri())
d.destroy()
def open_folder(self,uri):
# change folder by changing the chooser button
self.filechooserbutton.set_current_folder_uri(uri)
def close(self,w=None,e=None):
self.walker.stop()
self.destroy()
gtk.main_quit()
def open_parent(self,widget):
self.open_folder(get_parent_from_uri(self.uri))
def zoom(self,widget,factor):
self.zoomfactor += factor
if self.zoomfactor < 0:
self.zoomfactor = 0
self.redraw()
def zoom_rings(self,mod):
r = self.maxrings
r += mod
if r < 2: r = 2
if r != self.maxrings:
self.maxrings = r
#print "rings",self.maxrings
self.redraw()
def redraw(self,widget=None):
# clear the canvas
self.__show_busy_cursor(1)
self.ctx.rectangle(0,0,self.width,self.height)
self.ctx.set_source_rgb(*self.backgroundColour)
self.ctx.fill()
# draw segments
if not self.data: return
self.renderer.WIDTH = self.width
self.renderer.HEIGHT = self.height
self.renderer.INNER_RADIUS = self.height/12
self.renderer.RING_RADIUS = self.height/10
self.renderer.RING_RADIUS *= self.zoomfactor
self.renderer.RINGS_MAX = self.maxrings
self.renderer.draw_segment(self.ctx, 0, 0, 1, 0, 1, self.data, self.uri)
self.image.queue_draw()
self.__show_busy_cursor(-1)
def save_image(self, widget=None):
d = gtk.FileChooserDialog("Save As...",
self.get_toplevel(), gtk.FILE_CHOOSER_ACTION_SAVE,
(gtk.STOCK_CANCEL,0,gtk.STOCK_OK,1), None)
d.set_current_name( self.data.name.replace(" ","_") + ".png" )
r = d.run()
if r == 1:
#print d.get_uri()
self.__show_busy_cursor(1)
pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,True,8,self.width,self.height)
pixmap, mask = self.image.get_pixmap()
pixbuf.get_from_drawable(pixmap,pixmap.get_colormap(),0,0,0,0,-1,-1)
#pixbuf.save(d.get_filename(), "jpeg", {"quality":"100"})
pixbuf.save(d.get_filename(), "png", {})
self.__show_busy_cursor(-1)
d.destroy()
def refresh_tree(self,widget):
""" Refresh the current folder """
self.walker.stop()
self.collection.clear()
self.walker.walk(self.uri)
self.scan_active = True
self.__show_busy_cursor(1)
#----------------------------------------------------------- private methods
def __set_uri(self,uri):
print "requested:",uri,
d = self.collection.get_sumlist(uri)
if d is not None:
print "(load from collection)"
self.data = self.collection.get_sumlist(uri)
self.uri = uri
self.redraw()
else:
print "(walk)"
self.uri = uri
self.walker.stop()
if uri:
self.walker.walk(uri)
self.scan_active = True
self.__show_busy_cursor(1)
def __create_canvas(self):
""" Create a new pixmap and a new cairo context """
r = self.eventbox.get_allocation()
self.width, self.height = r.width, r.height
self.pixmap = gtk.gdk.Pixmap(self.window,r.width,r.height)
self.image.set_from_pixmap(self.pixmap,None)
self.ctx = self.pixmap.cairo_create()
self.renderer.prepare_layouts(self.ctx)
self.ctx.rectangle(0,0,self.width,self.height)
self.ctx.set_source_rgb(*self.backgroundColour)
self.ctx.fill()
def __show_busy_cursor(self, value=0):
self.busy_cursor += value
if self.busy_cursor > 0: self.__set_cursor(CURSOR_BUSY)
else: self.__set_cursor(CURSOR_NONE)
def __set_cursor(self, cursor):
if self.window is None: return
if self.cursor == cursor: return
if cursor == CURSOR_NONE:
self.window.set_cursor(None)
elif cursor == CURSOR_BUSY:
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
elif cursor == CURSOR_LINK:
self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND1))
#--------------------------------------------------- gobject event callbacks
def __event_togglehidden(self,widget):
if self.walker.showhidden == widget.get_active(): return
self.walker.showhidden = widget.get_active()
self.refresh_tree()
def __event_move(self, widget, event):
f = self.renderer.get_hotspot_at(event.x, event.y)
self.label.set_text("")
if f:
markup = "%s, %s, %0.1f%%" % (format_uri(f.path), pretty_size(f.sumlist.size), f.value*100)
self.label.set_text(markup)
if f.sumlist.has_children():
self.__set_cursor(CURSOR_LINK)
return
self.__show_busy_cursor() # reset busy cursor
def __event_click(self, widget, event):
f = self.renderer.get_hotspot_at(event.x, event.y)
if f and f.sumlist.children:
self.open_folder(f.path)
#if self.scan_active:
# if we are still walking the tree, FORCE a new scan on the path
# because we CANNOT guarantee that the path we point at is ready.
# self.open_folder(f.path)
#else:
# if we are sure that the sumlist is complete, jump there directly
# block filechooser callbacks while changing it
# self.filechooserbutton.handler_block(self.filechooserbutton.handler_folderchanged)
# self.uri = f.path
# self.__event_walker_progress(None,f.sumlist)
# self.filechooserbutton.set_current_folder_uri(f.path)
# self.filechooserbutton.handler_unblock(self.filechooserbutton.handler_folderchanged)
def __event_scroll(self, widget, event):
if event.direction is gtk.gdk.SCROLL_UP:
self.zoom(widget, 0.2)
elif event.direction is gtk.gdk.SCROLL_DOWN:
self.zoom(widget, -0.2)
def __event_walker_finished(self, widget, data):
self.__event_walker_progress(widget, data)
self.__show_busy_cursor(-1)
self.scan_active = False
def __event_resized(self, widget, event):
r = self.eventbox.get_allocation()
if (r.width,r.height) != (self.width,self.height):
self.__create_canvas()
self.redraw()
def __event_parentbutton_enter(self,widget,event):
markup = "%s" %format_uri(get_parent_from_uri(self.uri))
self.label.set_text(markup)
def __event_parentbutton_leave(self,widget,event):
self.label.set_text("")
def __event_walker_progress(self,widget,data):
self.data = data
self.redraw()