Example #1
0
    def __init__(self, bash_cmd_event):
      self.bash_cmd_event = bash_cmd_event # BashCommandEvent instance
      self.cmd_str = ' '.join(bash_cmd_event.cmd)
      self.annotator = AnnotationComponent(WINDOW_WIDTH-50, bash_cmd_event)


      command_context_menu = gtk.Menu()
      cc_item1 = gtk.MenuItem('Copy command')
      cc_item1.connect("activate", self.copy_cmd)
      cc_item2 = gtk.MenuItem('Copy event hashtag')
      cc_item2.connect("activate", self.copy_event_hashtag)
      add_comment_item = gtk.MenuItem('Annotate invocation')
      add_comment_item.connect("activate", self.annotator.show_comment_box)

      command_context_menu.append(cc_item1)
      command_context_menu.append(cc_item2)
      command_context_menu.append(add_comment_item)

      cmd_label = gtk.Label(self.cmd_str)
      cmd_label.modify_font(pango.FontDescription("monospace 8"))
      cmd_label_box = create_clickable_event_box(cmd_label, command_context_menu)
      cmd_label_box.set_has_tooltip(True)
      cmd_label_box.connect('query-tooltip', show_tooltip, self.cmd_str)

      cmd_lalign = create_alignment(cmd_label_box, ptop=2, pbottom=2, pleft=2)

      cmd_vbox = create_vbox((cmd_lalign, self.annotator.get_widget()))

      show_all_local_widgets(locals())
      self.widget = cmd_vbox
