/ lib / python / gladevcp / iconview.py
iconview.py
  1  #!/usr/bin/env python
  2  
  3  '''
  4      This IconView widget shows the contents of the currently selected
  5      directory on the disk.
  6  
  7      it is based on a tutorial from ZetCode PyGTK tutorial
  8      the original code is under the BSD license
  9      author: jan bodnar
 10      website: zetcode.com 
 11  
 12      Copyright 2012 Norbert Schechner
 13      nieson@web.de
 14  
 15      This program is free software; you can redistribute it and/or modify
 16      it under the terms of the GNU General Public License as published by
 17      the Free Software Foundation; either version 2 of the License, or
 18      (at your option) any later version.
 19  
 20      This program is distributed in the hope that it will be useful,
 21      but WITHOUT ANY WARRANTY; without even the implied warranty of
 22      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 23      GNU General Public License for more details.
 24  
 25      You should have received a copy of the GNU General Public License
 26      along with this program; if not, write to the Free Software
 27      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 28  
 29  '''
 30  
 31  import gtk
 32  import gobject
 33  import os
 34  import mimetypes
 35  import gio
 36  
 37  # constants
 38  _ASCENDING = 0
 39  _DESCENDING = 1
 40  _FOLDERFIRST = 2
 41  _FILEFIRST = 3
 42  COL_PATH = 0
 43  COL_PIXBUF = 1
 44  COL_IS_DIRECTORY = 2
 45  
 46  # prepared for localization
 47  import gettext
 48  _ = gettext.gettext
 49  
 50  # icon_size must be an integer, inbetween 12 and 96, default is 48
 51  # start_dir is the start directory, given as a string like "~/linuxcnc-dev/configs/sim/gmoccapy/nc_files"
 52  # user_dir is a user defined directory to jump to, given as a string like "/media"
 53  # filetypes is a comma separated string, giving the extensions of the files to be shown in the widget
 54  # like "ngc,py,png,hal"
 55  # sortorder one of ASCENDING, DESCENDING, FOLDERFIRST, FILEFIRST
 56  class IconFileSelection(gtk.HBox):
 57  
 58  # ToDo:
 59  # - make the button up and down work to move faster from top to bottom
 60  #   unfortuantely the selection of column is not availible in pygtk before 2.22
 61  
 62      __gtype_name__ = 'IconFileSelection'
 63      __gproperties__ = {
 64             'icon_size' : (gobject.TYPE_INT, 'Icon Size', 'Sets the size of the displayed icon',
 65                          12, 96, 48, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 66             'start_dir' : (gobject.TYPE_STRING, 'start directory', 'Sets the directory to start in',
 67                          "/", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 68             'jump_to_dir' : (gobject.TYPE_STRING, 'jump to directory', 'Sets the directory to jump to ',
 69                          "~", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 70             'filetypes' : (gobject.TYPE_STRING, 'file filter', 'Sets the filter for the file types to be shown',
 71                          "ngc,py", gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 72             'sortorder' : (gobject.TYPE_INT, 'sorting order', '0 = ASCENDING, 1 = DESCENDING", 2 = FOLDERFIRST, 3 = FILEFIRST',
 73                          0, 3, 2, gobject.PARAM_READWRITE | gobject.PARAM_CONSTRUCT),
 74                        }
 75      __gproperties = __gproperties__
 76  
 77      __gsignals__ = {
 78                      'selected': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)),
 79                      'sensitive': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gobject.TYPE_BOOLEAN,)),
 80                      'exit': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()),
 81                     }
 82  
 83      def __init__(self):
 84          super(IconFileSelection, self).__init__()
 85  
 86          # set some default values'
 87          self.icon_size = 48
 88          self.start_dir = os.path.expanduser('/')
 89          self.cur_dir = self.start_dir
 90          self.user_dir = os.path.expanduser('~')
 91          self.jump_to_dir = os.path.expanduser('/tmp')
 92          self.filetypes = ("ngc,py")
 93          self.sortorder = _FOLDERFIRST
 94          # This will hold the path we will return
 95          self.path = ""
 96          self.button_state = {}
 97          self.old_button_state = {}        
 98  
 99          # Make the GUI and connect signals
