Search code examples
pythontagspygtkgtk-textbuffer

Displaying tags correctly using TextBuffer pyGtk


I was playing around with pygtk to build my little project, that uses gtk, basically there is a window, in this window I have a treeview, inside the treeview a gtk.liststore.

Here is a image with the window and its values

Everything was cool until I realized that I was needing some tags, saw a lot of examples using pango, and it's work, at least until the row selected is changed.

I got around the problem with something not much elegant, and here a link to the complete code:

    def on_toolbar_button_clicked(self, widget, tag):
        bounds_front    = self.text_buffer_front.get_selection_bounds()
        bounds_back     = self.text_buffer_back.get_selection_bounds()

        if len(bounds_front) != 0:

            (start, end)        = bounds_front
            selection_front      = self.text_buffer_front.get_text(start, end, True)
            get_insert_front     = self.text_buffer_front.get_insert()

            self.text_buffer_front.delete(start, end)

            iter_front           = self.text_buffer_front.get_iter_at_mark(get_insert_front)
            self.text_buffer_front.insert(iter_front, tag[0] + selection_front + tag[1])

Basically this method will put <u></u> around a word when I click at the toolbar underline button, and it's value will be placed at liststore, and also display that value with textview. This would be perfect if at least set_text detected these syntaxes.

So, what I'm trying to achieve is something that display the word tagged at textview and when I change the row and get back at the previous tagged row that it still display words tagged, like, if I underline a word, that it still underline when I get back, and in case the solution involves using pango, how can I get the values from it to use later.

What I tried so far was messing around textbuffer.serialize and textbuffer.deserialized, but it didn't worked as I want.

Edit

Like here I had applied underline tag to 'paper', serialized the textbuffer, put it inside a variable, but how can I pass it back to the buffer?

exported = self.text_buffer_front.serialize( self.text_buffer_front, format, start_iter_front, end_iter_front )

Printing the variable 'exported' I get a byte value:

b'GTKTEXTBUFFERCONTENTS-0001\x00\x00\x00w <text_view_markup>\n <tags>\n </tags>\n<text>A
At the first comes rock!  Rock, <apply_tag name="underline">paper</apply_tag>, scissors!

Edit 2

This was probably obvious but not for me, if I have a serialized something all that I'll need to do next is just 'deserialize' it, and for that there is gtk.TextBuffer.deserialize.

The syntaxes should be something like this:

        self.dict_any_tags = {str(key): value[1] for key, value in enumerate(self.sub_list_store)}

    def item_selected(self, *args):
        try:
            iter_start_front        = self.text_buffer_front.get_start_iter()
            iter_end_front          = self.text_buffer_front.get_end_iter()
            path                    = self.selected_row.get_selected_rows()[1][0]

            try:
                self.text_buffer_front.deserialize(self.text_buffer_front, self.text_buffer_front.register_deserialize_tagset(), self.text_buffer_front.get_start_iter(), self.dict_any_tags[str(path)])
            except:
                self.text_buffer_front.set_text(self.sub_list_store[path][1])
        except IndexError:
            pass

    def on_toolbar_button_clicked(self, widget, tag):
        bounds_front    = self.text_buffer_front.get_selection_bounds()
        bounds_back     = self.text_buffer_back.get_selection_bounds()
        path            = self.selected_row.get_selected_rows()[1][0]

        if len(bounds_front) != 0:

            (start, end)        = bounds_front
            selection_front     = self.text_buffer_front.get_text(start, end, True)
            get_insert_front    = self.text_buffer_front.get_insert()

            self.text_buffer_front.apply_tag(tag, start, end)

            start_iter_front    = self.text_buffer_front.get_start_iter()
            end_iter_front      = self.text_buffer_front.get_end_iter()
            format              = self.text_buffer_front.register_serialize_tagset()
            exported            = self.text_buffer_front.serialize( self.text_buffer_front,
                                                                    format,
                                                                    start_iter_front,
                                                                    end_iter_front)

            self.dict_any_tags[str(path)] = exported

The thing is that when I tried it before I was probably putting the deserialize at wrong place and with that it did nothing. Now I can track more easily where are the tags, etc. I just need run some more tests.