Example #2
0
  class WebpageDisplay:
    def __init__(self, webpage_event):
      self.webpage_event = webpage_event # WebpageVisitEvent instance
      self.annotator = AnnotationComponent(WINDOW_WIDTH-50, webpage_event)

      webpage_context_menu = gtk.Menu()
      hashtag_item = gtk.MenuItem('Copy event hashtag')
      hashtag_item.connect("activate", self.copy_event_hashtag)
      add_comment_item = gtk.MenuItem('Annotate web visit')
      add_comment_item.connect("activate", self.annotator.show_comment_box)
      webpage_context_menu.append(hashtag_item)
      webpage_context_menu.append(add_comment_item)

      # make the domain name concise:
      domain_name = urlparse(webpage_event.url).netloc
      if domain_name.startswith('www.'):
        domain_name = domain_name[len('www.'):]

      domain_display = gtk.Label()
      domain_display.set_markup('<span font_family="sans" size="8000" foreground="#666666">[%s] </span>' % domain_name)
      domain_display_box = create_clickable_event_box(domain_display, webpage_context_menu)
      domain_display_box.set_has_tooltip(True)
      domain_display_box.connect('query-tooltip', show_tooltip, webpage_event.url)

      link_display = gtk.Label()
      encoded_url = webpage_event.url.replace('&', '&amp;')
      encoded_title = webpage_event.title.replace('&', '&amp;')

      link_display.set_markup('<span font_family="sans" size="8000"><a href="%s">%s</a></span>' % (encoded_url, encoded_title))

      domain_and_link_display = create_hbox((domain_display_box, link_display))
      webpage_display_lalign = create_alignment(domain_and_link_display, ptop=2, pbottom=1, pleft=1)

      disp_vbox = create_vbox((webpage_display_lalign, self.annotator.get_widget()))

      show_all_local_widgets(locals())
      self.widget = disp_vbox


    def copy_event_hashtag(self, _ignore):
      g_clipboard.set_text(self.webpage_event.get_hashtag())

    def get_widget(self):
      return self.widget
    def render_table_row(self, tbl, row_index):
      XPADDING=8
      YPADDING=15
      # using "yoptions=gtk.SHRINK" in table.attach seems to do the trick
      # in not having the table cells expand vertically like nuts


      # Print source file diffs

      sd = self.start_timestamp.strftime('%Y-%m-%d')
      ed = self.end_timestamp.strftime('%Y-%m-%d')

      st = self.start_timestamp.strftime('%H:%M:%S')
      et = self.end_timestamp.strftime('%H:%M:%S')

      # If the days are the same, then don't duplicate:
      if sd == ed:
        date_str = '%s to %s (%s)' % (st, et, sd)
      else:
        date_str = '%s %s to %s %s' % (sd, st, ed, et)

      date_lab = gtk.Label(date_str)
      date_lab.modify_font(pango.FontDescription("sans 8"))
      date_lab_lalign = create_alignment(date_lab, pbottom=3)

      diff_result_str = self.diff()

      # TODO: adjust height based on existing height of row/column
      text_widget = create_simple_text_view_widget(diff_result_str, 450, 200)


      source_file_vbox = create_vbox([date_lab_lalign, text_widget])
      tbl.attach(source_file_vbox, 0, 1, row_index, row_index+1,
                 xpadding=XPADDING + 5,
                 ypadding=YPADDING,
                 yoptions=gtk.SHRINK)


      # Print co-reads:
      # 1.) webpages visited
      # 2.) other vim files read
      # 3.) other non-vim files read
      co_read_widgets = []

      # TODO: make these labels clickable with pop-up context menus
      for (fn, timestamp) in self.other_vim_files_read.items() + \
                             self.non_vim_files_read.items():
        lab = gtk.Label(prettify_filename(fn))
        lab.modify_font(pango.FontDescription("monospace 9"))
        lab.set_selectable(True)
        lab.show()
        lab_lalign = create_alignment(lab, pbottom=3)
        lab_lalign.show()
        co_read_widgets.append(lab_lalign)


      # de-dup:
      urls_seen = set()

      if self.webpages_visited:
        n = WebpageFeedEvent()
        for w in self.webpages_visited:
          if w.url not in urls_seen:
            urls_seen.add(w.url)
            n.add_webpage_chron_order(w)

        n_lalign = create_alignment(n.get_widget(), ptop=3)
        co_read_widgets.append(n_lalign)


      co_reads_vbox = create_vbox(co_read_widgets)
      co_reads_vbox_lalign = create_alignment(co_reads_vbox)
      tbl.attach(co_reads_vbox_lalign, 1, 2, row_index, row_index+1,
                 xpadding=XPADDING, ypadding=YPADDING,
                 xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)


      # Print co-writes
      # 1.) other vim files edited
      # 2.) doodle events
      # 3.) happy face events
      # 4.) sad face events
      # 5.) status update events
      co_write_widgets = []

      for (fn, timestamp) in self.other_vim_files_edited.iteritems():
        lab = gtk.Label(prettify_filename(fn))
        lab.modify_font(pango.FontDescription("monospace 9"))
        lab.set_selectable(True)
        lab.show()
        lab_lalign = create_alignment(lab)
        lab_lalign.show()
        co_write_widgets.append(lab_lalign)

      all_feed_evts = []

      for e in self.doodle_save_events:
        d = DoodleFeedEvent(e, self.fvm)
        d.load_thumbnail() # subtle but dumb!!!
        all_feed_evts.append(d)

      for e in self.happy_face_events:
        all_feed_evts.append(HappyFaceFeedEvent(e))

      for e in self.sad_face_events:
        all_feed_evts.append(SadFaceFeedEvent(e))

      for e in self.status_update_events:
        all_feed_evts.append(StatusUpdateFeedEvent(e))


      for e in all_feed_evts:
        co_write_widgets.append(e.get_widget())


      co_writes_vbox = create_vbox(co_write_widgets,
                                   [4 for e in co_write_widgets])
      co_writes_vbox_lalign = create_alignment(co_writes_vbox)

      tbl.attach(co_writes_vbox_lalign, 2, 3, row_index, row_index+1,
                 xpadding=XPADDING, ypadding=YPADDING,
                 xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)


      # Print notes (annotations)

      # stick the annotation on the FINAL FileWriteEvent in this faux version:
      annotator = AnnotationComponent(300, self.get_last_write_event(), '<Click to enter a new note>')
      tbl.attach(annotator.get_widget(), 3, 4,
                 row_index, row_index+1,
                 xpadding=XPADDING, ypadding=YPADDING,
                 yoptions=gtk.SHRINK)

      show_all_local_widgets(locals())
