#!/usr/bin/env python2 import subprocess import cPickle import os.path import os import sys import pygtk pygtk.require('2.0') import gtk from autojump import open_dic,get_dic_file defaults={} actions={} #directory finding helpers, conforming to the XDG Base Directory Specification def data_dir(): xdg_data_dir = os.environ.get('XDG_DATA_HOME') or os.path.join(os.environ['HOME'], '.local', 'share') return os.path.join(xdg_data_dir, 'autojump') def config_dir(): xdg_config_dir = os.environ.get('XDG_CONFIG_HOME') or os.path.join(os.environ['HOME'], '.config') return os.path.join(xdg_config_dir, 'autojump') #decorator truff def action(validator,name=None): if name is None: def wrapper(action): actions[action.__name__]=(action,validator) else: def wrapper(action): actions[name]=(action,validator) return wrapper #validator helper def has_child_dir(dirname,recursion=0): def wrapper(path): k=recursion ii="" while k>=0: if os.path.isdir(os.path.join(path,ii,dirname)): return True k-=1 ii=os.path.join("..",ii) return False return wrapper def has_child_file(filename): def wrapper(path): return os.path.isfile(os.path.join(path,filename)) return wrapper def load_paths(maxpath=10): path_dict=open_dic(get_dic_file()) path_dict=path_dict.items() path_dict.sort(key=lambda x: x[1],reverse=True) return [path for path,score in path_dict[:maxpath]] def load_settings_file(): filename = os.path.join(config_dir(), 'jumpapplet_py') #migration from old location old_filename = os.path.join(os.environ['HOME'], '.jumpapplet_py') if not os.path.exists(filename) and os.path.exists(old_filename): os.rename(old_filename, filename) print "loading settings" global defaults dic_file = filename try: aj_file=open(dic_file,'r') defaults=cPickle.load(aj_file) aj_file.close() except IOError: print "no config file" pass if not "terminal" in defaults: defaults["terminal"]="gnome-terminal" if not "navigator" in defaults: defaults["navigator"]="nautilus" if not "maxpath" in defaults: defaults["maxpath"]=15 if not "invert" in defaults: defaults["invert"]=False if not "collapse" in defaults: defaults["collapse"]=True create_actions() def save_settings_file(filename=None): directory = config_dir() if not filename: filename = os.path.join(directory, 'jumpapplet_py') if not os.path.exists(directory): os.makedirs(directory) print "saving settings" dic_file= filename aj_file=open(dic_file,'w') cPickle.dump(defaults,aj_file) aj_file.close() def get_actions(path): return [(name,action) for name,(action,validator) in actions.items() if validator(path)] def popup(sender,button,activation): paths=load_paths(maxpath=defaults["maxpath"]) if defaults["collapse"]: def collapse_home(path): return path.replace(os.path.expanduser('~'),"~") else: def collapse_home(path): return path menu=gtk.Menu() if defaults["invert"]: item=gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT) item.connect("activate",quit) menu.append(item) item=gtk.ImageMenuItem(stock_id=gtk.STOCK_PREFERENCES) item.connect("activate",settings) menu.append(item) menu.append(gtk.SeparatorMenuItem()) for path in reversed(paths): actions=get_actions(path) if not actions: continue item=gtk.MenuItem(collapse_home(path),use_underline=False) submenu=gtk.Menu() item.set_submenu(submenu) for name,action in actions: subitem=gtk.MenuItem(name) subitem.connect("activate",action,path) submenu.append(subitem) menu.append(item) else: for path in paths: actions=get_actions(path) if not actions: continue item=gtk.MenuItem(collapse_home(path),use_underline=False) submenu=gtk.Menu() item.set_submenu(submenu) for name,action in actions: subitem=gtk.MenuItem(name) subitem.connect("activate",action,path) submenu.append(subitem) menu.append(item) menu.append(gtk.SeparatorMenuItem()) item=gtk.ImageMenuItem(stock_id=gtk.STOCK_PREFERENCES) item.connect("activate",settings) menu.append(item) item=gtk.ImageMenuItem(stock_id=gtk.STOCK_QUIT) item.connect("activate",quit) menu.append(item) menu.show_all() menu.popup(None,None,gtk.status_icon_position_menu,button,activation,sender) def settings(sender): window=gtk.Dialog("jump applet preferences",None,gtk.DIALOG_MODAL|gtk.DIALOG_DESTROY_WITH_PARENT,(gtk.STOCK_SAVE,gtk.RESPONSE_OK,gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL)) window.set_border_width(3) window.set_resizable(False) if os.path.isfile("icon.png"): window.set_icon_from_file("icon.png") elif os.path.isfile("/usr/share/autojump/icon.png"): window.set_icon_from_file("/usr/share/autojump/icon.png") vbox=gtk.Table(5,2) vbox.set_row_spacings(3) window.get_child().add(vbox) def add_string_setting(name,label,nsettings): label=gtk.Label(label+' ') label.set_alignment(1.,.5) entry=gtk.Entry() if name in defaults: entry.set_text(defaults[name]) vbox.attach(label,0,1,nsettings,nsettings+1) vbox.attach(entry,1,2,nsettings,nsettings+1) return (name,entry) def add_integer_setting(name,label,nsettings): label=gtk.Label(label+' ') label.set_alignment(1.,.5) entry=gtk.SpinButton() entry.set_range(5,35) entry.set_numeric(True) entry.set_increments(1,5) entry.set_snap_to_ticks(True) if name in defaults: entry.set_value(defaults[name]) vbox.attach(label,0,1,nsettings,nsettings+1) vbox.attach(entry,1,2,nsettings,nsettings+1) return (name,entry) def add_bool_setting(name,label,nsettings): entry=gtk.CheckButton(label=label,use_underline=False) if name in defaults: entry.set_active(defaults[name]) vbox.attach(entry,0,2,nsettings,nsettings+1) return (name,entry) entries=[] entries.append(add_string_setting("terminal","Terminal program",0)) entries.append(add_string_setting("navigator","Navigator program",1)) entries.append(add_integer_setting("maxpath","Number of directories",2)) entries.append(add_bool_setting("invert","List directories in reverse order",3)) entries.append(add_bool_setting("collapse","Collapse home directory to ~",4)) window.connect("response",save_settings,entries,window) window.show_all(); def save_settings(sender,response,entries,window): window.hide_all() if response!=gtk.RESPONSE_OK: return global defaults for name,entry in entries: try: defaults[name]=int(entry.get_text()) except (ValueError,AttributeError): try: defaults[name]=entry.get_active() except AttributeError: defaults[name]=entry.get_text() save_settings_file() create_actions() def init(): load_settings_file() if os.path.isfile("icon.png"): icon=gtk.status_icon_new_from_file("icon.png") elif os.path.isfile("/usr/share/autojump/icon.png"): icon=gtk.status_icon_new_from_file("/usr/share/autojump/icon.png") else: icon=gtk.status_icon_new_from_icon_name("help") icon.set_visible(True) icon.connect("popup-menu",popup) def quit(sender): gtk.main_quit() ###################################################### #insert other actions here using the action decorator# ###################################################### def create_actions(): global actions actions={} @action(has_child_dir(".git",recursion=3)) def gitk(sender,path): if not os.fork(): os.chdir(path) subprocess.Popen(['gitk']).wait() sys.exit() @action(has_child_file("CMakeCache.txt"),"configure") def cmake(sender,path): if not os.fork(): os.chdir(path) subprocess.Popen(['cmake-gui','.']).wait() sys.exit() @action(os.path.isdir) def terminal(sender,path): print "launch terminal '%s'" % defaults["terminal"] if not os.fork(): try: if defaults["terminal"]=="konsole": subprocess.Popen([defaults["terminal"],"--workdir=%s"%path]).wait() else: os.chdir(path) subprocess.Popen([defaults["terminal"]]).wait() except OSError: pass sys.exit() @action(os.path.isdir) def navigator(sender,path): print "launch navigator '%s'" % defaults["navigator"] if not os.fork(): try: subprocess.Popen([defaults["navigator"],path]).wait() except OSError: pass sys.exit() if __name__=='__main__': init() gtk.main()