Solution

  • The key was create another iterator (I used a dictionary) to track the serialized text, then when I click in a row it tries to use deserialize function if the value is byte, caso is not it will simply set a text normally with set_text.

    Also is important set the text to nothing set_text('') before deserialize, otherwise the previous value of the buffer will be placed in front of the current value at the buffer.

    As for the changes on text, I used the method connect to connect the 'changed' signal and serialized the changes and passed the serialized value to the dictionary. And this is what I got:

            # dictionary to track the tags
            self.dict_any_change_front  = {str(key): value[1] for key, value in enumerate(self.sub_list_store)}
            self.dict_any_change_back   = {str(key): value[1] for key, value in enumerate(self.sub_list_store_back)}
    
        def deserialize(self, text_buffer, exported):
            text_buffer.set_text('')
            text_buffer.deserialize( text_buffer,
                                     text_buffer.register_deserialize_tagset(),
                                     text_buffer.get_start_iter(),
                                     exported )
    
        def item_selected(self, *args):
            #   Need this try/except to silent a indexerror that will occur case the second window close and if opened again,
            # merely cosmetic as it will always occur, just select any row and all good.
            #   The get_selected_rows()[1] will return a empty list at first try when reopening the second window, I just don't know why
    
            try:
                path                    = self.selected_row.get_selected_rows()[1][0]
    
                exported_front          = self.dict_any_change_front[str(path)]
                exported_back           = self.dict_any_change_back[str(path)]
    
                try:
                    if isinstance(exported_front, bytes):
                        self.deserialize(self.text_buffer_front, exported_front)
                    else:
                        self.text_buffer_front.set_text(self.sub_list_store[path][1])
                    if isinstance(exported_back, bytes):
                        self.deserialize(self.text_buffer_back, exported_back)
                    else:
                        self.text_buffer_back.set_text(self.sub_list_store_back[path][1])
                except:
                    self.text_buffer_front.set_text(self.sub_list_store[path][1])
                    self.text_buffer_back.set_text(self.sub_list_store_back[path][1])
    
                self.text_buffer_front.connect('changed', self.editingCard)
                self.text_buffer_back.connect('changed', self.editingCardBack)
    
            except IndexError:
                pass
    
        def editingCard(self, text_buffer):
            path                                = self.selected_row.get_selected_rows()[1][0]
            start_iter_front                    = text_buffer.get_start_iter()
            end_iter_front                      = text_buffer.get_end_iter() 
    
            self.sub_list_store[path][1]        = text_buffer.get_text(start_iter_front, end_iter_front, True)
    
            format                              = text_buffer.register_serialize_tagset()
            exported                            = text_buffer.serialize(    text_buffer,
                                                                            format,
                                                                            start_iter_front,
                                                                            end_iter_front )
            
            self.dict_any_change_front[str(path)] = exported
    
        def editingCardBack(self, text_buffer):
            path                                = self.selected_row.get_selected_rows()[1][0]
            start_iter_back                     = text_buffer.get_start_iter()
            end_iter_back                       = text_buffer.get_end_iter() 
    
            self.sub_list_store_back[path][1]   = text_buffer.get_text(start_iter_back, end_iter_back, True)
    
            format              = text_buffer.register_serialize_tagset()
            exported            = text_buffer.serialize(    text_buffer,
                                                            format,
                                                            start_iter_back,
                                                            end_iter_back   )
            self.dict_any_change_back[str(path)] = exported
    
        def on_toolbar_button_clicked(self, widget, tag_front, tag_back):
            bounds_front    = self.text_buffer_front.get_selection_bounds()
            bounds_back     = self.text_buffer_back.get_selection_bounds()
            path            = self.selected_row.get_selected_rows()[1][0]
    
            ##### FRONT
            if len(bounds_front) != 0:
                (start, end)        = bounds_front
                selection_front     = self.text_buffer_front.get_text(start, end, True)
                get_insert_front    = self.text_buffer_front.get_insert()
    
                self.text_buffer_front.apply_tag(tag_front, start, end)
    
                start_iter_front    = self.text_buffer_front.get_start_iter()
                end_iter_front      = self.text_buffer_front.get_end_iter()
                format              = self.text_buffer_front.register_serialize_tagset()
                exported            = self.text_buffer_front.serialize( self.text_buffer_front,
                                                                        format,
                                                                        start_iter_front,
                                                                        end_iter_front )
                self.dict_any_change_front[str(path)] = exported
    
    
            ###### BACK
            if len(bounds_back) != 0:
                (start, end)        = bounds_back
                selection_back      = self.text_buffer_back.get_text(start, end, True)
                get_insert_back     = self.text_buffer_back.get_insert()
    
                self.text_buffer_back.apply_tag(tag_back, start, end)
    
                start_iter_back     = self.text_buffer_back.get_start_iter()
                end_iter_back       = self.text_buffer_back.get_end_iter()
                format              = self.text_buffer_back.register_serialize_tagset()
                exported            = self.text_buffer_back.serialize(  self.text_buffer_back,
                                                                        format,
                                                                        start_iter_back,
                                                                        end_iter_back )
                self.dict_any_change_back[str(path)] = exported
    

    Working as I wanted :).

    Edit

    I adjusted my code to serialize everything at start and putting at the dictionary, instead of putting strings in the dictionary and as was editing the texts serializing the text and putting it the dictionary, with this was possible remove some if/else's and try/except's.

    Also I created functions to serialize and deserialize thing, and put these functions in another file, I think this way is better.

    • myhandlerfile.py:
    ...
    
    from myfuncfile import serializeIt, deserializeIt
    
    ...
            # dictionary to track the tags
            self.dict_any_change_front  =   {str(key): serializeIt(text_buffer=self.text_buffer_front, tmp_string=value[1]) \
                                            for key, value in enumerate(self.sub_list_store)}
    
            self.dict_any_change_back   =   {str(key): serializeIt(text_buffer=self.text_buffer_back, tmp_string=value[1]) \
                                            for key, value in enumerate(self.sub_list_store_back)}
    
        def item_selected(self, *args):
            #   Silencing a indexerror that will occur in case the window was hided and rised again
            # it is not important, can be ignored
    
            try:
                path                    = self.selected_row.get_selected_rows()[1][0]
    
                exported_front          = self.dict_any_change_front[str(path)]
                exported_back           = self.dict_any_change_back[str(path)]
    
                deserializeIt(self.text_buffer_front, exported_front)
                deserializeIt(self.text_buffer_back, exported_back)
    
                self.text_buffer_front.connect('changed', self.editingCard)
                self.text_buffer_back.connect('changed', self.editingCardBack)
    
            except IndexError:
                pass
    
        def editingCard(self, text_buffer_front):
            #   Silencing a indexerror that will occur in case the window was hided and rised again
            # it is not important, can be ignored
            try:
                path                                    = self.selected_row.get_selected_rows()[1][0]
                start_iter_front                        = text_buffer_front.get_start_iter()
                end_iter_front                          = text_buffer_front.get_end_iter() 
    
                self.sub_list_store[path][1]            = text_buffer_front.get_text(start_iter_front, end_iter_front, True)
    
                exported                                = serializeIt(text_buffer=text_buffer_front)
                self.dict_any_change_front[str(path)]   = exported
            except IndexError:
                pass
    
        def editingCardBack(self, text_buffer_back):
            #   Silencing a indexerror that will occur in case the window was hided and rised again
            # it is not important, can be ignored
            try:
                path                                    = self.selected_row.get_selected_rows()[1][0]
                start_iter_back                         = text_buffer_back.get_start_iter()
                end_iter_back                           = text_buffer_back.get_end_iter() 
    
                self.sub_list_store_back[path][1]       = text_buffer_back.get_text(start_iter_back, end_iter_back, True)
    
                exported                                = serializeIt(text_buffer=text_buffer_back)
                self.dict_any_change_back[str(path)]    = exported
    
            except IndexError:
                pass
    
        def on_toolbar_button_clicked(self, widget, tag_front, tag_back):
            bounds_front    = self.text_buffer_front.get_selection_bounds()
            bounds_back     = self.text_buffer_back.get_selection_bounds()
            path            = self.selected_row.get_selected_rows()[1][0]
    
            ##### FRONT
            if len(bounds_front) != 0:
                (start, end)        = bounds_front
                selection_front     = self.text_buffer_front.get_text(start, end, True)
                get_insert_front    = self.text_buffer_front.get_insert()
    
                self.text_buffer_front.apply_tag(tag_front, start, end)
    
                exported                                = serializeIt(text_buffer=self.text_buffer_front)
                self.dict_any_change_front[str(path)]   = exported
    
    
            ###### BACK
            if len(bounds_back) != 0:
                (start, end)        = bounds_back
                selection_back      = self.text_buffer_back.get_text(start, end, True)
                get_insert_back     = self.text_buffer_back.get_insert()
    
                self.text_buffer_back.apply_tag(tag_back, start, end)
    
                exported                                = serializeIt(text_buffer=self.text_buffer_back)
                self.dict_any_change_back[str(path)]    = exported
    ...
    
    • myfuncfile.py:
    ...
    
    def serializeIt(text_buffer, tmp_string=None):
        if tmp_string:
            text_buffer.set_text(tmp_string)
            tmp_start_iter  = text_buffer.get_start_iter()
            tmp_end_iter    = text_buffer.get_end_iter()
            tmp_format      = text_buffer.register_serialize_tagset()
            tmp_exported    = text_buffer.serialize( text_buffer,
                                                     tmp_format,
                                                     tmp_start_iter,
                                                     tmp_end_iter )
            return tmp_exported
        else:
            start_iter  = text_buffer.get_start_iter()
            end_iter    = text_buffer.get_end_iter()
            format      = text_buffer.register_serialize_tagset()
            exported    = text_buffer.serialize( text_buffer,
                                                 format,
                                                 start_iter,
                                                 end_iter )
            return exported
    
    def deserializeIt(text_buffer, exported):
        text_buffer.set_text('')
        text_buffer.deserialize(text_buffer,
                                text_buffer.register_deserialize_tagset(),
                                text_buffer.get_start_iter(),
                                exported )
    ...