#!/usr/bin/env python ''' This IconView widget shows the contents of the currently selected directory on the disk. it is based on a tutorial from ZetCode PyGTK tutorial the original code is under the BSD license author: jan bodnar website: zetcode.com Copyright 2012 Norbert Schechner nieson@web.de 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 2 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, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ''' import gtk import gobject import os import mimetypes import gio # constants _ASCENDING = 0 _DESCENDING = 1 _FOLDERFIRST = 2 _FILEFIRST = 3 COL_PATH = 0 COL_PIXBUF = 1 COL_IS_DIRECTORY = 2 # prepared for localization import gettext _ = gettext.gettext # icon_size must be an integer, inbetween 12 and 96, default is 48 # start_dir is the start directory, given as a string like "~/linuxcnc-dev/configs/sim/gmoccapy/nc_files" # user_dir is a user defined directory to jump to, given as a string like "/media" # filetypes is a comma separated string, giving the extensions of the files to be shown in the widget # like "ngc,py,png,hal" # sortorder one of ASCENDING, DESCENDING, FOLDERFIRST, FILEFIRST class IconFileSelection(gtk.HBox): # ToDo: # - make the button up and down work to move faster from top to bottom # unfortuantely the selection of column is not availible in pygtk before 2.22 __gtype_name__ = 'IconFileSelection' __gproperties__ = { 'icon_size' : (gobject.TYPE_INT, 'Icon Size', 'Sets the size of the displayed icon', 12, 96, 48, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 'start_dir' : (gobject.TYPE_STRING, 'start directory', 'Sets the directory to start in', "/", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 'jump_to_dir' : (gobject.TYPE_STRING, 'jump to directory', 'Sets the directory to jump to ', "~", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 'filetypes' : (gobject.TYPE_STRING, 'file filter', 'Sets the filter for the file types to be shown', "ngc,py", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), 'sortorder' : (gobject.TYPE_INT, 'sorting order', '0 = ASCENDING, 1 = DESCENDING", 2 = FOLDERFIRST, 3 = FILEFIRST', 0, 3, 2, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT), } __gproperties = __gproperties__ __gsignals__ = { 'selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), 'sensitive': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_BOOLEAN,)), 'exit': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()), } def __init__(self): super(IconFileSelection, self).__init__() # set some default values' self.icon_size = 48 self.start_dir = os.path.expanduser('/') self.cur_dir = self.start_dir self.user_dir = os.path.expanduser('~') self.jump_to_dir = os.path.expanduser('/tmp') self.filetypes = ("ngc,py") self.sortorder = _FOLDERFIRST # This will hold the path we will return self.path = "" self.button_state = {} self.old_button_state = {} # Make the GUI and connect signals vbox = gtk.VBox(False, 0) self.buttonbox = gtk.HButtonBox() self.buttonbox.set_layout(gtk.BUTTONBOX_EDGE) self.buttonbox.set_property("homogeneous", True) vbox.pack_end(self.buttonbox, False, False, 0) self.btn_home = gtk.Button() self.btn_home.set_size_request(56, 56) image = gtk.Image() image.set_from_stock(gtk.STOCK_HOME, 48) self.btn_home.set_image(image) self.btn_home.set_tooltip_text(_("Move to your home directory")) self.buttonbox.add(self.btn_home) self.btn_dir_up = gtk.Button(); self.btn_dir_up.set_size_request(56, 56) image = gtk.Image() image.set_from_stock(gtk.STOCK_GOTO_TOP, 48) self.btn_dir_up.set_image(image) self.btn_dir_up.set_tooltip_text(_("Move to parent directory")) self.buttonbox.add(self.btn_dir_up) self.btn_sel_prev = gtk.Button() self.btn_sel_prev.set_size_request(56, 56) image = gtk.Image() image.set_from_stock(gtk.STOCK_GO_BACK, 48) self.btn_sel_prev.set_image(image) self.btn_sel_prev.set_tooltip_text(_("Select the previous file")) self.buttonbox.add(self.btn_sel_prev) self.btn_sel_next = gtk.Button() self.btn_sel_next.set_size_request(56, 56) image = gtk.Image() image.set_from_stock(gtk.STOCK_GO_FORWARD, 48) self.btn_sel_next.set_image(image) self.btn_sel_next.set_tooltip_text(_("Select the next file")) self.buttonbox.add(self.btn_sel_next) # ToDo : Find out how to move one line down or up # self.btn_go_down = gtk.Button() # self.btn_go_down.set_size_request(56,56) # image = gtk.Image() # image.set_from_stock(gtk.STOCK_GO_DOWN,48) # self.btn_go_down.set_image(image) # self.buttonbox.add(self.btn_go_down) # # self.btn_go_up = gtk.Button() # self.btn_go_up.set_size_request(56,56) # image = gtk.Image() # image.set_from_stock(gtk.STOCK_GO_UP,48) # self.btn_go_up.set_image(image) # self.buttonbox.add(self.btn_go_up) # ToDo : End self.btn_jump_to = gtk.Button() self.btn_jump_to.set_size_request(56, 56) image = gtk.Image() image.set_from_stock(gtk.STOCK_JUMP_TO, 48) self.btn_jump_to.set_image(image) self.btn_jump_to.set_tooltip_text(_("Jump to user defined directory")) self.buttonbox.add(self.btn_jump_to) self.btn_select = gtk.Button() self.btn_select.set_size_request(56, 56) image = gtk.Image() image.set_from_stock(gtk.STOCK_OK, 48) self.btn_select.set_image(image) self.btn_select.set_tooltip_text(_("select the highlighted file and return the path")) self.buttonbox.add(self.btn_select) self.btn_exit = gtk.Button() self.btn_exit.set_size_request(56, 56) image = gtk.Image() image.set_from_stock(gtk.STOCK_STOP, 48) self.btn_exit.set_image(image) self.btn_exit.set_tooltip_text(_("Close without returning a file path")) self.buttonbox.add(self.btn_exit) self.dirIcon = self._get_icon("folder") sw = gtk.ScrolledWindow() sw.set_shadow_type(gtk.SHADOW_ETCHED_IN) sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) vbox.pack_start(sw, True, True, 0) self.file_label = gtk.Label("File Label") vbox.pack_start(self.file_label, False, True, 0) self.store = self._create_store() self.iconView = gtk.IconView(self.store) self.iconView.set_selection_mode(gtk.SELECTION_SINGLE) self.iconView.set_text_column(COL_PATH) self.iconView.set_pixbuf_column(COL_PIXBUF) sw.add(self.iconView) self.iconView.grab_focus() self.model = self.iconView.get_model() self.btn_dir_up.connect("clicked", self.on_btn_dir_up_clicked) self.btn_home.connect("clicked", self.on_btn_home_clicked) self.btn_sel_next.connect("clicked", self.on_btn_sel_next_clicked) self.btn_sel_prev.connect("clicked", self.on_btn_sel_prev_clicked) # self.btn_go_down.connect("clicked", self.on_btn_go_down_clicked) # self.btn_go_up.connect("clicked", self.on_btn_go_up_clicked) self.btn_jump_to.connect("clicked", self.on_btn_jump_to_clicked) self.btn_select.connect("clicked", self.on_btn_select_clicked) self.btn_exit.connect("clicked", self.on_btn_exit_clicked) # will be emitted, when a icon has been activated, so we keep track of the path self.iconView.connect("item-activated", self._on_item_activated) # will be emitted, when a icon is activated and the ENTER key has been pressed self.iconView.connect("activate-cursor-item", self._on_activate_cursor_item) # will be emmited if the selection has changed, this happens also if the user clicks ones on an icon self.iconView.connect("selection-changed", self._on_selection_changed) # will be emitted, when the widget is destroyed self.connect("destroy", gtk.main_quit) self.add(vbox) self.show_all() # To use the the events, we have to unmask them self.iconView.add_events(gtk.gdk.BUTTON_PRESS_MASK) self.iconView.connect("button_press_event", self._button_press) self._fill_store() self._init_button_state() def _init_button_state(self): # we need this to check for differnces in the button state self.button_state["btn_home"] = self.btn_home.get_sensitive() self.button_state["btn_dir_up"] = self.btn_dir_up.get_sensitive() self.button_state["btn_sel_prev"] = self.btn_sel_prev.get_sensitive() self.button_state["btn_sel_next"] = self.btn_sel_next.get_sensitive() self.button_state["btn_jump_to"] = self.btn_jump_to.get_sensitive() self.button_state["btn_select"] = self.btn_select.get_sensitive() self.old_button_state = self.button_state.copy() # With the left mouse button and a dobble click, the file can be selected def _button_press(self, widget, event): # left button used? if event.button == 1: # dobble click? if event.type == gtk.gdk._2BUTTON_PRESS: self.btn_select.emit("clicked") def _on_activate_cursor_item(self, widget): self.btn_select.emit("clicked") def _get_icon(self, name): theme = gtk.icon_theme_get_default() if name == "folder": name = gtk.STOCK_DIRECTORY else: mime = gio.content_type_guess(name) if mime: iconname = gio.content_type_get_icon(mime) icon = theme.choose_icon(iconname.get_names(), self.icon_size, 0) if icon: return gtk.IconInfo.load_icon(icon) else: name = gtk.STOCK_FILE else: name = gtk.STOCK_FILE return theme.load_icon(name, self.icon_size, 0) def _create_store(self): store = gtk.ListStore(str, gtk.gdk.Pixbuf, bool) return store def _fill_store(self): if self.cur_dir == None: return try: self.store.clear() number = 0 dirs = [] files = [] for fl in os.listdir(self.cur_dir): # we don't want to add hidden files if fl[0] == '.': continue if os.path.isdir(os.path.join(self.cur_dir, fl)): try: os.listdir(os.path.join(self.cur_dir, fl)) dirs.append(fl) number += 1 except OSError: #print ("no rights for ", os.path.join(self.cur_dir, fl), " skip that dir") continue else: try: name, ext = fl.rsplit(".", 1) if "*" in self.filetypes: files.append(fl) number += 1 elif ext in self.filetypes: files.append(fl) number += 1 except: pass if self.sortorder not in [_ASCENDING, _DESCENDING, _FOLDERFIRST, _FILEFIRST]: self.sortorder = _FOLDERFIRST if self.sortorder == _ASCENDING or self.sortorder == _DESCENDING: allobjects = dirs allobjects.extend(files) allobjects.sort(cmp = None, key = None, reverse = not self.sortorder == _ASCENDING) for obj in allobjects: if os.path.isdir(os.path.join(self.cur_dir, obj)): self.store.append([obj, self.dirIcon, True]) else: icon = self._get_icon(obj) self.store.append([obj, icon, False]) dirs.sort(cmp = None, key = None, reverse = False) files.sort(cmp = None, key = None, reverse = False) if self.sortorder == _FOLDERFIRST: for dir in dirs: self.store.append([dir, self.dirIcon, True]) for file in files: icon = self._get_icon(file) self.store.append([file, icon, False]) elif self.sortorder == _FILEFIRST: for file in files: icon = self._get_icon(file) self.store.append([file, icon, False]) for dir in dirs: self.store.append([dir, self.dirIcon, True]) except: pass finally: # check the stat of the button and set them as they should be self.check_button_state() def check_button_state(self): if self.model.get_iter_first() == None: state = False else: state = True self.btn_sel_next.set_sensitive(state) self.button_state["btn_sel_next"] = state self.btn_sel_prev.set_sensitive(state) self.button_state["btn_sel_prev"] = state self.btn_select.set_sensitive(state) self.button_state["btn_select"] = state state = self.cur_dir == self.user_dir self.btn_home.set_sensitive(not state) self.button_state["btn_home"] = not state state = self.cur_dir == "/" self.btn_dir_up.set_sensitive(not state) self.button_state["btn_dir_up"] = not state state = self.cur_dir == self.jump_to_dir self.btn_jump_to.set_sensitive(not state) self.button_state["btn_jump_to"] = not state state = self.iconView.get_cursor() == None self.btn_select.set_sensitive(not state) self.button_state["btn_select"] = not state self.state_changed() def state_changed(self): # find the differnce diff = set(self.button_state.iteritems()) - set(self.old_button_state.iteritems()) for key in self.button_state.keys(): try: if self.button_state[key] != self.old_button_state[key]: self.emit("sensitive",key, self.button_state[key]) except: continue self.old_button_state = self.button_state.copy() def show_buttonbox(self, state): if state: self.buttonbox.show() else: self.buttonbox.hide() def show_filelabel(self, state): if state: self.file_label.show() else: self.file_label.hide() def on_btn_home_clicked(self, widget): self.cur_dir = self.user_dir self._fill_store() def on_btn_jump_to_clicked(self, widget): self.cur_dir = self.jump_to_dir self._fill_store() def get_iter_last(self, model): itr = model.get_iter_first() last = None while itr: last = itr itr = model.iter_next(itr) return last def on_btn_sel_prev_clicked(self, data): if not self.btn_sel_prev.is_sensitive(): return None try: actual = self.iconView.get_cursor()[0] iter = self.model.get_iter(actual) pos = int(self.model.get_string_from_iter(iter)) first = int(self.model.get_string_from_iter(self.model.get_iter_first())) pos = pos - 1 if pos < first: pos = int(self.model.get_string_from_iter(self.get_iter_last(self.model))) new_iter = self.model.get_iter_from_string(str(pos)) new_path = self.model.get_path(new_iter) except: new_iter = self.get_iter_last(self.model) new_path = self.model.get_path(new_iter) self.iconView.set_cursor(new_path) self.iconView.select_path(new_path) self.check_button_state() def on_btn_sel_next_clicked(self, data): if not self.btn_sel_next.is_sensitive(): return None try: actual = self.iconView.get_cursor()[0] iter = self.model.get_iter(actual) try: new_iter = self.model.iter_next(iter) new_path = self.model.get_path(new_iter) self.iconView.set_cursor(new_path) self.iconView.select_path(new_path) except: first = int(self.model.get_string_from_iter(self.model.get_iter_first())) new_path = self.model.get_path(first) self.iconView.set_cursor(new_path) self.iconView.select_path(new_path) except: self.iconView.set_cursor(0) self.iconView.select_path(0) self.check_button_state() # ToDo: find out how to move a line down or up # def on_btn_go_down_clicked(self,data): # print("This is the number of the icon selected", self.iconView.get_cursor()) # col = self.iconView.get_item_column(self.iconView.get_cursor()[0]) # row = self.iconView.get_item_row(self.iconView.get_cursor()[0]) # print("This is the column :", col) # print("This is the row :", row) # print(self.iconView.get_columns()) # self.iconView.item_activated(self.iconView.get_cursor()[0]) # print("go down") # print("columns = ",self.iconView.get_columns()) # print("column spacing = ", self.iconView.get_column_spacing()) # print("item with = ", self.iconView.get_item_width()) # print("margin = ", self.iconView.get_margin()) # print("visible range = ", self.iconView.get_visible_range()) # print("get cursor = ", self.iconView.get_cursor()) # #print("item row = ", self.iconView.get_item_at_row(self.get_selected())) # print("item column = ", self.iconView.get_item_column(self.get_selected())) # # # def on_btn_go_up_clicked(self,data): # print("go up") # ToDo: end def set_icon_size(self, iconsize): try: self.icon_size = iconsize self.dirIcon = self._get_icon("folder") self._fill_store() except: pass def set_directory(self, directory): self.cur_dir = os.path.expanduser(directory) self._fill_store() def set_filetypes(self, filetypes): self.filetypes = filetypes.split(",") self._fill_store() def refresh_filelist(self): self._fill_store() def get_selected(self): return self.on_btn_select_clicked(self) def on_btn_select_clicked(self, data): try: self.iconView.item_activated(self.iconView.get_cursor()[0]) if self.path: filepath = self.cur_dir + os.path.sep + self.path self.file_label.set_text(filepath) else: self.file_label.set_text("") filepath = None self.emit('selected', filepath) except: pass def on_btn_exit_clicked(self, data): if __name__ == "__main__": gtk.main_quit() self.emit('exit') def _on_item_activated(self, widget, item): path = self.model[item][COL_PATH] isDir = self.model[item][COL_IS_DIRECTORY] if not isDir: self.path = path return self.cur_dir = self.cur_dir + os.path.sep + path self._fill_store() # This is only to advise, that the selection wasn't a file, but an directory self.path = None def _on_selection_changed(self, widget): if not self.btn_select.get_sensitive(): self.btn_select.set_sensitive(True) self.check_button_state() def on_btn_dir_up_clicked(self, widget): self.cur_dir = os.path.dirname(self.cur_dir) self._fill_store() def do_get_property(self, property): name = property.name.replace('-', '_') if name in self.__gproperties.keys(): return getattr(self, name) else: raise AttributeError('unknown iconview get_property %s' % property.name) def do_set_property(self, property, value): try: name = property.name.replace('-', '_') if name in self.__gproperties.keys(): setattr(self, name, value) self.queue_draw() if name == 'icon_size': self.set_icon_size(value) if name == 'start_dir': self.start_dir = os.path.expanduser(value) self.set_directory(self.start_dir) if name == 'jump_to_dir': self.jump_to_dir = os.path.expanduser(value) self.on_btn_jump_to() if name == 'filetypes': self.set_filetypes(value) if name == 'sortorder': if value not in [_ASCENDING, _DESCENDING, _FOLDERFIRST, _FILEFIRST]: raise AttributeError('unknown property of sortorder %s' % value) else: self.sortorder = value self._fill_store() else: raise AttributeError('unknown iconview set_property %s' % property.name) except: pass # for testing without glade editor: def main(): window = gtk.Window(gtk.WINDOW_TOPLEVEL) IFS = IconFileSelection() IFS.set_property("filetypes", "*") IFS.set_property("jump_to_dir", "/tmp") window.add(IFS) window.connect("destroy", gtk.main_quit) window.show_all() window.set_size_request(680, 480) gtk.main() if __name__ == "__main__": main()