100          vbox = gtk.VBox(False, 0)
101  
102          self.buttonbox = gtk.HButtonBox()
103          self.buttonbox.set_layout(gtk.BUTTONBOX_EDGE)
104          self.buttonbox.set_property("homogeneous", True)
105          vbox.pack_end(self.buttonbox, False, False, 0)
106  
107          self.btn_home = gtk.Button()
108          self.btn_home.set_size_request(56, 56)
109          image = gtk.Image()
110          image.set_from_stock(gtk.STOCK_HOME, gtk.ICON_SIZE_LARGE_TOOLBAR)
111          self.btn_home.set_image(image)
112          self.btn_home.set_tooltip_text(_("Move to your home directory"))
113          self.buttonbox.add(self.btn_home)
114  
115          self.btn_dir_up = gtk.Button();
116          self.btn_dir_up.set_size_request(56, 56)
117          image = gtk.Image()
118          image.set_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_LARGE_TOOLBAR)
119          self.btn_dir_up.set_image(image)
120          self.btn_dir_up.set_tooltip_text(_("Move to parent directory"))
121          self.buttonbox.add(self.btn_dir_up)
122  
123          self.btn_sel_prev = gtk.Button()
124          self.btn_sel_prev.set_size_request(56, 56)
125          image = gtk.Image()
126          image.set_from_stock(gtk.STOCK_GO_BACK, gtk.ICON_SIZE_LARGE_TOOLBAR)
127          self.btn_sel_prev.set_image(image)
128          self.btn_sel_prev.set_tooltip_text(_("Select the previous file"))
129          self.buttonbox.add(self.btn_sel_prev)
130  
131          self.btn_sel_next = gtk.Button()
132          self.btn_sel_next.set_size_request(56, 56)
133          image = gtk.Image()
134          image.set_from_stock(gtk.STOCK_GO_FORWARD, gtk.ICON_SIZE_LARGE_TOOLBAR)
135          self.btn_sel_next.set_image(image)
136          self.btn_sel_next.set_tooltip_text(_("Select the next file"))
137          self.buttonbox.add(self.btn_sel_next)
138  
139  # ToDo : Find out how to move one line down or up
140  #        self.btn_go_down = gtk.Button()
141  #        self.btn_go_down.set_size_request(56,56)
142  #        image = gtk.Image()
143  #        image.set_from_stock(gtk.STOCK_GO_DOWN,48)
144  #        self.btn_go_down.set_image(image)
145  #        self.buttonbox.add(self.btn_go_down)
146  #
147  #        self.btn_go_up = gtk.Button()
148  #        self.btn_go_up.set_size_request(56,56)
149  #        image = gtk.Image()
150  #        image.set_from_stock(gtk.STOCK_GO_UP,48)
151  #        self.btn_go_up.set_image(image)
152  #        self.buttonbox.add(self.btn_go_up)
153  # ToDo : End
154  
155          self.btn_jump_to = gtk.Button()
156          self.btn_jump_to.set_size_request(56, 56)
157          image = gtk.Image()
158          image.set_from_stock(gtk.STOCK_JUMP_TO, gtk.ICON_SIZE_LARGE_TOOLBAR)
159          self.btn_jump_to.set_image(image)
160          self.btn_jump_to.set_tooltip_text(_("Jump to user defined directory"))
161          self.buttonbox.add(self.btn_jump_to)
162  
163          self.btn_select = gtk.Button()
164          self.btn_select.set_size_request(56, 56)
165          image = gtk.Image()
166          image.set_from_stock(gtk.STOCK_OK, gtk.ICON_SIZE_LARGE_TOOLBAR)
167          self.btn_select.set_image(image)
168          self.btn_select.set_tooltip_text(_("select the highlighted file and return the path"))
169          self.buttonbox.add(self.btn_select)
170  
171          self.btn_exit = gtk.Button()
172          self.btn_exit.set_size_request(56, 56)
173          image = gtk.Image()
174          image.set_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_LARGE_TOOLBAR)
175          self.btn_exit.set_image(image)
176          self.btn_exit.set_tooltip_text(_("Close without returning a file path"))
177          self.buttonbox.add(self.btn_exit)
178  
179          self.dirIcon = self._get_icon("folder")
180  
181          sw = gtk.ScrolledWindow()
182          sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
183          sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
184          vbox.pack_start(sw, True, True, 0)
185  
186          self.file_label = gtk.Label("File Label")
187          vbox.pack_start(self.file_label, False, True, 0)
188  
189          self.store = self._create_store()
190          
191          self.iconView = gtk.IconView(self.store)
192          self.iconView.set_selection_mode(gtk.SELECTION_SINGLE)
193  
194          self.iconView.set_text_column(COL_PATH)
195          self.iconView.set_pixbuf_column(COL_PIXBUF)
196  
197          sw.add(self.iconView)
198          self.iconView.grab_focus()
199          self.model = self.iconView.get_model()
200  
201          self.btn_dir_up.connect("clicked", self.on_btn_dir_up_clicked)
202          self.btn_home.connect("clicked", self.on_btn_home_clicked)
203          self.btn_sel_next.connect("clicked", self.on_btn_sel_next_clicked)
204          self.btn_sel_prev.connect("clicked", self.on_btn_sel_prev_clicked)
205  #        self.btn_go_down.connect("clicked", self.on_btn_go_down_clicked)
206  #        self.btn_go_up.connect("clicked", self.on_btn_go_up_clicked)
207          self.btn_jump_to.connect("clicked", self.on_btn_jump_to_clicked)
208          self.btn_select.connect("clicked", self.on_btn_select_clicked)
209          self.btn_exit.connect("clicked", self.on_btn_exit_clicked)
210          # will be emitted, when a icon has been activated, so we keep track of the path
211          self.iconView.connect("item-activated", self._on_item_activated)
212          # will be emitted, when a icon is activated and the ENTER key has been pressed
213          self.iconView.connect("activate-cursor-item", self._on_activate_cursor_item)
214          # will be emmited if the selection has changed, this happens also if the user clicks ones on an icon
215          self.iconView.connect("selection-changed",  self._on_selection_changed)
216          # will be emitted, when the widget is destroyed
217          self.connect("destroy", gtk.main_quit)      
218          
219          self.add(vbox)
220          self.show_all()
221  
222          # To use the the events, we have to unmask them
223          self.iconView.add_events(gtk.gdk.BUTTON_PRESS_MASK)
224          self.iconView.connect("button_press_event", self._button_press)
225          
226          self._fill_store()
227          self._init_button_state()
228          
229      def _init_button_state(self):    
230          # we need this to check for differnces in the button state
231          self.button_state["btn_home"] = self.btn_home.get_sensitive()
232          self.button_state["btn_dir_up"] = self.btn_dir_up.get_sensitive()
233          self.button_state["btn_sel_prev"] = self.btn_sel_prev.get_sensitive()
234          self.button_state["btn_sel_next"] = self.btn_sel_next.get_sensitive()
235          self.button_state["btn_jump_to"] = self.btn_jump_to.get_sensitive()
236          self.button_state["btn_select"] = self.btn_select.get_sensitive()
237          self.old_button_state = self.button_state.copy()
238          
239      # With the left mouse button and a dobble click, the file can be selected
240      def _button_press(self, widget, event):
241          # left button used?
242          if event.button == 1:
243              # dobble click?
244              if event.type == gtk.gdk._2BUTTON_PRESS:
245                  self.btn_select.emit("clicked")
246  
247      def _on_activate_cursor_item(self, widget):
248          self.btn_select.emit("clicked")
249  
250      def _get_icon(self, name):
251          theme = gtk.icon_theme_get_default()
252          if name == "folder":
253              name = gtk.STOCK_DIRECTORY
254          else:
255              mime = gio.content_type_guess(name)
256              if mime:
257                  iconname = gio.content_type_get_icon(mime)
258                  icon = theme.choose_icon(iconname.get_names(), self.icon_size, 0)
259                  if icon:
260                      return gtk.IconInfo.load_icon(icon)
261                  else:
262                      name = gtk.STOCK_FILE
263              else:
264                  name = gtk.STOCK_FILE
265          return theme.load_icon(name, self.icon_size, 0)
266  
267      def _create_store(self):
268          store = gtk.ListStore(str, gtk.gdk.Pixbuf, bool)
269          return store
270  
271      def _fill_store(self):
272          if self.cur_dir == None:
273              return
274          
275          try:
276              self.store.clear()
277              number = 0
278              dirs = []
279              files = []
280              for fl in os.listdir(self.cur_dir):
281                  # we don't want to add hidden files
282                  if fl[0] == '.':
283                      continue
284                  if os.path.isdir(os.path.join(self.cur_dir, fl)):
285                      try:
286                          os.listdir(os.path.join(self.cur_dir, fl))
287                          dirs.append(fl)
288                          number += 1
289                      except OSError:
290                          #print ("no rights for ", os.path.join(self.cur_dir, fl), " skip that dir")
291                          continue
292                  else:
293                      try:
294                          name, ext = fl.rsplit(".", 1)
295                          if "*" in self.filetypes:
296                              files.append(fl)
297                              number += 1
298                          elif ext in self.filetypes:
299                              files.append(fl)
300                              number += 1
301                      except:
302                          pass
303      
304              if self.sortorder not in [_ASCENDING, _DESCENDING, _FOLDERFIRST, _FILEFIRST]:
305                  self.sortorder = _FOLDERFIRST
306      
307              if self.sortorder == _ASCENDING or self.sortorder == _DESCENDING:
308                  allobjects = dirs
309                  allobjects.extend(files)
310                  allobjects.sort(cmp = None, key = None, reverse = not self.sortorder == _ASCENDING)
311      
312                  for obj in allobjects:
313                      if os.path.isdir(os.path.join(self.cur_dir, obj)):
314                          self.store.append([obj, self.dirIcon, True])
315                      else:
316                          icon = self._get_icon(obj)
317                          self.store.append([obj, icon, False])
318      
319              dirs.sort(cmp = None, key = None, reverse = False)
320              files.sort(cmp = None, key = None, reverse = False)
321              if self.sortorder == _FOLDERFIRST:
322                  for dir in dirs:
323                      self.store.append([dir, self.dirIcon, True])
324                  for file in files:
325                      icon = self._get_icon(file)
326                      self.store.append([file, icon, False])
327              elif self.sortorder == _FILEFIRST:
328                  for file in files:
329                      icon = self._get_icon(file)
330                      self.store.append([file, icon, False])
331                  for dir in dirs:
332                      self.store.append([dir, self.dirIcon, True])
333          except:
334              pass
335          finally:
336              # check the stat of the button and set them as they should be
337              self.check_button_state()
338          
339      def check_button_state(self):
340          if self.model.get_iter_first() == None:
341              state = False
342          else:
343              state = True
344          self.btn_sel_next.set_sensitive(state)
345          self.button_state["btn_sel_next"] = state
346          self.btn_sel_prev.set_sensitive(state)
347          self.button_state["btn_sel_prev"] = state
348          self.btn_select.set_sensitive(state)
349          self.button_state["btn_select"] = state
350          state = self.cur_dir == self.user_dir
351          self.btn_home.set_sensitive(not state)
352          self.button_state["btn_home"] = not state
353          state = self.cur_dir == "/"
354          self.btn_dir_up.set_sensitive(not state)
355          self.button_state["btn_dir_up"] = not state
356          state = self.cur_dir == self.jump_to_dir
357          self.btn_jump_to.set_sensitive(not state)
358          self.button_state["btn_jump_to"] = not state
359          state = self.iconView.get_cursor() == None
360          self.btn_select.set_sensitive(not state)
361          self.button_state["btn_select"] = not state        
362          self.state_changed()
363          
364      def state_changed(self):
365          # find the differnce
366          diff = set(self.button_state.iteritems()) - set(self.old_button_state.iteritems())
367          for key in self.button_state.keys():
368              try:
369                  if self.button_state[key] != self.old_button_state[key]:
370                      self.emit("sensitive",key, self.button_state[key])
371              except:
372                  continue 
373  
374          self.old_button_state = self.button_state.copy()
375  
376      def show_buttonbox(self, state):
377          if state:
378              self.buttonbox.show()
379          else:
380              self.buttonbox.hide()
381  
382      def show_filelabel(self, state):
383          if state:
384              self.file_label.show()
385          else:
386              self.file_label.hide()
387  
388      def on_btn_home_clicked(self, widget):
389          self.cur_dir = self.user_dir
390          self._fill_store()
391  
392      def on_btn_jump_to_clicked(self, widget):
393          self.cur_dir = self.jump_to_dir
394          self._fill_store()
395  
396      def get_iter_last(self, model):
397          itr = model.get_iter_first()
398          last = None
399          while itr:
400              last = itr
401              itr = model.iter_next(itr)
402          return last
403  
404      def on_btn_sel_prev_clicked(self, data):
405          if not self.btn_sel_prev.is_sensitive():
406              return None
407          try:
408              actual = self.iconView.get_cursor()[0]
409              iter = self.model.get_iter(actual)
410              pos = int(self.model.get_string_from_iter(iter))
411              first = int(self.model.get_string_from_iter(self.model.get_iter_first()))
412              pos = pos - 1
413              if pos < first:
414                  pos = int(self.model.get_string_from_iter(self.get_iter_last(self.model)))
415              new_iter = self.model.get_iter_from_string(str(pos))
416              new_path = self.model.get_path(new_iter)
417          except:
418              new_iter = self.get_iter_last(self.model)
419              new_path = self.model.get_path(new_iter)
420          self.iconView.set_cursor(new_path)
421          self.iconView.select_path(new_path)
422          self.check_button_state()
423  
424      def on_btn_sel_next_clicked(self, data):
425          if not self.btn_sel_next.is_sensitive():
426              return None
427          try:
428              actual = self.iconView.get_cursor()[0]
429              iter = self.model.get_iter(actual)
430              try:
431                  new_iter = self.model.iter_next(iter)
432                  new_path = self.model.get_path(new_iter)
433                  self.iconView.set_cursor(new_path)
434                  self.iconView.select_path(new_path)
435              except:
436                  first = int(self.model.get_string_from_iter(self.model.get_iter_first()))
437                  new_path = self.model.get_path(first)
438                  self.iconView.set_cursor(new_path)
439                  self.iconView.select_path(new_path)
440          except:
441              self.iconView.set_cursor(0)
442              self.iconView.select_path(0)
443          self.check_button_state()
444  
445  # ToDo: find out how to move a line down or up
446  #    def on_btn_go_down_clicked(self,data):
447  #        print("This is the number of the icon selected", self.iconView.get_cursor())
448  #        col = self.iconView.get_item_column(self.iconView.get_cursor()[0])
449  #        row = self.iconView.get_item_row(self.iconView.get_cursor()[0])
450  #        print("This is the column :", col)
451  #        print("This is the row :", row)
452  #        print(self.iconView.get_columns())
453  
454          
455  #        self.iconView.item_activated(self.iconView.get_cursor()[0])
456  #        print("go down")
457  #        print("columns = ",self.iconView.get_columns())
458  #        print("column spacing = ", self.iconView.get_column_spacing())
459  #        print("item with = ", self.iconView.get_item_width())
460  #        print("margin = ", self.iconView.get_margin())
461  #        print("visible range = ", self.iconView.get_visible_range())
462  #        print("get cursor = ", self.iconView.get_cursor())
463  #        #print("item row = ", self.iconView.get_item_at_row(self.get_selected()))
464  #        print("item column = ", self.iconView.get_item_column(self.get_selected()))
465  #
466  #
467  #    def on_btn_go_up_clicked(self,data):
468  #        print("go up")
469  # ToDo: end
470  
471      def set_icon_size(self, iconsize):
472          try:
473              self.icon_size = iconsize
474              self.dirIcon = self._get_icon("folder")
475              self._fill_store()
476          except:
477              pass
478  
479      def set_directory(self, directory):
480          self.cur_dir = os.path.expanduser(directory)
481          self._fill_store()
482  
483      def set_filetypes(self, filetypes):
484          self.filetypes = filetypes.split(",")
485          self._fill_store()
486  
487      def refresh_filelist(self):
488          self._fill_store()
489  
490      def get_selected(self):
491          return self.on_btn_select_clicked(self)
492  
493      def on_btn_select_clicked(self, data):
494          try:
495              self.iconView.item_activated(self.iconView.get_cursor()[0])
496              if self.path:
497                  filepath = self.cur_dir + os.path.sep + self.path
498                  self.file_label.set_text(filepath)
499              else:
500                  self.file_label.set_text("")
501                  filepath = None
502              self.emit('selected', filepath)
503          except:
504              pass
505  
506      def on_btn_exit_clicked(self, data):
507          if __name__ == "__main__":
508              gtk.main_quit()
509          self.emit('exit')
510  
511      def _on_item_activated(self, widget, item):
512          path = self.model[item][COL_PATH]
513          isDir = self.model[item][COL_IS_DIRECTORY]
514  
515          if not isDir:
516              self.path = path
517              return
518  
519          self.cur_dir = self.cur_dir + os.path.sep + path
520          self._fill_store()
521          # This is only to advise, that the selection wasn't a file, but an directory
522          self.path = None
523  
524      def _on_selection_changed(self, widget):
525          if not self.btn_select.get_sensitive():
526              self.btn_select.set_sensitive(True)
527          self.check_button_state()
528  
529      def on_btn_dir_up_clicked(self, widget):
530          self.cur_dir = os.path.dirname(self.cur_dir)
531          self._fill_store()
532  
533      def do_get_property(self, property):
534          name = property.name.replace('-', '_')
535          if name in self.__gproperties.keys():
536              return getattr(self, name)
537          else:
538              raise AttributeError('unknown iconview get_property %s' % property.name)
539  
540      def do_set_property(self, property, value):
541          try:
542              name = property.name.replace('-', '_')
543              if name in self.__gproperties.keys():
544                  setattr(self, name, value)
545                  self.queue_draw()
546                  if name == 'icon_size':
547                      self.set_icon_size(value)
548                  if name == 'start_dir':
549                      self.start_dir = os.path.expanduser(value)
550                      self.set_directory(self.start_dir)
551                  if name == 'jump_to_dir':
552                      self.jump_to_dir = os.path.expanduser(value)
553                      self.on_btn_jump_to()
554                  if name == 'filetypes':
555                      self.set_filetypes(value)
556                  if name == 'sortorder':
557                      if value not in [_ASCENDING, _DESCENDING, _FOLDERFIRST, _FILEFIRST]:
558                          raise AttributeError('unknown property of sortorder %s' % value)
559                      else:
560                          self.sortorder = value
561                          self._fill_store()
562              else:
563                  raise AttributeError('unknown iconview set_property %s' % property.name)
564          except:
565              pass
566  
567  # for testing without glade editor:
568  def main():
569      window = gtk.Window(gtk.WINDOW_TOPLEVEL)
570  
571      IFS = IconFileSelection()
572      IFS.set_property("filetypes", "*")
573      IFS.set_property("jump_to_dir", "/tmp")
574  
575      window.add(IFS)
576      window.connect("destroy", gtk.main_quit)
577      window.show_all()
578      window.set_size_request(680, 480)
579      gtk.main()
580      
581  
582  if __name__ == "__main__":
583      main()