Example #4
0
    def render_table_row(self, prev_cmd_invocation, tbl, row_index):
        XPADDING = 8
        YPADDING = 15
        # using "yoptions=gtk.SHRINK" in table.attach seems to do the trick
        # in not having the table cells expand vertically like nuts

        # Print inputs:

        widgets = []

        for re in self.read_event_lst:
            lab = gtk.Label(prettify_filename(re.filename))
            lab.modify_font(pango.FontDescription("monospace 9"))
            lab.show()

            menu = gtk.Menu()

            view_item = gtk.MenuItem('Open')
            view_item.connect("activate", self.view_file_version, re)
            view_item.show()
            mark_diff_item = gtk.MenuItem('Select for diff')
            mark_diff_item.connect("activate", self.mark_for_diff, re)
            mark_diff_item.show()
            prov_item = gtk.MenuItem('View source file provenance')
            prov_item.connect("activate", self.view_source_prov, re)
            prov_item.show()
            menu.append(view_item)
            menu.append(mark_diff_item)
            menu.append(prov_item)

            global diff_menu_items
            diff_menu_items.append(mark_diff_item)

            lab_box = create_clickable_event_box(lab, menu)
            lab_box.show()

            lab_align = create_alignment(lab_box, pbottom=5)
            lab_align.show()
            widgets.append(lab_align)

        if prev_cmd_invocation:
            diff_result_str = self.diff_input_files(prev_cmd_invocation)

            # TODO: adjust height based on existing height of row/column
            text_widget = create_simple_text_view_widget(
                diff_result_str, 400, 200)
            #text_widget = create_simple_text_view_widget(diff_result_str, 500, 300)

            widgets.append(text_widget)

        input_vbox = create_vbox(widgets)
        tbl.attach(input_vbox,
                   0,
                   1,
                   row_index,
                   row_index + 1,
                   xpadding=XPADDING + 5,
                   ypadding=YPADDING,
                   yoptions=gtk.SHRINK)

        # Print command:

        # cool that we get to re-use BashFeedEvent objects
        n = burrito_feed.BashFeedEvent(self.cmd_event.pwd)
        n.add_command_chron_order(self.cmd_event)

        # make it not expand like crazy in either the horizontal or vertical directions
        tbl.attach(n.get_widget(),
                   1,
                   2,
                   row_index,
                   row_index + 1,
                   xpadding=XPADDING,
                   ypadding=YPADDING,
                   xoptions=gtk.SHRINK,
                   yoptions=gtk.SHRINK)

        # Print output:
        mime_type_guess = mimetypes.guess_type(self.get_output_filename())[0]

        cur_output_filepath = self.fvm.checkout_file_before_next_write(
            self.output_event, self.sorted_write_events_lst)

        if 'image/' in mime_type_guess:
            output_image = gtk.Image()
            output_image.set_from_file(cur_output_filepath)
            tbl.attach(output_image,
                       2,
                       3,
                       row_index,
                       row_index + 1,
                       xpadding=XPADDING,
                       ypadding=YPADDING,
                       yoptions=gtk.SHRINK)

        elif 'text/' in mime_type_guess:
            if prev_cmd_invocation:
                str_to_display = self.diff_output_file(cur_output_filepath,
                                                       prev_cmd_invocation)
            else:
                # display entire file contents:
                str_to_display = open(cur_output_filepath, 'U').read()

            text_widget = create_simple_text_view_widget(
                str_to_display, 500, 350)
            tbl.attach(text_widget,
                       2,
                       3,
                       row_index,
                       row_index + 1,
                       xpadding=XPADDING,
                       ypadding=YPADDING,
                       yoptions=gtk.SHRINK)

        # Print annotations associated with self.output_event:
        annotator = AnnotationComponent(300, self.output_event,
                                        '<Click to enter a new note>')
        tbl.attach(annotator.get_widget(),
                   3,
                   4,
                   row_index,
                   row_index + 1,
                   xpadding=XPADDING,
                   ypadding=YPADDING,
                   yoptions=gtk.SHRINK)

        show_all_local_widgets(locals())
