#!/usr/bin/env python # coding=utf-8 # # FileListApplet.py # # Copyright © 2008 Steven Brown # # This file is part of File List Applet. # # FileListApplet is free software: you can redistribute it and/or # modify it under the terms of the GNU General Public License as # published by the Free Software Foundation, either version 3 of the # License, or (at your option) any later version. # FileListApplet is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with FileListApplet. If not, see . from GnomePanelApplet import GnomePanelApplet from FileTypeList import FileTypeList import sys, os, time import subprocess import pyinotify import gtk, gnomeapplet import gobject import xdg.BaseDirectory from urllib import unquote from urlparse import urlparse # Performance ------------------------------------------------------------ #TODO: Remove delay when clicking "Open" on the folder selection dialog? - requires threading #TODO: Re-use 'GtkImage' to save space and time. cache by type? # Application Types ------------------------------------------------------------ #TODO: "Bubble up" highly populated Application subtypes: PDF, ODT, etc. But how to organize them? extra menu on TYPE list? #TODO: SOME form of organization to the "application" category of files. # - group "package/zip/compressed" types # - separating different groups? # Features ------------------------------------------------------------ # These all require creating a new menu creation/management class PRIORITY #TODO: **RECENT ITEMS** for all files menuitem #TODO: **SUMMARY** on main menu - collection of all files in all monitored directories #TODO: **Delete Monitor** - no point until the new menu creation/management class is created #TODO: Allow toggling sort method, from modtime to alphabetical? (LOW PRIORITY) # Backend ------------------------------------------------------------ #TODO: Store values in gconf to restore monitored directories (LOW PRIORITY) # GUI ------------------------------------------------------------ #TODO: Move away from UIManager. Maybe it can solve the context menu issue. (disappearing hierarchy vs not closing on activation) #TODO: Preferences Dialog - should implement gconf first #TODO: If total items is small (<=10?), instead of creating submenus, split the "media" menu into sections with titles #TODO: If number of items in a menu is less than MAX_MEDIUM_SIZE_ITEMS, then use small icons? (LOW priority) #TODO: Larger Icons on the root menu # Error Handling ------------------------------------------------------------ #TODO: Fall back to a dialog prompt for file-types with no association? # Drag and Drop ------------------------------------------------------------ #TODO: Drag and Drop, using menu as a source of URIs - but... create link, copy, or move? # Done ------------------------------------------------------------ PROGRAM_NAME = "File List Applet" VERSION = "pre 0.1" LICENSE = """ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ COPYRIGHT = "Copyright © Steven Brown 2008" AUTHORS = ["Steven Brown ", "and you? :)"] COMMENTS = "Browse files of any folder by type and modification time." WEBSITE = "http://www.stevenbrown.ca/blog/?p=497" OPEN_CMD = None devnull = open("/dev/null", "w") for cmd in ["gnome-open", "kde-open", "exo-open", "xdg-open", None]: if cmd: try: subprocess.call(cmd, stdout = devnull, stderr = devnull) OPEN_CMD = cmd break except OSError: print "'%s' not found" % cmd #if last item, print message then sys.exit(1), else, continue continue else: print "No program found to open files with. Exiting..." sys.exit(1) devnull.close() print "Found '%s' to open files with." % cmd #DEBUG # setup trash folders... hopefully. Could be more thorough. TRASH_LOC = os.path.join(xdg.BaseDirectory.xdg_data_home, "Trash") TRASH_FILES = os.path.join(TRASH_LOC, 'files') TRASH_INFOS = os.path.join(TRASH_LOC, 'info') if not os.path.exists(TRASH_INFOS): os.makedirs(TRASH_INFOS) if not os.path.exists(TRASH_FILES): os.makedirs(TRASH_FILES) def trash(filepath): "Send file to Trash with Restore capability." # determine destination name filename = os.path.basename(filepath) trash_filename = os.path.join(TRASH_FILES, filename) counter = 1 # make sure it's unique (same method as nautilus, hopefully) while os.path.exists(trash_filename): fn,ext = os.path.splitext(trash_filename) trash_filename = fn + "." + str(counter) + ext counter += 1 try: print "Trashing ", filepath, "as", trash_filename os.rename(filepath, trash_filename) trash_info_filename = os.path.join(TRASH_INFOS, os.path.basename(trash_filename) + '.trashinfo') f = open(trash_info_filename, 'w') print >>f, "[Trash Info]" print >>f, "Path=%s" % filepath print >>f, "DeletionDate=%s" % time.strftime("%Y-%m-%dT%H:%M:%S") f.close() except Exception, e: print "There was an error trashing file: ", filepath, e # global callback for file activation def on_activate_file(widget, data=None): print " on_file_activate!", data devnull = open("/dev/null", "w") p = subprocess.Popen((OPEN_CMD, data), stdout = devnull, stderr = devnull) devnull.close() def on_root_item_rename(widget, data=None): print "RENAME", widget, data #data.root_menuitem.get_children()[0].set_label("TESTING") #TODO: implement with popup dialog #FIXME: pass GMenuFileListManager into this function def on_root_item_remove(widget, data=None): print "Remove", widget, data data.uimanager.remove_ui(data.merge_id) #FIXME: pass GMenuFileListManager into this function, and properly remove 'data'... def on_root_menuitem_context_menu(widget, event, gfilelist, pshell, pitem): print "on_root_menuitem_context_menu()" if event.button == 3: # print "IT's A RIGHT CLICK!!" menu = gtk.Menu() title = gtk.MenuItem("Manage") title.set_sensitive(False) menu.append(title) menu.append(gtk.SeparatorMenuItem()) openroot = gtk.ImageMenuItem(gtk.STOCK_OPEN) openroot.connect("activate", on_activate_file, gfilelist.path) rename = gtk.ImageMenuItem(gtk.STOCK_EDIT) rename.get_children()[0].set_label("Change Label") rename.connect("activate", on_root_item_rename, gfilelist) rename.set_sensitive(False) # not implemented remove = gtk.ImageMenuItem(gtk.STOCK_REMOVE) remove.get_children()[0].set_label("Remove") remove.get_children()[0].set_use_underline(False) remove.connect("activate", on_root_item_remove, gfilelist) #delete.set_sensitive(False) #delete.connect("activate", on_delete_root, data) menu.append(openroot) menu.append(rename) menu.append(remove) cancel = gtk.ImageMenuItem(gtk.STOCK_CANCEL) # for now #sort.set_sensitive(False) # not implemented menu.append(cancel) # menu.attach_to_widget(pitem,menu.destroy) menu.show_all() menu.popup(pshell, pitem, None, event.button, event.time) return True #consume return False #propagate def on_media_context_menu(widget, event, label, pshell, pitem): print "on_media_context_menu()" if event.button == 3: # print "IT's A RIGHT CLICK!!" menu = gtk.Menu() title = gtk.MenuItem(label) title.set_sensitive(False) menu.append(title) menu.append(gtk.SeparatorMenuItem()) sort = gtk.MenuItem("Sort by Name (NA)") #sort.set_sensitive(False) # not implemented.. BUT can't get rid of it unless ESC over sensitive item menu.append(sort) # menu.attach_to_widget(pitem,menu.destroy) menu.show_all() menu.popup(pshell, pitem, None, event.button, event.time) return True #consume return False #propagate def on_file_context_menu(widget, event, filepath, pshell, pitem): print "on_file_context_menu()" if event.button == 3: #print "IT's A RIGHT CLICK!!" menu = gtk.Menu() title = gtk.MenuItem("File") title.set_sensitive(False) menu.append(title) menu.append(gtk.SeparatorMenuItem()) delete = gtk.ImageMenuItem(gtk.STOCK_DELETE) delete.connect("activate", on_delete_file, filepath) openfile = gtk.ImageMenuItem(gtk.STOCK_OPEN) openfile.connect("activate", on_activate_file, filepath) props = gtk.ImageMenuItem(gtk.STOCK_PROPERTIES) props.set_sensitive(False) menu.append(openfile) menu.append(delete) menu.append(props) # menu.attach_to_widget(pitem,menu.destroy) menu.show_all() menu.popup(pshell, pitem, None, event.button, event.time) return True return False def on_delete_file(widget, filepath): trash(filepath) iconcache = [] # hack for speedier icon searching ui = """ """ ui_monitor_popup = """ """ uimanager = None def get_icon(icon_theme, media, subtype=None, ext=None, size=24): #TODO: use extension, ext information, if given "Returns a gtk.Image with a best-guess mimetype icon according to media and subtype." image = gtk.Image() lookup = media global iconcache l = [] if subtype: l = filter(lambda i: subtype in i, iconcache) #print subtype, "l: ", l #DEBUG if len(l) > 1: lookup = l[0] #first element from list elif media == "audio": lookup = "sound" elif media == "inode": lookup = "folder" try: image.set_from_pixbuf(icon_theme.load_icon(lookup, size, 0)) except: image.set_from_pixbuf(icon_theme.load_icon("empty", size, 0)) return image class ErrorMessageDialog(gtk.Dialog): """Simple Dialog with a Close button to relay messages. Takes a title and message as arguments.""" def __init__(self, title, message, *args, **kwargs): super(ErrorMessageDialog, self).__init__(title, buttons=(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE), *args, **kwargs) self.vbox.pack_start(gtk.Label(message)) self.vbox.show_all() self.action_area.show_all() class GMenuFileList(FileTypeList): def __init__(self, path, uimanager, actiongroup): super(GMenuFileList, self).__init__(path) #GUI stuff # action for root menu item (Folder) label = os.path.basename(path) counter = 1 while uimanager.get_widget('/MenuBar/MonitorsMenu/placeholder/'+label): label = os.path.basename(path) + str(counter) counter = counter + 1 action_name = path action = gtk.Action(name = action_name, label = label, # label tooltip = "tooltip", stock_id = gtk.STOCK_OPEN) #action.connect("activate", self.on_monitor_activate, path) #TODO - context menu actiongroup.add_action( action ) # add the root menu item using the uimanagaer self.merge_id = uimanager.new_merge_id() uimanager.add_ui(self.merge_id, #merge_id to use '/MenuBar/MonitorsMenu/placeholder', #path label, #name action_name, #action gtk.UI_MANAGER_MENUITEM, False) #type, top uimanager.ensure_update() root_menuitem = uimanager.get_widget('/MenuBar/MonitorsMenu/placeholder/'+label) root_menu = uimanager.get_widget('/MenuBar/MonitorsMenu/').get_submenu() # print "root_menu: ", root_menu # print "item's parent: ", root_menuitem.get_parent() root_menuitem.get_children()[0].set_use_underline(False) root_menuitem.connect("button-press-event", on_root_menuitem_context_menu, self, root_menu, root_menuitem) self.label = label self.root_menu = root_menu self.root_menuitem = root_menuitem self.uimanager = uimanager self.action_name = action_name self.action = action self.actiongroup = actiongroup self.update_all_menus() # build menus from scratch def get_menuitem_by_label(self, label): #TODO pass def update_all_menus(self): print "update_all_menus" # debug try: self.media_menu = self.build_media_menu() self.root_menuitem.set_submenu(self.media_menu) self.root_menuitem.show_all() except: print "Exception Occurred!" pass # the object may not be fully constructed yet def add_file_gui(self, filepath): super(GMenuFileList, self).add_file(filepath) self.update_all_menus() print "done update_all_menus" def remove_file_gui(self, filepath): super(GMenuFileList, self).remove_file(filepath) self.update_all_menus() print "done update_all_menus" def update_file_gui(self, filepath): super(GMenuFileList, self).update_file(filepath) self.update_all_menus() print "done update_all_menus" def build_file_menu(self, m, icon_theme=None): "Returns a GtkMenu class with all files of type m. l = FileTypeList, m=media (string)" filelist = gtk.Menu() if not icon_theme: icon_theme = gtk.icon_theme_get_default() # Build the file list for f in self.get_files_by_media(m): fileitem = gtk.ImageMenuItem(gtk.STOCK_OPEN, None) fileitem.get_children()[0].set_label("%s" % (f)) fileitem.get_children()[0].set_use_underline(False) fileitem.set_image(get_icon(icon_theme, m, self.get_subtype(f))) fileitem.connect("activate", on_activate_file, os.path.join(self.path,f) ) fileitem.connect("button-press-event", on_file_context_menu, os.path.join(self.path,f), self.root_menu, fileitem) # fileitem.connect("button-press-event", on_file_context_menu, os.path.join(self.path,f), filelist, fileitem) # fileitem.connect("button-press-event", on_file_context_menu, os.path.join(self.path,f), None, fileitem) fileitem.set_tooltip_text( self.get_comment(f).title() ) filelist.append(fileitem) return filelist def build_media_menu(self, menu=None, icon_theme=None): if not menu: menu = gtk.Menu() if not icon_theme: icon_theme = gtk.icon_theme_get_default() media_with_counts = self.get_media_counts() if len(media_with_counts) <= 0: description = gtk.MenuItem("No files",False) description.set_sensitive(False) menu.append(description) elif len(media_with_counts) == 1: menu = self.build_file_menu( media_with_counts[0][0], icon_theme) else: # separate folders from files if "inode" in zip(*media_with_counts)[0]: menu.prepend(gtk.SeparatorMenuItem()) # Build the media list for m,c in media_with_counts: label = m if m == "inode": label = "folder" subitem = gtk.ImageMenuItem(gtk.STOCK_OPEN, None) subitem.get_children()[0].set_label("%s (%i)" % (label.title(), c)) subitem.set_image(get_icon(icon_theme, m)) #Now populate the filelist for this media filelist = self.build_file_menu(m, icon_theme) subitem.connect("button-press-event", on_media_context_menu, label.title(), self.root_menu, subitem) # subitem.connect("button-press-event", on_media_context_menu, label.title(), None, subitem) filelist.show_all() subitem.set_submenu(filelist) st = self.get_subtypes(m) subitem.set_tooltip_text("%i Subtypes: %s" % (len(st), ", ".join(st).title() )) if label == "folder": menu.prepend(subitem) else: menu.append(subitem) menu.show_all() return menu class GMenuFileListManager(object): def __init__(self): super(GMenuFileListManager, self).__init__() self.filelists = [] def get_filelist_by_path(self, path): for t in self.filelists: if t[1].path == path: return t[1] # path not in filelists raise ValueError() def get_label_by_path(self, path): for t in self.filelists: if t[1].path == path: return t[0] # path not in filelists raise ValueError() def get_label_filelist_by_path(self, path): "Returns the tuple: (label, filelist)." for t in self.filelists: if t[1].path == path: return t # path not in filelists raise ValueError() def add(self, filelist): # append the (label, filelist) tuple to self.filelists self.filelists.append( (os.path.basename(filelist.path), filelist)) #TODO: update GUI def remove(self, path): for t in self.filelists: if t[1].path == path: del t #TODO: update GUI # path not in filelists raise ValueError() def path_exists(self, path): for t in self.filelists: if t[1] == path: return True return False def change_label(self, path, label): t = self.get_label_filelist_by_path(path) t[0] = label # + remove(l) # + add(GMenuFileList l) # + change_label(l) # + update_summary() # + get_filelist_by_index # # + get_filelist_by_path # should be unique # + get_filelist_by_label # returns first found # + build_summary() # + update_summary() # takes main menu as argument? # [(label, GMenuFileList), (label, GMenuFileList) ... ] ## allow these formats: ## filelist = self.filelists[path] ## if path in self.filelists .... class FileListApplet(GnomePanelApplet): baiid = "OAFIID:FileListApplet_Factory" description = "Browse files of any folder by type and modification time" version = "0.0" def __init__(self, *args, **kwargs): #super(FileListApplet, self).__init__(*args, **kwargs) self.__gobject_init__() def default_action(self, dialog, link, user_data): print "default_action: ", dialog, link, user_data if user_data: link = user_data + link # add protocol, if specified. on_activate_file(dialog, link) def file_updated(self, filepath, action): "Updates the gui appropriately." path,filename = os.path.split(filepath) if self.filelists.path_exists(path): print "file '%s' not being monitored." % filepath # should never happen return print "file_updated() file=%s, action=%s" % (filepath, action) # debug filelist = self.filelists.get_filelist_by_path(path) if "create" in action: filelist.add_file_gui(filename) elif "modify" in action: filelist.update_file_gui(filename) elif "delete" in action: filelist.remove_file_gui(filename) #TODO - EFFICIENTLY update gui def remove_folder(self, path): #TODO pass def update_folder(self, path): #TODO pass def add_folder(self, path, recursive): "Adds path to list of paths to monitor." if os.path.exists(path): if self.filelists.path_exists(path): # This folder has already been added. print "This folder has already been added. Skipping." return l = GMenuFileList(path, self.uimanager, self.actiongroup) self.filelists.add(l) self.wm.add_watch(path, self.wm_mask, rec=recursive) #TODO: Add "Recently Modified >" section and separator else: #FIXME: properly handle error.. print "'%s' is not a valid path." %path def remove_folder(self, path): "Removes an existing monitor." #TODO: implement pass def applet_factory(self, iid): # enclose Process Monitor class within applet_factory() method class PMon(pyinotify.ProcessEvent): def __init__(pmonself): pmonself.last_modified = None # absolute filename of last modified file pmonself.applet = self # the applet for when updates are needed pmonself.last_gui_update = time.time() # time of last successful update_ui() call def update_ui(pmonself, path, action): if not pmonself.applet: return # easy there, big fella.... # if (time.time() - pmonself.last_gui_update) < 0.5: # return # TODO: Add to event QUEUE print "enter threads" gtk.gdk.threads_enter() pmonself.applet.file_updated(path, action) gtk.gdk.threads_leave() print "exit threads" pmonself.last_gui_update = time.time() def process_IN_CREATE(pmonself, event): print "CREATE: %s" % os.path.join(event.path, event.name) if event.name[0] == ".": return pmonself.update_ui( os.path.join(event.path, event.name), "create") def process_IN_DELETE(pmonself, event): print "DELETE: %s" % os.path.join(event.path, event.name) if event.name[0] == ".": return pmonself.update_ui( os.path.join(event.path, event.name), "delete") def process_IN_MODIFY(pmonself, event): absfile = os.path.join(event.path, event.name) print "MODIFY: %s" % absfile if event.name[0] == ".": return if pmonself.last_modified and pmonself.last_modified == absfile: return # no changes to sorting pmonself.update_ui( os.path.join(event.path, event.name), "modify") pmonself.last_modified = absfile def process_IN_MOVED_FROM(pmonself, event): print "MOVED FROM:" print " "+event.name if event.name[0] == ".": return pmonself.update_ui( os.path.join(event.path, event.name), "delete") #FIXME: kind of hackish def process_IN_MOVED_TO(pmonself, event): print "MOVED TO:" print " "+event.name if event.name[0] == ".": return pmonself.update_ui( os.path.join(event.path, event.name), "create") #FIXME: kind of hackish def process_default(pmonself, event): print "PROCESS DEFAULT EVENT:" print " "+event.name if event.name[0] == ".": return # custom processing for default event goes here # resume regular scheduled applet_factory() method.... # inotify self.wm = pyinotify.WatchManager() self.wm_mask = pyinotify.EventsCodes.IN_DELETE | \ pyinotify.EventsCodes.IN_CREATE | \ pyinotify.EventsCodes.IN_MODIFY | \ pyinotify.EventsCodes.IN_MOVED_FROM | \ pyinotify.EventsCodes.IN_MOVED_TO self.notifier = pyinotify.ThreadedNotifier(self.wm, PMon()) self.notifier.start() # folder selection dialog self.dialog_select_folder = gtk.FileChooserDialog("Select a Folder to Monitor...", None, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) self.dialog_select_folder.set_default_response(gtk.RESPONSE_OK) # about dialog gtk.about_dialog_set_url_hook(self.default_action, None) gtk.about_dialog_set_email_hook(self.default_action, "mailto:") self.dialog_about = gtk.AboutDialog() self.dialog_about.set_name(PROGRAM_NAME) self.dialog_about.set_version(VERSION) self.dialog_about.set_copyright(COPYRIGHT) self.dialog_about.set_license(LICENSE) self.dialog_about.set_authors(AUTHORS) self.dialog_about.set_comments(COMMENTS) self.dialog_about.set_website(WEBSITE) self.dialog_about.set_website_label("Homepage") self.dialog_about.set_logo_icon_name(gtk.STOCK_OPEN) # prefs dialog - placeholder self.prefs_dialog = ErrorMessageDialog("Not Yet Implemented", # title "Sorry... This hasn't been implemented yet. :(") # message # dict of GMenuFileList objects, folder-paths are keys #self.filelists = {} #TODO: use GMenuFileListManager self.filelists = GMenuFileListManager() # icons global iconcache icon_theme = gtk.icon_theme_get_default() if not iconcache: iconcache = icon_theme.list_icons() gtk.window_set_default_icon_name(gtk.STOCK_OPEN) self.label = gtk.Label("No Message Yet") self.connect("destroy", self._on_applet_destroy) # Actions actiongroup = gtk.ActionGroup("RadioSortMethod") self.actiongroup = actiongroup actiongroup.add_radio_actions([('SortByModTime', None, 'Sort by Modification _Time (Recent First)', None, 'Sort by modification time', 0), ('SortByName', gtk.STOCK_SORT_ASCENDING, 'Sort by _Name', None, 'Sort by filename', 1) ], 0, self.on_sort_toggle) #default, callback actiongroup.add_actions([('Add Monitor', gtk.STOCK_ADD, '_Add Folder', None, 'Add New Monitor', self.on_add_folder), ('Monitors', gtk.STOCK_OPEN, 'Files'), ('Manage', gtk.STOCK_PREFERENCES, '_Manage'), ('RadioSortMethod', gtk.STOCK_SORT_ASCENDING, '') ]) global label label = self.label #self.set_applet_flags(gnomeapplet.EXPAND_MINOR) #Causes border to appear global uimanager uimanager = gtk.UIManager() uimanager.insert_action_group(actiongroup, 0) uimanager.add_ui_from_string(ui) self.uimanager = uimanager self.menubar = uimanager.get_widget("ui/MenuBar") print "menubar: ", self.menubar #print "parent: ", self.menubar self.menubar.connect("button-press-event", self.on_menubar_press) # Drag'n Drop implementation TARGET_STRING, TARGET_ROOTWIN = 0,1 targets = ( ('STRING', 0, TARGET_STRING), ('text/plain', 0, TARGET_STRING), ('application/x-rootwin-drop', 0, TARGET_ROOTWIN) ) self.drag_dest_set(gtk.DEST_DEFAULT_ALL, targets, gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE) self.connect('drag_data_received', self.on_applet_drag_data_received) # Remove gaps # gtk.rc_parse_string(''' # style "applet-style" # { # GtkWidget::focus-line-width = 0 # GtkWidget::inner-border = 0 # GtkWidget::internal-padding = 0 # GtkWidget::focus-padding = 0 # GtkWidget::shadow-type = none # bg[NORMAL] = "#006600" # } # style "item-style" # { # bg[NORMAL] = "#660000" # } # #class "*Applet*" style "applet-style" # class "*GtkMenuBar*MenuItem*" style "item-style" # class "*MenuBar*" style "applet-style"''') gtk.rc_parse_string(''' style "menubar-applet-style" { GtkWidget::focus-padding = 0 GtkWidget::focus-line-width = 5 GtkWidget::interior-focus = 1 #GtkWidget::focus-line-pattern = none #GtkMenuBar::shadow-type = 0 #GtkMenuItem::horizontal-padding = 3 #GtkMenuBar::internal-padding = 0 #GtkWidget::inner-border = 0 } widget "*.gimmesomestyle" style "menubar-applet-style"''') self.menubar.set_name("gimmesomestyle") self.menubar.connect("size-allocate", self.on_menubar_size_allocate) context_menu_xml = """ """ self.setup_menu(context_menu_xml, [("Prefs", self.on_prefs), ("About", self.on_about)], None) self.add(self.menubar) self.show_all() return True # return applet_factory() def on_prefs(self, bbuicomponent, data=None): print "on_prefs!!" r = self.prefs_dialog.run() print "result:", r self.prefs_dialog.hide() def on_about(self, bbuicomponent, data=None): response = self.dialog_about.run() if response in (gtk.RESPONSE_CLOSE, gtk.RESPONSE_CANCEL): pass self.dialog_about.hide() def on_menubar_press(self, widget, event): if event.button != 1: #print "right or middle" #return True # Consume event? #<--- Doesn't work widget.emit_stop_by_name("button-press-event") # Must emit_stop_by_name #return False #print "left" return False # Propagate event # This little hack is required to get Fitt's Law compliance - # i.e. to get the Menubar to be the full height of the panel. # THANK YOU, browser-bookmarks-menu applet. ;) def on_menubar_size_allocate(self, menubar, rect): if (rect.x <= 0) or (rect.y <= 0): return False rect.x -= 1 rect.y -= 1 rect.width += 2 rect.height += 2 gtk.Widget.size_allocate(menubar, rect) return False def on_applet_drag_data_received(self, w, context, x, y, data, info, time): if data and data.format == 8: #data is a GtkSelectionData object path = urlparse(data.data)[2] path = unquote(path).strip() if os.path.exists(path) and os.path.isdir(path): self.add_folder(path, False) context.finish(True, False, time) #successful drop at time, don't delete source else: if data: print "dropped data not recognized: " print " get_text:", data.get_text() print " get_targets:", data.get_targets() print " get_uris:", data.get_uris() print " targets_include_text()", data.targets_include_text() print " targets_include_image()", data.targets_include_image() print " targets_include_uri()", data.targets_include_uri() print " targets_include_rich_text()", data.targets_include_rich_text() print " tree_get_row_drag_data()", data.tree_get_row_drag_data() print " get_pixbuf()", data.get_pixbuf() context.finish(False, False, time) #unsuccessful drop at time, don't delete source def on_sort_toggle(self, radioaction, current): print "on sort toggle" print "radioaction: ", radioaction print "current: ", current #TODO: Implement, add icon for mod-sort using 'stock_form-time-field' def on_add_folder(self, b): print "on_add_folder: ", b response = self.dialog_select_folder.run() if response == gtk.RESPONSE_OK: folder = self.dialog_select_folder.get_filename() print "Adding '%s' to monitors..." % folder #DEBUG self.add_folder(folder, False) elif response == gtk.RESPONSE_CANCEL: print "Cancelled. Nothing selected." #DEBUG self.dialog_select_folder.hide() def on_monitor_activate(self, action, data=None): #print "ACTIVATED! ", a #print "The path: ", data pass def run(self, windowed=False): GnomePanelApplet.run(self, windowed) def _cleanup_before_destroy(self): print "safe cleanup....Please, wait..." self.label.set_text("stopping notifier...") self.queue_draw() #gtk.gdk.flush() self.notifier.stop() print "shutting down..." self.label.set_text("Shutting Down...") gtk.gdk.flush() gobject.type_register(FileListApplet) def applet_init(applet, iid): print "applet_init()" print applet, iid return applet.applet_factory(iid) def run_applet(windowed=False): print "run_applet()" print "windowed = ", if windowed: print windowed applet = FileListApplet() main_window = gtk.Window(gtk.WINDOW_TOPLEVEL) main_window.set_title("File List Applet") main_window.set_default_size(200,24) main_window.connect("destroy", gtk.main_quit) applet.applet_factory(None) applet.reparent(main_window) main_window.show_all() gtk.gdk.threads_init() # Important gtk.main() sys.exit() else: print windowed gtk.gdk.threads_init() # Important gnomeapplet.bonobo_factory( FileListApplet.baiid, \ FileListApplet.__gtype__, \ FileListApplet.description, \ FileListApplet.version, \ applet_init) if __name__ == "__main__": if len(sys.argv) > 2: # It's either the bonobo factory, or an error if "--oaf-activate-iid="+FileListApplet.baiid in sys.argv: run_applet(windowed=False) # ran from bonobo factory else: print "Too many arguments. Either 'window' as an argument or none at all." sys.exit() elif len(sys.argv) == 2: if "window" in sys.argv[1].lower(): #app.run(windowed=True) run_applet(windowed=True) # ran explicitly in a window else: print "Invalid argument: ", sys.argv[1] sys.exit() else: #len(sys.argv) == 1: print "Ran directly from console..." run_applet(windowed=False) # ran directly from console