Example #1
0
 def __init__ (self,
               custom_parser = None,
               tags=DEFAULT_TAGS,
               tag_labels=DEFAULT_TAG_LABELS,
               modal=True,
               title=_('Import recipe')):
     self.title = title
     if custom_parser: self.parser = custom_parser
     else: self.parser = RecipeParser()
     self.labels_by_tag = tag_labels
     self.tags_by_label = {self.NEW_REC_TEXT:'newrec'}
     for k,v in self.labels_by_tag.items(): self.tags_by_label[v]=k
     self.tags = tags
     self.setup_window()
     self.setup_action_area()
     self.markup_marks = {}; self.markup_partners = {}
     self.anchors = []
     self.midno = 0 # an ID counter for markup marks we insert
     self.labelled = []
     self.label_counts = {}
     self.modal = modal # If we're in an embedded gtk mainloop...
     ConvenientImporter.__init__(self)
Example #2
0
class InteractiveImporter(ConvenientImporter, NotThreadSafe):

    NEW_REC_TEXT = _('New Recipe')

    def __init__(self,
                 custom_parser=None,
                 tags=DEFAULT_TAGS,
                 tag_labels=DEFAULT_TAG_LABELS,
                 modal=True,
                 title=_('Import recipe')):
        self.title = title
        if custom_parser: self.parser = custom_parser
        else: self.parser = RecipeParser()
        self.labels_by_tag = tag_labels
        self.tags_by_label = {self.NEW_REC_TEXT: 'newrec'}
        for k, v in self.labels_by_tag.items():
            self.tags_by_label[v] = k
        self.tags = tags
        self.setup_window()
        self.setup_action_area()
        self.markup_marks = {}
        self.markup_partners = {}
        self.anchors = []
        self.midno = 0  # an ID counter for markup marks we insert
        self.labelled = []
        self.label_counts = {}
        self.modal = modal  # If we're in an embedded gtk mainloop...
        ConvenientImporter.__init__(self)

    def setup_window(self):
        # set our parent...
        from gourmet.threadManager import get_thread_manager_gui
        import gourmet.GourmetRecipeManager
        tmg = get_thread_manager_gui()
        self.w = gtk.Window()
        self.w.set_title(self.title)
        main_app = gourmet.GourmetRecipeManager.get_application()
        self.w.set_transient_for(main_app.window)
        self.w.set_destroy_with_parent(False)
        self.hb = gtk.HBox()
        self.w.add(self.hb)
        self.tv = gtk.TextView()
        self.tv.set_size_request(600, 500)
        self.tv.set_wrap_mode(gtk.WRAP_WORD)
        self.action_area = gtk.VBox()
        sw = gtk.ScrolledWindow()
        sw.add(self.tv)
        sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
        self.hb.add(sw)
        sw.show()
        self.tv.show()
        self.hb.pack_end(self.action_area, expand=False)
        self.action_area.show()
        self.tb = self.tv.get_buffer()
        self.setup_tags()

    def setup_action_area(self):
        # Set up hard-coded functional buttons...
        self.new_recipe_button = gtk.Button(_('_New Recipe'))
        self.new_recipe_button.connect('clicked', self.new_recipe_cb)
        self.remove_markup_button = gtk.Button(_('Clear _Tags'))
        self.remove_markup_button.connect('clicked', self.clear_tags)
        # Set up ActionModel (used for drop-down menu version of these commands)
        self.action_model = gtk.ListStore(str, str)
        action_table = gtk.Table()
        self.action_area.pack_start(action_table, expand=False)
        r = 0  #rownum
        # Get our UI layout from UI_TAG_ORDER
        for label, rows in UI_TAG_ORDER:
            if r != 0:
                blank = gtk.Label('')
                action_table.attach(blank, 0, 2, r, r + 1)
                blank.show()
            r += 1
            l = gtk.Label()
            l.set_markup('<b>' + label + '</b>')
            l.set_alignment(0.0, 0.5)
            action_table.attach(l, 0, 2, r, r + 1)
            l.show()
            r += 1
            for row in rows:
                for c, t in enumerate(row):  #column number, tag
                    if t == 'clear':
                        tag_button = self.remove_markup_button
                    elif t == 'newrec':
                        tag_button = self.new_recipe_button
                    else:
                        tag_button = gtk.Button('_' + self.labels_by_tag[t])
                        self.action_model.append([self.labels_by_tag[t], t])
                        tag_button.connect('clicked', self.label_callback,
                                           self.labels_by_tag[t])
                    action_table.attach(
                        tag_button,
                        c,
                        c + 1,
                        r,
                        r + 1,
                        xpadding=12,
                    )
                r += 1
        action_table.set_row_spacings(3)
        action_table.set_col_spacings(3)
        #for t in self.tags:
        #
        #    self.action_model.append([self.labels_by_tag[t],t])
        #    tag_button = gtk.Button('_'+self.labels_by_tag[t])
        #    tag_button.connect('clicked',
        #                       self.label_callback,
        #                       self.labels_by_tag[t])
        #    self.action_area.pack_start(tag_button,expand=False,fill=False,padding=6)
        #    tag_button.show()
        #
        self.import_button = gtk.Button(_('Import Recipe'))
        self.import_button.connect('clicked',
                                   lambda *args: self.commit_changes())
        self.import_button.set_alignment(0.5, 1.0)
        self.action_area.pack_end(self.import_button, fill=False, expand=False)
        self.action_area.show_all()

    def setup_tags(self):
        self.markup_tag = gtk.TextTag('markup')
        self.markup_tag.set_property('editable', False)
        self.markup_tag.set_property('scale', pango.SCALE_SMALL)
        self.markup_tag.set_property('rise', 15)
        self.markup_tag.set_property('foreground', '#f00')
        self.ignore_tag = gtk.TextTag('ignore')
        self.ignore_tag.set_property('invisible', True)
        self.ignore_tag.set_property('editable', False)
        self.tb.get_tag_table().add(self.markup_tag)
        self.tb.get_tag_table().add(self.ignore_tag)

    def label_callback(self, button, label):
        self.label_selection(label)

    def label_selection(self, label):
        cursel = self.tb.get_selection_bounds()
        if cursel:
            st, end = cursel
        else:
            # Otherwise, there's no clear sane default... we'll just
            # select the current whole line
            cur_mark = self.tb.get_insert()
            cur_pos = gtk.TextBuffer.get_iter_at_mark(cur_pos)
            cur_pos.backward_chars(cur_pos.get_line_offset())
            st = cur_pos
            end = cur_pos.copy()
            end = cur_pos.forward_line()
        self.label_range(st, end, label)

    def insert_with_label(self, st, text, label):
        start_offset = st.get_offset()
        self.tb.insert(st, text)
        end_offset = start_offset + len(text)
        self.label_range(self.tb.get_iter_at_offset(start_offset),
                         self.tb.get_iter_at_offset(end_offset), label)

    def unhide_area(self, midno):
        st, end = self.markup_marks[midno]
        self.tb.remove_tag(self.ignore_tag, self.tb.get_iter_at_mark(st),
                           self.tb.get_iter_at_mark(end))

    def hide_range(self, st, end):
        """Hide text between start and end.

        Return midno that can be used to unhide the range."""
        midno = self.midno
        self.midno += 1
        start_mark = gtk.TextMark('start-markup-%s' % midno, False)
        end_mark = gtk.TextMark('end-markup-%s' % midno, True)
        self.tb.apply_tag(self.ignore_tag, st, end)
        self.tb.add_mark(start_mark, st)
        self.tb.add_mark(end_mark, end)
        self.markup_marks[midno] = (start_mark, end_mark)
        return midno

    def label_range(self, st, end, label):
        if self.tags_by_label.get(label, '') == 'ignore':
            midno = self.hide_range(st, end)
            b = gtk.Button('Ignored text: Reveal hidden text')
            anchor = self.insert_widget(end, b)

            def unhide_text(*args):
                self.unhide_area(midno)
                self.remove_widget(anchor)

            b.connect('clicked', unhide_text)
            b.show()
            return
        if self.label_counts.has_key(label):
            count = self.label_counts[label]
            self.label_counts[label] += 1
        else:
            self.label_counts[label] = 1
            count = 0
        smark = gtk.TextMark(label + '-' + str(count) + '-start', True)
        emark = gtk.TextMark(label + '-' + str(count) + '-end', False)
        self.tb.add_mark(smark, st)
        self.tb.add_mark(emark, end)
        self.labelled.append((smark, emark))
        # Now we add the labels...
        start_txt = '['
        start_id = self.insert_markup_text(st, start_txt, self.markup_tag)
        # Now move the mark back up...
        new_pos = self.tb.get_iter_at_mark(smark)
        new_pos.forward_chars(len(start_txt))
        self.tb.move_mark(smark, new_pos)
        # Create a "Remove me" button
        #b = gtk.Button('_Remove tag'); b.show)(
        b = gtk.Button()
        i = gtk.Image()
        i.set_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU)
        b.add(i)
        i.show()
        itr = self.tb.get_iter_at_mark(emark)
        anchor = self.insert_widget(itr, b)
        # Set up combo button...
        labelbutton = gtk.combo_box_new_text()
        labelbutton.set_model(self.action_model)
        cb.cb_set_active_text(labelbutton, label)
        anchor2 = self.insert_widget(self.tb.get_iter_at_mark(smark),
                                     labelbutton)
        # Add final bracket for end of markup
        end_bracket_itr = self.tb.get_iter_at_mark(emark)
        end_id = self.insert_markup_text(end_bracket_itr, ']', self.markup_tag)
        self.markup_partners[start_id] = end_id
        self.markup_partners[end_id] = start_id
        # Now back up our itr one character (it got advanced by adding
        # the right bracket and the button)
        eitr = self.tb.get_iter_at_mark(emark)
        eitr.backward_chars(2)
        self.tb.move_mark(emark, eitr)

        # Define callback to remove our text when button is clicked
        def remove_markup(*args):
            self.labelled.remove((smark, emark))
            self.remove_markup_text(start_id)
            self.remove_markup_text(end_id)
            self.remove_widget(anchor)
            self.remove_widget(anchor2)

        def change_mark(cb):
            # copy marks for safekeeping...
            new_text = cb.get_active_text()
            sm = gtk.TextMark(None, True)
            self.tb.add_mark(sm, self.tb.get_iter_at_mark(smark))
            em = gtk.TextMark(None, False)
            self.tb.add_mark(em, self.tb.get_iter_at_mark(emark))
            # remove old marks...
            remove_markup()
            # And relabel!
            self.label_range(self.tb.get_iter_at_mark(sm),
                             self.tb.get_iter_at_mark(em), new_text)

        labelbutton.connect('changed', change_mark)
        b.connect('clicked', remove_markup)

    def new_recipe_cb(self, *args):
        # Start a new recipe at cursor
        itr = self.tb.get_iter_at_mark(self.tb.get_insert())
        self.label_range(itr, itr, self.NEW_REC_TEXT)

    def insert_markup_text(self, itr, text, *tags):
        """Insert markup text into the buffer. We do this in such a
        way that we can remove it easily later.
        """
        midno = self.midno
        self.midno += 1
        start_mark = gtk.TextMark('start-markup-%s' % midno, False)
        end_mark = gtk.TextMark('end-markup-%s' % midno, True)
        start_offset = itr.get_offset()
        if tags:
            self.tb.insert_with_tags(itr, text, *tags)
        else:
            self.tb.insert(itr, text)
        self.tb.add_mark(start_mark, self.tb.get_iter_at_offset(start_offset))
        end_offset = start_offset + len(text)
        end_itr = self.tb.get_iter_at_offset(end_offset)
        self.tb.add_mark(end_mark, end_itr)
        self.markup_marks[midno] = (start_mark, end_mark)
        return midno

    def change_mark(self, cb, smark, emark, start_id, end_id):

        new_label = cb.get_active_text()

    def insert_widget(self, itr, widget):
        anchor = self.tb.create_child_anchor(itr)
        self.anchors.append(anchor)
        self.tv.add_child_at_anchor(widget, anchor)
        widgetstart = self.tb.get_iter_at_child_anchor(anchor)
        widgetend = widgetstart.copy()
        widgetend.forward_char()
        self.tb.apply_tag(self.markup_tag, widgetstart, widgetend)
        widget.show()
        return anchor

    def remove_widget(self, anchor):
        anchor_iter = self.tb.get_iter_at_child_anchor(anchor)
        delete_to = anchor_iter.copy()
        delete_to.forward_char()
        self.tb.delete(anchor_iter, delete_to)

    def remove_markup_text(self, idno):
        smark, emark = self.markup_marks[idno]
        sitr, eitr = (self.tb.get_iter_at_mark(smark),
                      self.tb.get_iter_at_mark(emark))
        self.tb.delete(sitr, eitr)

    def clear_tags(self, *args):
        """Clear all markup in current selection, or whole buffer if
        there is no selection
        """
        cursel = self.tb.get_selection_bounds()
        if cursel:
            st, end = cursel
        else:
            st, end = self.tb.get_bounds()
        st_offset = st.get_offset()
        e_offset = end.get_offset()
        for idno, iters in self.markup_marks.items():
            lst, lend = iters
            if ((e_offset > self.tb.get_iter_at_mark(lst).get_offset() >
                 st_offset)
                    or (e_offset > self.tb.get_iter_at_mark(lend).get_offset()
                        > st_offset)):
                self.remove_markup_text(idno)
                if self.markup_partners.has_key(idno):
                    self.remove_markup_text(self.markup_partners[idno])
        for lst, lend in self.labelled[:]:
            if ((e_offset > self.tb.get_iter_at_mark(lst).get_offset() >
                 st_offset)
                    or (e_offset > self.tb.get_iter_at_mark(lend).get_offset()
                        > st_offset)):
                self.labelled.remove((lst, lend))
        for anchor in self.anchors[:]:
            anchor_iter = self.tb.get_iter_at_child_anchor(anchor)
            if e_offset > anchor_iter.get_offset() > st_offset:
                self.anchors.remove(anchor)
                self.remove_widget(anchor)

    def commit_changes(self):
        def mark_sorter(a, b):
            a = self.tb.get_iter_at_mark(a[0]).get_offset()
            b = self.tb.get_iter_at_mark(b[0]).get_offset()
            return cmp(a, b)

        self.labelled.sort(mark_sorter)
        if not self.labelled: return
        self.start_rec()
        started = False
        for smark, emark in self.labelled:
            siter = self.tb.get_iter_at_mark(smark)
            eiter = self.tb.get_iter_at_mark(emark)
            text = siter.get_text(eiter)
            name = smark.get_name()
            label = name.split('-')[0]
            tag = self.tags_by_label[label]
            if tag in gglobals.TEXT_ATTR_DIC:
                self.add_text(tag, text)
                started = True
            elif tag in gglobals.REC_ATTR_DIC:
                if text: self.add_attribute(tag, text)
            elif tag == 'ingredient':
                if text:
                    self.add_ing_from_text(text)
                    started = True
            elif tag == 'ingredients':
                if text:
                    self.add_ings_from_text(text)
                    started = True
            elif tag == 'inggroup':
                if text:
                    self.add_ing_group(text)
                    started = True
            elif tag == 'newrec':
                if not started: continue
                # Then we're starting a new recipe at this point...
                # Commit old recipe...
                self.commit_rec()
                started = False
                # Start new one...
                self.start_rec()
            elif tag == 'ignore':
                continue
            elif tag == 'servings':
                self.add_attribute('yields', text)
                self.add_attribute('yield_unit', 'servings')
            else:
                try:
                    print 'UNKNOWN TAG', tag, text, label
                except UnicodeError:
                    print 'UNKNOWN TAG (unprintable)'
        if started: self.commit_rec()
        if hasattr(self, 'images') and self.images:
            # This is ugly -- we run the dialog once per recipe. This
            # should happen rarely in current use-case (I don't know
            # of a usecase where many recipes will come from a single
            # text document / website); if in fact this becomes a
            # common usecase, we'll need to rework the UI here.
            for rec in self.added_recs:
                ibd = imageBrowser.ImageBrowserDialog(
                    title=_('Select recipe image'),
                    label=_('Select image for recipe "%s"') %
                    escape(rec.title or _('Untitled')),
                    sublabel=
                    _("Below are all the images found for the page you are importing. Select any images that are of the recipe, or don't select anything if you don't want any of these images."
                      ),
                )
                for i in self.images:
                    ibd.add_image_from_uri(i)
                ibd.run()
                if ibd.ret:
                    ifi = file(imageBrowser.get_image_file(ibd.ret), 'r')
                    image_str = ifi.read()
                    ifi.close()
                    image = ImageExtras.get_image_from_string(image_str)
                    # Adding image!
                    thumb = ImageExtras.resize_image(image, 40, 40)
                    self.rd.modify_rec(
                        rec, {
                            'image': ImageExtras.get_string_from_image(image),
                            'thumb': ImageExtras.get_string_from_image(thumb),
                        })
        if self.modal:
            self.w.hide()
            gtk.main_quit()

    def set_text(self, txt):
        txt = unicode(txt)  # convert to unicode for good measure
        txt = re.sub('(\n\s*\n)+', '\n\n', txt)  # Take out extra newlines
        txt = self.parser.parse(txt)  # Parse
        self.set_parsed(txt)

    def set_parsed(self, parsed):
        #dbg_file = file('/tmp/out','w')
        for chunk, tag in parsed:
            #dbg_file.write(chunk)
            if tag == None:
                self.tb.insert(self.tb.get_end_iter(), chunk)
            else:
                self.insert_with_label(self.tb.get_end_iter(), chunk,
                                       self.labels_by_tag.get(tag, tag))
        #dbg_file.close()

    def do_run(self):
        self.w.show_all()
        if self.modal:
            self.w.connect('delete-event', gtk.main_quit)
            gtk.main()
        else:
            self.w.connect('delete-event', lambda *args: self.w.hide())