Example #5
0
    def __init__(self, file_provenance_event, parent):
      self.file_provenance_event = file_provenance_event
      self.parent = parent # sub-class of FileFeedEvent
      self.fvm = parent.fvm # instance of FileVersionManager

      self.annotator = AnnotationComponent(WINDOW_WIDTH-50, file_provenance_event)

      file_context_menu = gtk.Menu()

      diff_cur_item = gtk.MenuItem('Diff against latest')
      diff_cur_item.connect("activate", self.diff_with_latest)

      diff_pred_item = gtk.MenuItem('Diff against predecessor')
      diff_pred_item.connect("activate", self.diff_with_predecessor)

      mark_diff = gtk.MenuItem('Select for diff')
      mark_diff.connect("activate", self.mark_for_diff)

      global diff_menu_items
      diff_menu_items.append(mark_diff)

      view = gtk.MenuItem('Open')
      view.connect("activate", self.open_to_view, 'current')
      view_pred = gtk.MenuItem('Open predecessor')
      view_pred.connect("activate", self.open_to_view, 'predecessor')

      revert_current = gtk.MenuItem('Revert to current')
      revert_current.connect("activate", self.revert, 'current')
      revert_pred = gtk.MenuItem('Revert to predecessor')
      revert_pred.connect("activate", self.revert, 'predecessor')
      watch_me = gtk.MenuItem('Watch for changes')
      watch_me.connect("activate", self.watch_for_changes)
      view_source_prov = gtk.MenuItem('View source file provenance')
      view_source_prov.connect("activate", self.view_source_prov)
      view_output_prov = gtk.MenuItem('View output file provenance')
      view_output_prov.connect("activate", self.view_output_prov)

      # not implemented yet
      item5 = gtk.MenuItem('Ignore file')
      item6 = gtk.MenuItem('Ignore directory')

      copy_filename_item = gtk.MenuItem('Copy filename')
      copy_filename_item.connect("activate", self.copy_filename)
      hashtag_item = gtk.MenuItem('Copy event hashtag')
      hashtag_item.connect("activate", self.copy_event_hashtag)
      add_comment_item = gtk.MenuItem('Annotate file version')
      add_comment_item.connect("activate", self.annotator.show_comment_box)

      separator1 = gtk.SeparatorMenuItem()
      separator2 = gtk.SeparatorMenuItem()
      separator3 = gtk.SeparatorMenuItem()
      separator4 = gtk.SeparatorMenuItem()

      file_context_menu.append(copy_filename_item)
      file_context_menu.append(hashtag_item)
      file_context_menu.append(add_comment_item)
      file_context_menu.append(separator1)
      file_context_menu.append(diff_cur_item)
      file_context_menu.append(diff_pred_item)
      file_context_menu.append(mark_diff)
      file_context_menu.append(separator2)
      file_context_menu.append(view)
      file_context_menu.append(view_pred)
      file_context_menu.append(watch_me)
      file_context_menu.append(separator3)
      file_context_menu.append(revert_current)
      file_context_menu.append(revert_pred)
      file_context_menu.append(separator4)
      file_context_menu.append(view_source_prov)
      file_context_menu.append(view_output_prov)
      #file_context_menu.append(item5)
      #file_context_menu.append(item6)

      # only show base path in label for brevity
      file_label = gtk.Label(os.path.basename(self.file_provenance_event.filename))
      file_label.modify_font(pango.FontDescription("monospace 8"))
      file_label_box = create_clickable_event_box(file_label, file_context_menu)
      # ... but show FULL file path in tooltip
      file_label_box.set_has_tooltip(True)
      file_label_box.connect('query-tooltip', show_tooltip, prettify_filename(self.file_provenance_event.filename))

      icon_and_label_box = gtk.HBox()
      icon_and_label_box.pack_end(file_label_box, expand=False)

      file_lalign = create_alignment(icon_and_label_box, ptop=2, pbottom=2, pleft=2)

      file_vbox = create_vbox((file_lalign, self.annotator.get_widget()))

      show_all_local_widgets(locals())
      self.widget = file_vbox
      self.icon_and_label_box = icon_and_label_box
      self.watchme_icon_alignment = None # lazily allocate to save memory


      global watch_files
      try:
        old_version_path = watch_files[self.file_provenance_event.filename].checkout_and_get_path()
        if os.path.exists(old_version_path):
          if not filecmp.cmp(old_version_path, self.file_provenance_event.filename):
            # there's a diff!
            changed_icon = gtk.Image()
            changed_icon.set_from_file('red-exclamation-point-16x16.png')
            changed_icon.show()
            changed_icon_alignment = create_alignment(changed_icon, pright=3)
            changed_icon_alignment.show()
            self.icon_and_label_box.pack_end(changed_icon_alignment)
            file_label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color('#800517')) # make it red!
          else:
            # 'passed' the informal regression test set by watchfile
            test_pass_icon = gtk.Image()
            test_pass_icon.set_from_file('tasque-check-box.png')
            test_pass_icon.show()
            test_pass_icon_alignment = create_alignment(test_pass_icon, pright=3)
            test_pass_icon_alignment.show()
            self.icon_and_label_box.pack_end(test_pass_icon_alignment)
      except KeyError:
        pass