class InteractiveImporter (ConvenientImporter, NotThreadSafe):

    NEW_REC_TEXT = _('New Recipe')

    def __init__ (self,
                  custom_parser = None,
                  tags=DEFAULT_TAGS,
                  tag_labels=DEFAULT_TAG_LABELS,
                  modal=True,
                  title=_('Import recipe')):
        self.title = title
        if custom_parser: self.parser = custom_parser
        else: self.parser = RecipeParser()
        self.labels_by_tag = tag_labels
        self.tags_by_label = {self.NEW_REC_TEXT:'newrec'}
        for k,v in self.labels_by_tag.items(): self.tags_by_label[v]=k
        self.tags = tags
        self.setup_window()
        self.setup_action_area()
        self.markup_marks = {}; self.markup_partners = {}
        self.anchors = []
        self.midno = 0 # an ID counter for markup marks we insert
        self.labelled = []
        self.label_counts = {}
        self.modal = modal # If we're in an embedded gtk mainloop...
        ConvenientImporter.__init__(self)

    def setup_window (self):
        # set our parent...
        from gourmet.threadManager import get_thread_manager_gui
        import gourmet.GourmetRecipeManager
        tmg = get_thread_manager_gui()
        self.w = gtk.Window();
        self.w.set_title(self.title)
        main_app = gourmet.GourmetRecipeManager.get_application()
        self.w.set_transient_for(main_app.window)
        self.w.set_destroy_with_parent(False)
        self.hb = gtk.HBox()
        self.w.add(self.hb)
        self.tv = gtk.TextView()
        self.tv.set_size_request(600,500)
        self.tv.set_wrap_mode(gtk.WRAP_WORD)
        self.action_area = gtk.VBox()
        sw = gtk.ScrolledWindow(); sw.add(self.tv)
        sw.set_policy(gtk.POLICY_NEVER,gtk.POLICY_AUTOMATIC)
        self.hb.add(sw); sw.show(); self.tv.show()
        self.hb.pack_end(self.action_area,expand=False); self.action_area.show()
        self.tb = self.tv.get_buffer()
        self.setup_tags()

    def setup_action_area (self):
        # Set up hard-coded functional buttons...
        self.new_recipe_button = gtk.Button(_('_New Recipe'))
        self.new_recipe_button.connect('clicked',self.new_recipe_cb)
        self.remove_markup_button = gtk.Button(_('Clear _Tags'))
        self.remove_markup_button.connect('clicked',self.clear_tags)
        # Set up ActionModel (used for drop-down menu version of these commands)
        self.action_model = gtk.ListStore(str,str)
        action_table = gtk.Table()
        self.action_area.pack_start(action_table,expand=False)
        r = 0 #rownum
        # Get our UI layout from UI_TAG_ORDER
        for label,rows in UI_TAG_ORDER:
            if r != 0:
                blank = gtk.Label('')
                action_table.attach(blank,0,2,r,r+1);blank.show()
            r += 1
            l = gtk.Label(); l.set_markup('<b>'+label+'</b>')
            l.set_alignment(0.0,0.5)
            action_table.attach(l,0,2,r,r+1); l.show()
            r += 1
            for row in rows:
                for c,t in enumerate(row): #column number, tag
                    if t == 'clear':
                        tag_button = self.remove_markup_button
                    elif t=='newrec':
                        tag_button = self.new_recipe_button
                    else:
                        tag_button = gtk.Button('_'+self.labels_by_tag[t])
                        self.action_model.append([self.labels_by_tag[t],t])
                        tag_button.connect('clicked',
                                           self.label_callback,
                                           self.labels_by_tag[t])
                    action_table.attach(
                        tag_button,
                        c,c+1,r,r+1,
                        xpadding=12,
                        )
                r += 1
        action_table.set_row_spacings(3)
        action_table.set_col_spacings(3)
        #for t in self.tags:
        #
        #    self.action_model.append([self.labels_by_tag[t],t])
        #    tag_button = gtk.Button('_'+self.labels_by_tag[t])
        #    tag_button.connect('clicked',
        #                       self.label_callback,
        #                       self.labels_by_tag[t])
        #    self.action_area.pack_start(tag_button,expand=False,fill=False,padding=6)
        #    tag_button.show()
        #
        self.import_button = gtk.Button(_('Import Recipe'))
        self.import_button.connect('clicked',
                                      lambda *args: self.commit_changes())
        self.import_button.set_alignment(0.5,1.0)
        self.action_area.pack_end(self.import_button,fill=False,expand=False)
        self.action_area.show_all()

    def setup_tags (self):
        self.markup_tag = gtk.TextTag('markup')
        self.markup_tag.set_property('editable',False)
        self.markup_tag.set_property('scale',pango.SCALE_SMALL)
        self.markup_tag.set_property('rise',15)
        self.markup_tag.set_property('foreground',
                                     '#f00'
                                     )
        self.ignore_tag = gtk.TextTag('ignore')
        self.ignore_tag.set_property('invisible',True)
        self.ignore_tag.set_property('editable',False)
        self.tb.get_tag_table().add(self.markup_tag)
        self.tb.get_tag_table().add(self.ignore_tag)

    def label_callback (self, button, label):
        self.label_selection(label)

    def label_selection (self, label):
        cursel = self.tb.get_selection_bounds()
        if cursel:
            st,end = cursel
        else:
            # Otherwise, there's no clear sane default... we'll just
            # select the current whole line
            cur_mark = self.tb.get_insert()
            cur_pos=gtk.TextBuffer.get_iter_at_mark(cur_pos)
            cur_pos.backward_chars(
                cur_pos.get_line_offset())
            st = cur_pos
            end = cur_pos.copy()
            end = cur_pos.forward_line()
        self.label_range(st,end,label)

    def insert_with_label (self, st, text, label):
        start_offset = st.get_offset()
        self.tb.insert(st,text)
        end_offset = start_offset + len(text)
        self.label_range(
            self.tb.get_iter_at_offset(start_offset),
            self.tb.get_iter_at_offset(end_offset),
            label
            )

    def unhide_area (self, midno):
        st,end = self.markup_marks[midno]
        self.tb.remove_tag(self.ignore_tag,
                           self.tb.get_iter_at_mark(st),
                           self.tb.get_iter_at_mark(end)
                           )

    def hide_range (self, st, end):
        """Hide text between start and end.

        Return midno that can be used to unhide the range."""
        midno = self.midno; self.midno += 1
        start_mark = gtk.TextMark('start-markup-%s'%midno,False)
        end_mark = gtk.TextMark('end-markup-%s'%midno,True)
        self.tb.apply_tag(self.ignore_tag,
                       st,end)
        self.tb.add_mark(start_mark,st)
        self.tb.add_mark(end_mark,end)
        self.markup_marks[midno] = (start_mark,end_mark)
        return midno

    def label_range (self, st, end, label):
        if self.tags_by_label.get(label,'')=='ignore':
            midno = self.hide_range(st,end)
            b = gtk.Button('Ignored text: Reveal hidden text')
            anchor = self.insert_widget(end,b)
            def unhide_text (*args):
                self.unhide_area(midno)
                self.remove_widget(anchor)
            b.connect('clicked',unhide_text)
            b.show()
            return
        if self.label_counts.has_key(label):
            count = self.label_counts[label]
            self.label_counts[label] += 1
        else:
            self.label_counts[label] = 1
            count = 0
        smark = gtk.TextMark(label+'-'+str(count)+'-start',True)
        emark = gtk.TextMark(label+'-'+str(count)+'-end',False)
        self.tb.add_mark(smark,st)
        self.tb.add_mark(emark,end)
        self.labelled.append((smark,emark))
        # Now we add the labels...
        start_txt = '['
        start_id = self.insert_markup_text(st,start_txt,self.markup_tag)
        # Now move the mark back up...
        new_pos = self.tb.get_iter_at_mark(smark); new_pos.forward_chars(len(start_txt))
        self.tb.move_mark(smark,new_pos)
        # Create a "Remove me" button
        #b = gtk.Button('_Remove tag'); b.show)(
        b = gtk.Button()
        i = gtk.Image(); i.set_from_stock(gtk.STOCK_REMOVE,gtk.ICON_SIZE_MENU)
        b.add(i); i.show()
        itr = self.tb.get_iter_at_mark(emark)
        anchor = self.insert_widget(itr,b)
        # Set up combo button...
        labelbutton = gtk.combo_box_new_text()
        labelbutton.set_model(self.action_model)
        cb.cb_set_active_text(labelbutton,label)
        anchor2 = self.insert_widget(self.tb.get_iter_at_mark(smark),labelbutton)
        # Add final bracket for end of markup
        end_bracket_itr = self.tb.get_iter_at_mark(emark)
        end_id = self.insert_markup_text(end_bracket_itr,']',self.markup_tag)
        self.markup_partners[start_id]=end_id; self.markup_partners[end_id]=start_id
        # Now back up our itr one character (it got advanced by adding
        # the right bracket and the button)
        eitr = self.tb.get_iter_at_mark(emark)
        eitr.backward_chars(2)
        self.tb.move_mark(emark,eitr)
        # Define callback to remove our text when button is clicked
        def remove_markup (*args):
            self.labelled.remove((smark,emark))
            self.remove_markup_text(start_id)
            self.remove_markup_text(end_id)
            self.remove_widget(anchor)
            self.remove_widget(anchor2)
        def change_mark (cb):
            # copy marks for safekeeping...
            new_text = cb.get_active_text()
            sm = gtk.TextMark(None,True)
            self.tb.add_mark(sm,self.tb.get_iter_at_mark(smark))
            em = gtk.TextMark(None,False)
            self.tb.add_mark(em,self.tb.get_iter_at_mark(emark))
            # remove old marks...
            remove_markup()
            # And relabel!
            self.label_range(
                self.tb.get_iter_at_mark(sm),
                self.tb.get_iter_at_mark(em),
                new_text
                )
        labelbutton.connect('changed',change_mark)
        b.connect('clicked',remove_markup)

    def new_recipe_cb (self, *args):
        # Start a new recipe at cursor
        itr = self.tb.get_iter_at_mark(self.tb.get_insert())
        self.label_range(itr,itr,self.NEW_REC_TEXT)

    def insert_markup_text (self, itr, text, *tags):
        """Insert markup text into the buffer. We do this in such a
        way that we can remove it easily later.
        """
        midno = self.midno; self.midno += 1
        start_mark = gtk.TextMark('start-markup-%s'%midno,False)
        end_mark = gtk.TextMark('end-markup-%s'%midno,True)
        start_offset = itr.get_offset()
        if tags:
            self.tb.insert_with_tags(itr,text,*tags)
        else:
            self.tb.insert(itr,text)
        self.tb.add_mark(start_mark,self.tb.get_iter_at_offset(start_offset))
        end_offset = start_offset + len(text)
        end_itr = self.tb.get_iter_at_offset(end_offset)
        self.tb.add_mark(end_mark,end_itr)
        self.markup_marks[midno] = (start_mark,end_mark)
        return midno

    def change_mark (self, cb, smark, emark, start_id, end_id):

        new_label = cb.get_active_text()


    def insert_widget (self, itr, widget):
        anchor = self.tb.create_child_anchor(itr)
        self.anchors.append(anchor)
        self.tv.add_child_at_anchor(widget,anchor)
        widgetstart = self.tb.get_iter_at_child_anchor(anchor)
        widgetend = widgetstart.copy(); widgetend.forward_char()
        self.tb.apply_tag(self.markup_tag,widgetstart,widgetend)
        widget.show()
        return anchor

    def remove_widget (self, anchor):
        anchor_iter = self.tb.get_iter_at_child_anchor(anchor)
        delete_to = anchor_iter.copy()
        delete_to.forward_char()
        self.tb.delete(anchor_iter,delete_to)

    def remove_markup_text (self, idno):
        smark,emark = self.markup_marks[idno]
        sitr,eitr = (self.tb.get_iter_at_mark(smark),
                     self.tb.get_iter_at_mark(emark))
        self.tb.delete(sitr,eitr)

    def clear_tags (self, *args):
        """Clear all markup in current selection, or whole buffer if
        there is no selection
        """
        cursel = self.tb.get_selection_bounds()
        if cursel:
            st,end = cursel
        else:
            st,end = self.tb.get_bounds()
        st_offset = st.get_offset()
        e_offset = end.get_offset()
        for idno,iters in self.markup_marks.items():
            lst,lend = iters
            if ((e_offset > self.tb.get_iter_at_mark(lst).get_offset() > st_offset)
                or
                (e_offset > self.tb.get_iter_at_mark(lend).get_offset() > st_offset)):
                self.remove_markup_text(idno)
                if self.markup_partners.has_key(idno):
                    self.remove_markup_text(self.markup_partners[idno])
        for lst,lend in self.labelled[:]:
            if ((e_offset > self.tb.get_iter_at_mark(lst).get_offset() > st_offset)
                or
                (e_offset > self.tb.get_iter_at_mark(lend).get_offset() > st_offset)):
                self.labelled.remove((lst,lend))
        for anchor in self.anchors[:]:
            anchor_iter = self.tb.get_iter_at_child_anchor(anchor)
            if e_offset > anchor_iter.get_offset() > st_offset:
                self.anchors.remove(anchor)
                self.remove_widget(anchor)

    def commit_changes (self):
        def mark_sorter (a,b):
            a = self.tb.get_iter_at_mark(a[0]).get_offset()
            b = self.tb.get_iter_at_mark(b[0]).get_offset()
            return cmp(a,b)
        self.labelled.sort(mark_sorter)
        if not self.labelled: return
        self.start_rec()
        started = False
        for smark,emark in self.labelled:
            siter = self.tb.get_iter_at_mark(smark)
            eiter = self.tb.get_iter_at_mark(emark)
            text = siter.get_text(eiter)
            name = smark.get_name()
            label = name.split('-')[0]
            tag = self.tags_by_label[label]
            if tag in gglobals.TEXT_ATTR_DIC:
                self.add_text(tag,text); started=True
            elif tag in gglobals.REC_ATTR_DIC:
                if text: self.add_attribute(tag,text)
            elif tag == 'ingredient':
                if text: self.add_ing_from_text(text); started=True
            elif tag == 'ingredients':
                if text: self.add_ings_from_text(text); started=True
            elif tag == 'inggroup':
                if text: self.add_ing_group(text); started=True
            elif tag=='newrec':
                if not started: continue
                # Then we're starting a new recipe at this point...
                # Commit old recipe...
                self.commit_rec(); started=False
                # Start new one...
                self.start_rec()
            elif tag=='ignore':
                continue
            elif tag == 'servings':
                self.add_attribute('yields',text)
                self.add_attribute('yield_unit','servings')
            else:
                try:
                    print 'UNKNOWN TAG',tag,text,label
                except UnicodeError:
                    print 'UNKNOWN TAG (unprintable)'
        if started: self.commit_rec()
        if hasattr(self,'images') and self.images:
            # This is ugly -- we run the dialog once per recipe. This
            # should happen rarely in current use-case (I don't know
            # of a usecase where many recipes will come from a single
            # text document / website); if in fact this becomes a
            # common usecase, we'll need to rework the UI here.
            for rec in self.added_recs:
                ibd = imageBrowser.ImageBrowserDialog(
                    title=_('Select recipe image'),
                    label=_('Select image for recipe "%s"')%escape(rec.title or _('Untitled')),
                    sublabel=_("Below are all the images found for the page you are importing. Select any images that are of the recipe, or don't select anything if you don't want any of these images."),
                    )
                for i in self.images: ibd.add_image_from_uri(i)
                ibd.run()
                if ibd.ret:
                    ifi = file(imageBrowser.get_image_file(ibd.ret),'r')
                    image_str = ifi.read(); ifi.close()
                    image = ImageExtras.get_image_from_string(image_str)
                    # Adding image!
                    thumb = ImageExtras.resize_image(image,40,40)
                    self.rd.modify_rec(rec,{'image':ImageExtras.get_string_from_image(image),
                                            'thumb':ImageExtras.get_string_from_image(thumb),
                                            })
        if self.modal:
            self.w.hide()
            gtk.main_quit()

    def set_text (self, txt):
        txt = unicode(txt) # convert to unicode for good measure
        txt = re.sub('(\n\s*\n)+','\n\n',txt) # Take out extra newlines
        txt = self.parser.parse(txt) # Parse
        self.set_parsed(txt)

    def set_parsed (self, parsed):
        #dbg_file = file('/tmp/out','w')
        for chunk,tag in parsed:
            #dbg_file.write(chunk)
            if tag==None:
                self.tb.insert(self.tb.get_end_iter(),
                               chunk)
            else:
                self.insert_with_label(
                    self.tb.get_end_iter(),
                    chunk,
                    self.labels_by_tag.get(tag,tag)
                    )
        #dbg_file.close()

    def do_run (self):
        self.w.show_all()
        if self.modal:
            self.w.connect('delete-event',gtk.main_quit)
            gtk.main()
        else:
            self.w.connect('delete-event',lambda *args: self.w.hide())