Example #6
0
  class FileEventDisplay:
    def __init__(self, file_provenance_event, parent):
      self.file_provenance_event = file_provenance_event
      self.parent = parent # sub-class of FileFeedEvent
      self.fvm = parent.fvm # instance of FileVersionManager

      self.annotator = AnnotationComponent(WINDOW_WIDTH-50, file_provenance_event)

      file_context_menu = gtk.Menu()

      diff_cur_item = gtk.MenuItem('Diff against latest')
      diff_cur_item.connect("activate", self.diff_with_latest)

      diff_pred_item = gtk.MenuItem('Diff against predecessor')
      diff_pred_item.connect("activate", self.diff_with_predecessor)

      mark_diff = gtk.MenuItem('Select for diff')
      mark_diff.connect("activate", self.mark_for_diff)

      global diff_menu_items
      diff_menu_items.append(mark_diff)

      view = gtk.MenuItem('Open')
      view.connect("activate", self.open_to_view, 'current')
      view_pred = gtk.MenuItem('Open predecessor')
      view_pred.connect("activate", self.open_to_view, 'predecessor')

      revert_current = gtk.MenuItem('Revert to current')
      revert_current.connect("activate", self.revert, 'current')
      revert_pred = gtk.MenuItem('Revert to predecessor')
      revert_pred.connect("activate", self.revert, 'predecessor')
      watch_me = gtk.MenuItem('Watch for changes')
      watch_me.connect("activate", self.watch_for_changes)
      view_source_prov = gtk.MenuItem('View source file provenance')
      view_source_prov.connect("activate", self.view_source_prov)
      view_output_prov = gtk.MenuItem('View output file provenance')
      view_output_prov.connect("activate", self.view_output_prov)

      # not implemented yet
      item5 = gtk.MenuItem('Ignore file')
      item6 = gtk.MenuItem('Ignore directory')

      copy_filename_item = gtk.MenuItem('Copy filename')
      copy_filename_item.connect("activate", self.copy_filename)
      hashtag_item = gtk.MenuItem('Copy event hashtag')
      hashtag_item.connect("activate", self.copy_event_hashtag)
      add_comment_item = gtk.MenuItem('Annotate file version')
      add_comment_item.connect("activate", self.annotator.show_comment_box)

      separator1 = gtk.SeparatorMenuItem()
      separator2 = gtk.SeparatorMenuItem()
      separator3 = gtk.SeparatorMenuItem()
      separator4 = gtk.SeparatorMenuItem()

      file_context_menu.append(copy_filename_item)
      file_context_menu.append(hashtag_item)
      file_context_menu.append(add_comment_item)
      file_context_menu.append(separator1)
      file_context_menu.append(diff_cur_item)
      file_context_menu.append(diff_pred_item)
      file_context_menu.append(mark_diff)
      file_context_menu.append(separator2)
      file_context_menu.append(view)
      file_context_menu.append(view_pred)
      file_context_menu.append(watch_me)
      file_context_menu.append(separator3)
      file_context_menu.append(revert_current)
      file_context_menu.append(revert_pred)
      file_context_menu.append(separator4)
      file_context_menu.append(view_source_prov)
      file_context_menu.append(view_output_prov)
      #file_context_menu.append(item5)
      #file_context_menu.append(item6)

      # only show base path in label for brevity
      file_label = gtk.Label(os.path.basename(self.file_provenance_event.filename))
      file_label.modify_font(pango.FontDescription("monospace 8"))
      file_label_box = create_clickable_event_box(file_label, file_context_menu)
      # ... but show FULL file path in tooltip
      file_label_box.set_has_tooltip(True)
      file_label_box.connect('query-tooltip', show_tooltip, prettify_filename(self.file_provenance_event.filename))

      icon_and_label_box = gtk.HBox()
      icon_and_label_box.pack_end(file_label_box, expand=False)

      file_lalign = create_alignment(icon_and_label_box, ptop=2, pbottom=2, pleft=2)

      file_vbox = create_vbox((file_lalign, self.annotator.get_widget()))

      show_all_local_widgets(locals())
      self.widget = file_vbox
      self.icon_and_label_box = icon_and_label_box
      self.watchme_icon_alignment = None # lazily allocate to save memory


      global watch_files
      try:
        old_version_path = watch_files[self.file_provenance_event.filename].checkout_and_get_path()
        if os.path.exists(old_version_path):
          if not filecmp.cmp(old_version_path, self.file_provenance_event.filename):
            # there's a diff!
            changed_icon = gtk.Image()
            changed_icon.set_from_file('red-exclamation-point-16x16.png')
            changed_icon.show()
            changed_icon_alignment = create_alignment(changed_icon, pright=3)
            changed_icon_alignment.show()
            self.icon_and_label_box.pack_end(changed_icon_alignment)
            file_label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color('#800517')) # make it red!
          else:
            # 'passed' the informal regression test set by watchfile
            test_pass_icon = gtk.Image()
            test_pass_icon.set_from_file('tasque-check-box.png')
            test_pass_icon.show()
            test_pass_icon_alignment = create_alignment(test_pass_icon, pright=3)
            test_pass_icon_alignment.show()
            self.icon_and_label_box.pack_end(test_pass_icon_alignment)
      except KeyError:
        pass


    def get_widget(self):
      return self.widget
    
    def get_filename(self):
      return self.file_provenance_event.filename

    def copy_filename(self, _ignore):
      g_clipboard.set_text(self.file_provenance_event.filename)

    def copy_event_hashtag(self, _ignore):
      g_clipboard.set_text(self.file_provenance_event.get_hashtag())


    def checkout_and_get_path(self):
      return self.fvm.checkout_file_before_next_write(self.file_provenance_event,
                                                      sorted_write_events[self.file_provenance_event.filename])

    # to find the predecessor, simply check out the file one second
    # before the write occurred ...
    #
    # TODO: this isn't exactly correct, since you could've had a bunch
    # of coalesced writes, so you might want to get the version BEFORE
    # the series of coalesced writes.
    def checkout_predecessor_and_get_path(self):
      return self.fvm.checkout_file(self.get_filename(),
                                    self.file_provenance_event.timestamp - ONE_SEC)


    def diff_with_latest(self, _ignore):
      # requires the 'meld' visual diff tool to be installed
      old_version_path = self.checkout_and_get_path()
      fn = self.file_provenance_event.filename
      os.system('meld "%s" "%s" &' % (old_version_path, fn))


    def diff_with_predecessor(self, _ignore):
      post_write_path = self.checkout_and_get_path()
      predecessor_path = self.checkout_predecessor_and_get_path()
      os.system('meld "%s" "%s" &' % (predecessor_path, post_write_path))


    def mark_for_diff(self, _ignore):
      global diff_left_half, diff_menu_items # KLUDGY!
      if diff_left_half:
        diff_right_half_path = self.checkout_and_get_path()
        diff_left_half_path = diff_left_half.checkout_and_get_path()
        os.system('meld "%s" "%s" &' % (diff_left_half_path, diff_right_half_path))

        # RESET!
        diff_left_half = None
        for e in diff_menu_items:
          e.set_label('Select for diff')
      else:
        diff_left_half = self 
        for e in diff_menu_items:
          e.set_label('Diff against selected file')


    def open_to_view(self, _ignore, option):
      if option == 'current':
        old_version_path = self.checkout_and_get_path()
      elif option == 'predecessor':
        old_version_path = self.checkout_predecessor_and_get_path()
      else:
        assert False

      # gnome-open to the rescue!!!  uses a file's type to determine the
      # proper viewer application :)
      if not os.path.isfile(old_version_path):
        create_popup_error_dialog("File not found:\n" + old_version_path)
      else:
        os.system('gnome-open "%s" &' % old_version_path)


    def view_source_prov(self, _ignore):
      global cur_session
      spv = source_file_prov_viewer.SourceFileProvViewer(self.get_filename(), cur_session, self.fvm)

    def view_output_prov(self, _ignore):
      global cur_session # KLUDGY!
      print 'view_output_prov:', self.get_filename(), cur_session
      opv = output_file_prov_viewer.OutputFileProvViewer(self.get_filename(), cur_session, self.fvm)


    def watch_for_changes(self, _ignore):
      global watch_files
      fn = self.file_provenance_event.filename
      if fn in watch_files:
        # un-watch the other file:
        other = watch_files[fn]
        assert other.watchme_icon_alignment
        other.icon_and_label_box.remove(other.watchme_icon_alignment)

        # if other is actually self, then un-watch!
        if other == self:
          del watch_files[fn]
          return # PUNTTT!

      watch_files[fn] = self

      # "freeze" the enclosing FileMutatedFeedEvent object when you
      # create a watchpoint so that subsequent writes don't coalesce into
      # this FileMutatedFeedEvent entry and possibly destroy the current
      # FileEventDisplay object in the # process!
      self.parent.frozen = True

      watchme_icon = gtk.Image()
      watchme_icon.set_from_file('magnifying-glass-16x16.png')
      watchme_icon.show()
      self.watchme_icon_alignment = create_alignment(watchme_icon, pright=3)
      self.watchme_icon_alignment.show()

      self.icon_and_label_box.pack_end(self.watchme_icon_alignment)


    # option = 'current' or 'predecessor'
    def revert(self, _ignore, option):
      if option == 'current':
        old_version_path = self.checkout_and_get_path()
      elif option == 'predecessor':
        old_version_path = self.checkout_predecessor_and_get_path()
      else:
        assert False

      if not os.path.isfile(old_version_path):
        create_popup_error_dialog("File not found:\n" + old_version_path)
      else:
        # pop-up a confirmation dialog before taking drastic action!
        d = gtk.MessageDialog(None,
                              gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                              gtk.MESSAGE_QUESTION,
                              gtk.BUTTONS_YES_NO,
                              message_format="Are you sure you want to revert\n\n  %s\n\nto\n\n  %s" % \
                                             (self.get_filename(), old_version_path))
        d.show()
        response = d.run()
        d.destroy()

        if response == gtk.RESPONSE_YES:

          # VERY INTERESTING: the 'cp' command sometimes doesn't work
          # for NILFS, since it thinks that the snapshot version is
          # IDENTICAL to the latest current version of the file and will
          # thus refuse to do the copy even though their contents are
          # clearly different.
          #
          # Thus, we will do a super-hack where we copy the file to
          # tmp_blob and then rename it to the real filename ...
          tmp_blob = '/tmp/tmp-reverted-file'
          revert_cmd = "cp '%s' '%s'; mv '%s' '%s'" % (old_version_path, tmp_blob,
                                                       tmp_blob, self.get_filename())
          os.system(revert_cmd)
        def render_table_row(self, tbl, row_index):
            XPADDING = 8
            YPADDING = 15
            # using "yoptions=gtk.SHRINK" in table.attach seems to do the trick
            # in not having the table cells expand vertically like nuts

            # Print source file diffs

            sd = self.start_timestamp.strftime('%Y-%m-%d')
            ed = self.end_timestamp.strftime('%Y-%m-%d')

            st = self.start_timestamp.strftime('%H:%M:%S')
            et = self.end_timestamp.strftime('%H:%M:%S')

            # If the days are the same, then don't duplicate:
            if sd == ed:
                date_str = '%s to %s (%s)' % (st, et, sd)
            else:
                date_str = '%s %s to %s %s' % (sd, st, ed, et)

            date_lab = gtk.Label(date_str)
            date_lab.modify_font(pango.FontDescription("sans 8"))
            date_lab_lalign = create_alignment(date_lab, pbottom=3)

            diff_result_str = self.diff()

            # TODO: adjust height based on existing height of row/column
            text_widget = create_simple_text_view_widget(
                diff_result_str, 450, 200)

            source_file_vbox = create_vbox([date_lab_lalign, text_widget])
            tbl.attach(source_file_vbox,
                       0,
                       1,
                       row_index,
                       row_index + 1,
                       xpadding=XPADDING + 5,
                       ypadding=YPADDING,
                       yoptions=gtk.SHRINK)

            # Print co-reads:
            # 1.) webpages visited
            # 2.) other vim files read
            # 3.) other non-vim files read
            co_read_widgets = []

            # TODO: make these labels clickable with pop-up context menus
            for (fn, timestamp) in self.other_vim_files_read.items() + \
                                   self.non_vim_files_read.items():
                lab = gtk.Label(prettify_filename(fn))
                lab.modify_font(pango.FontDescription("monospace 9"))
                lab.set_selectable(True)
                lab.show()
                lab_lalign = create_alignment(lab, pbottom=3)
                lab_lalign.show()
                co_read_widgets.append(lab_lalign)

            # de-dup:
            urls_seen = set()

            if self.webpages_visited:
                n = WebpageFeedEvent()
                for w in self.webpages_visited:
                    if w.url not in urls_seen:
                        urls_seen.add(w.url)
                        n.add_webpage_chron_order(w)

                n_lalign = create_alignment(n.get_widget(), ptop=3)
                co_read_widgets.append(n_lalign)

            co_reads_vbox = create_vbox(co_read_widgets)
            co_reads_vbox_lalign = create_alignment(co_reads_vbox)
            tbl.attach(co_reads_vbox_lalign,
                       1,
                       2,
                       row_index,
                       row_index + 1,
                       xpadding=XPADDING,
                       ypadding=YPADDING,
                       xoptions=gtk.SHRINK,
                       yoptions=gtk.SHRINK)

            # Print co-writes
            # 1.) other vim files edited
            # 2.) doodle events
            # 3.) happy face events
            # 4.) sad face events
            # 5.) status update events
            co_write_widgets = []

            for (fn, timestamp) in self.other_vim_files_edited.iteritems():
                lab = gtk.Label(prettify_filename(fn))
                lab.modify_font(pango.FontDescription("monospace 9"))
                lab.set_selectable(True)
                lab.show()
                lab_lalign = create_alignment(lab)
                lab_lalign.show()
                co_write_widgets.append(lab_lalign)

            all_feed_evts = []

            for e in self.doodle_save_events:
                d = DoodleFeedEvent(e, self.fvm)
                d.load_thumbnail()  # subtle but dumb!!!
                all_feed_evts.append(d)

            for e in self.happy_face_events:
                all_feed_evts.append(HappyFaceFeedEvent(e))

            for e in self.sad_face_events:
                all_feed_evts.append(SadFaceFeedEvent(e))

            for e in self.status_update_events:
                all_feed_evts.append(StatusUpdateFeedEvent(e))

            for e in all_feed_evts:
                co_write_widgets.append(e.get_widget())

            co_writes_vbox = create_vbox(co_write_widgets,
                                         [4 for e in co_write_widgets])
            co_writes_vbox_lalign = create_alignment(co_writes_vbox)

            tbl.attach(co_writes_vbox_lalign,
                       2,
                       3,
                       row_index,
                       row_index + 1,
                       xpadding=XPADDING,
                       ypadding=YPADDING,
                       xoptions=gtk.SHRINK,
                       yoptions=gtk.SHRINK)

            # Print notes (annotations)

            # stick the annotation on the FINAL FileWriteEvent in this faux version:
            annotator = AnnotationComponent(300, self.get_last_write_event(),
                                            '<Click to enter a new note>')
            tbl.attach(annotator.get_widget(),
                       3,
                       4,
                       row_index,
                       row_index + 1,
                       xpadding=XPADDING,
                       ypadding=YPADDING,
                       yoptions=gtk.SHRINK)

            show_all_local_widgets(locals())
  def render_table_row(self, prev_cmd_invocation, tbl, row_index):
    XPADDING=8
    YPADDING=15
    # using "yoptions=gtk.SHRINK" in table.attach seems to do the trick
    # in not having the table cells expand vertically like nuts

    # Print inputs:

    widgets = []

    for re in self.read_event_lst:
      lab = gtk.Label(prettify_filename(re.filename))
      lab.modify_font(pango.FontDescription("monospace 9"))
      lab.show()

      menu = gtk.Menu()

      view_item = gtk.MenuItem('Open')
      view_item.connect("activate", self.view_file_version, re)
      view_item.show()
      mark_diff_item = gtk.MenuItem('Select for diff')
      mark_diff_item.connect("activate", self.mark_for_diff, re)
      mark_diff_item.show()
      prov_item = gtk.MenuItem('View source file provenance')
      prov_item.connect("activate", self.view_source_prov, re)
      prov_item.show()
      menu.append(view_item)
      menu.append(mark_diff_item)
      menu.append(prov_item)

      global diff_menu_items
      diff_menu_items.append(mark_diff_item)

      lab_box = create_clickable_event_box(lab, menu)
      lab_box.show()

      lab_align = create_alignment(lab_box, pbottom=5)
      lab_align.show()
      widgets.append(lab_align)

    if prev_cmd_invocation:
      diff_result_str = self.diff_input_files(prev_cmd_invocation)

      # TODO: adjust height based on existing height of row/column
      text_widget = create_simple_text_view_widget(diff_result_str, 400, 200)
      #text_widget = create_simple_text_view_widget(diff_result_str, 500, 300)

      widgets.append(text_widget)

    input_vbox = create_vbox(widgets)
    tbl.attach(input_vbox, 0, 1, row_index, row_index+1,
               xpadding=XPADDING + 5,
               ypadding=YPADDING,
               yoptions=gtk.SHRINK)
   

    # Print command:

    # cool that we get to re-use BashFeedEvent objects
    n = burrito_feed.BashFeedEvent(self.cmd_event.pwd)
    n.add_command_chron_order(self.cmd_event)

    # make it not expand like crazy in either the horizontal or vertical directions
    tbl.attach(n.get_widget(), 1, 2, row_index, row_index+1, xpadding=XPADDING, ypadding=YPADDING,
               xoptions=gtk.SHRINK, yoptions=gtk.SHRINK)

    # Print output:
    mime_type_guess = mimetypes.guess_type(self.get_output_filename())[0]

    cur_output_filepath = self.fvm.checkout_file_before_next_write(self.output_event,
                                                                   self.sorted_write_events_lst)

    if 'image/' in mime_type_guess:
      output_image = gtk.Image()
      output_image.set_from_file(cur_output_filepath)
      tbl.attach(output_image, 2, 3, row_index, row_index+1, xpadding=XPADDING, ypadding=YPADDING, yoptions=gtk.SHRINK)

    elif 'text/' in mime_type_guess:
      if prev_cmd_invocation:
        str_to_display = self.diff_output_file(cur_output_filepath, prev_cmd_invocation)
      else:
        # display entire file contents:
        str_to_display = open(cur_output_filepath, 'U').read()

      text_widget = create_simple_text_view_widget(str_to_display, 500, 350)
      tbl.attach(text_widget, 2, 3, row_index, row_index+1, xpadding=XPADDING, ypadding=YPADDING, yoptions=gtk.SHRINK)


    # Print annotations associated with self.output_event:
    annotator = AnnotationComponent(300, self.output_event, '<Click to enter a new note>')
    tbl.attach(annotator.get_widget(), 3, 4, row_index, row_index+1, xpadding=XPADDING, ypadding=YPADDING, yoptions=gtk.SHRINK)

    show_all_local_widgets(locals())