Exemple #1
0
 def __init__(self, gui, get_selected_func):
     Action_List.__init__(self)
     self.get_selected = get_selected_func
     self.community_gui = gui
     for event in self.community_gui.user_events:
         self.add_event(event)
     self.community_gui.register_user_action_list(self)
Exemple #2
0
    def initialize_user_action_page(self):
        vbox = gtk.VBox()

        add_user_icon = gtk.gdk.pixbuf_new_from_file_at_size(join(get_dir(ICON_DIR), "64px-plus_icon.png"), ACTION_IMAGE_SIZE, ACTION_IMAGE_SIZE)
        remove_user_icon = gtk.gdk.pixbuf_new_from_file_at_size(join(get_dir(ICON_DIR), "64px-no_icon.png"), ACTION_IMAGE_SIZE, ACTION_IMAGE_SIZE)
        exchange_keys_icon = gtk.gdk.pixbuf_new_from_file_at_size(join(get_dir(ICON_DIR), "key.png"), ACTION_IMAGE_SIZE, ACTION_IMAGE_SIZE)
        refetch_icon =  gtk.gdk.pixbuf_new_from_file_at_size(join(get_dir(ICON_DIR), "64px-edit_metadata_icon.png"), ACTION_IMAGE_SIZE, ACTION_IMAGE_SIZE)

        action_buttons = [(add_user_icon, 'Invite to\nCommunity', self.show_invite_dialog_cb),
                          (refetch_icon, 'Refetch\nProfile', self.refetch_profile_cb),
                         ]
        if self.state_plugin.options.personal_communities:
            action_buttons.insert(0, (add_user_icon, 'Add to\n Community',
                self.show_add_dialog_cb))
            action_buttons.insert(1, (remove_user_icon, 'Remove from\nCommunity',
                self.show_remove_dialog_cb))

        if self.state_plugin.options.key_exchange:
            action_buttons.insert(3, (exchange_keys_icon, 'Exchange\nKeys', self.show_exchange_keys_dialog_cb))

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        vbox.pack_start(self.actions.get_widget())

        self.announce_checkbox = gtk.CheckButton('Make an alarm when user appears')
        vbox.pack_start(self.announce_checkbox, False, False)
        self.announce_checkbox.set_active(self.user.get('friend'))
        self.announce_checkbox.connect('toggled', self.set_announce)

        self.notebook.append_page(vbox, gtk.Label('More actions'))
    def initialize_action_list(self):
        search_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-search_content_icon.png"))
        remove_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-remove_content_icon.png"))

        action_buttons = [(search_icon, 'Search', self.search_cb),
                          (remove_icon, 'Clear', self.clear_cb)
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        self.pack_start(self.actions.get_widget(), False, True)
    def create_action_list(self):
        new_message_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), self.NEW_MESSAGE_ICON))
        refresh_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), self.REFRESH_ICON))
        search_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), self.SEARCH_ICON))

        action_buttons = [(new_message_icon, 'Compose', self.create_new_cb),
                          (refresh_icon, 'Refresh / All\nMessages', self.refresh_cb),
                          (new_message_icon, 'My\nMessages', self.my_messages_cb),
                          (search_icon, 'Search', self.search_cb),
                          (search_icon, 'Watches', self.watches_cb),
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)
Exemple #5
0
    def __init__(self, main_gui, getstatecb, modifycb):
        self.main_gui = main_gui

        self.modifycb = modifycb
        self.getstatecb = getstatecb

        self.guistate = 0

        self.page = GUI_Page('Watches')

        # self.watch_list stores columns: possible picture, keyword, target
        self.watch_list = gtk.ListStore(gtk.gdk.Pixbuf, str, str)
        self.update()

        self.watch_view = gtk.TreeView(self.watch_list)
        self.pic_cell = gtk.CellRendererPixbuf()
        self.name_cell = gtk.CellRendererText()
        self.target_cell = gtk.CellRendererText()
        self.pic_column = self.watch_view.insert_column_with_attributes(
            self.COL_ICON, '', self.pic_cell, pixbuf=self.COL_ICON)
        self.name_column = self.watch_view.insert_column_with_attributes(
            self.COL_NAME, '', self.name_cell, text=self.COL_NAME)
        self.target_column = self.watch_view.insert_column_with_attributes(
            self.COL_TARGET, '', self.target_cell, text=self.COL_TARGET)
        self.page.pack_start(self.watch_view, True, True)

        add_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-add_content_icon.png"))
        remove_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-remove_content_icon.png"))

        action_buttons = [(add_icon, 'Add', self.add_clicked),
                          (remove_icon, 'Remove', self.remove_clicked)
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        self.page.pack_start(self.actions.get_widget(), False, True)

        self.main_gui.add_page(self.page)
    def initialize_action_list(self):
        add_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-add_content_icon.png"))
        remove_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-remove_content_icon.png"))
        open_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-publish_content_icon.png"))
        metadata_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-edit_metadata_icon.png"))

        action_buttons = [(add_icon, 'Publish\nFile', self.add_file_cb),
                          (add_icon, 'Publish\nDirectory', self.add_dir_cb),
                          (remove_icon, 'Remove', self.remove_cb),
                          (open_icon, 'Open', self.open_cb),
                          (metadata_icon, 'Edit\nMetadata', self.edit_metadata_cb)
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        self.pack_start(self.actions.get_widget(), False, True)
    def initialize_action_list(self):
        download_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-browse_content_icon.png"))
        open_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-publish_content_icon.png"))
        metadata_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-edit_metadata_icon.png"))
        search_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-search_content_icon.png"))

        action_buttons = [(download_icon, 'Download', self.download_cb),
                          (open_icon, 'Stream', self.stream_cb),
                          (metadata_icon, 'Show\nMetadata', self.show_metadata_cb),
                          (search_icon, 'Search\nFrom User', self.show_search_cb),
                          (search_icon, 'Refresh', self.refresh_cb)
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        self.pack_start(self.actions.get_widget(), False, True)
class Messageboard_GUI(GUI_Page):

    MSGBOARD_ICON = '64px-msgboard_icon.png'
    MESSAGE_ICON = '64px-messaging_status_icon.png'
    REFRESH_ICON = '64px-refresh_icon.png'
    SEARCH_ICON = '64px-search_icon.png'
    NEW_MESSAGE_ICON = '64px-msgboard_icon.png'

    def __init__(self, gui):
        global community, msgboard, chat

        GUI_Page.__init__(self, 'Messageboard')
        community = get_plugin_by_type(PLUGIN_TYPE_COMMUNITY)
        chat = get_plugin_by_type(PLUGIN_TYPE_MESSAGING)
        msgboard = get_plugin_by_type(PLUGIN_TYPE_MESSAGE_BOARD)

        self.notify = get_plugin_by_type(PLUGIN_TYPE_NOTIFICATION).notify

        self.main_gui = gui

        # Store Message_Page instances, key = sharemeta of the message
        self.message_pages = {}

        messageboard_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), self.MSGBOARD_ICON))

        community.community_gui.register_user_event(messageboard_icon, 'Msg board', self.start_messageboard_cb)
        community.community_gui.register_com_event(messageboard_icon, 'Msg board', self.start_messageboard_cb)

        self.message_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), self.MESSAGE_ICON))

        self.in_search_mode = False
        self.store = None
        self.search_store = None
        self.target = None

        # create widgets here
        self.create_action_list()
        self.message_list, self.message_view = self.create_message_list()

        self.search_hbox = gtk.HBox()
        self.search_entry = new_entry('Enter search keywords')
        self.search_entry.connect('activate', self.search_activate_cb)
        self.search_close = gtk.Button(stock=gtk.STOCK_CLOSE)
        self.search_close.connect('clicked', self.search_close_cb)
        self.search_now = gtk.Button(stock=gtk.STOCK_FIND)
        self.search_now.connect('clicked', self.search_now_cb)
        self.search_hbox.pack_start(self.search_entry, True, True)
        self.search_hbox.pack_start(self.search_close, False, True)
        self.search_hbox.pack_start(self.search_now, False, True)

        self.vbox = gtk.VBox()
        self.vbox.pack_start(self.message_list, True, True)
        self.vbox.pack_start(self.search_hbox, False, True)

        self.pack_start(self.vbox, True, True)
        self.pack_start(self.actions.get_widget(), False, True)

        self.show_all()
        self.search_hbox.hide()
        gui.add_page(self)

        gui.add_key_binding(gtk.gdk.CONTROL_MASK, gtk.keysyms.m, self.key_pressed_ctrl_m)

        msgboard.register_ui(self)

        self.watch_dialog = Watches_GUI(self.main_gui, msgboard.get_state, msgboard.modify_state)

    def append_messages_to_store(self, store, metas):
        # Display hot messages first
        hot = []
        normal = []
        for meta in metas:
            if msgboard.is_hot(meta):
                hot.append((True, meta))
            else:
                normal.append((False, meta))

        for l in hot, normal:
            for (ishot, meta) in l:
                user = None
                uid = meta.get('src')
                if uid != None:
                    user = community.get_user(uid)
                if user == community.get_myself():
                    sender = 'Myself'
                elif user != None:
                    sender = pango_escape(user.tag())
                else:
                    sender = pango_escape(meta.get('from'))
                timestamp = msgtime(meta)
                subject = pango_escape(meta.get('subject'))
                if ishot:
                    line = '%s <span foreground="gray"><small>%s</small></span>\n<span foreground="red">[HOT] </span><b>%s</b>' % (sender, timestamp, subject)
                else:
                    line = '%s <span foreground="gray"><small>%s</small></span>\n<b>%s</b>' % (sender, timestamp, subject)
                icon = self.message_icon
                if user != None:
                    icon = get_user_profile_picture(user).scale_simple(64, 64, gtk.gdk.INTERP_BILINEAR)
                store.append([icon, line, meta])

    def key_pressed_ctrl_m(self, target, ctx):
        self.start_messageboard_cb(target)

    def create_action_list(self):
        new_message_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), self.NEW_MESSAGE_ICON))
        refresh_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), self.REFRESH_ICON))
        search_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), self.SEARCH_ICON))

        action_buttons = [(new_message_icon, 'Compose', self.create_new_cb),
                          (refresh_icon, 'Refresh / All\nMessages', self.refresh_cb),
                          (new_message_icon, 'My\nMessages', self.my_messages_cb),
                          (search_icon, 'Search', self.search_cb),
                          (search_icon, 'Watches', self.watches_cb),
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

    def watches_cb(self, widget):
        self.watch_dialog.show()

    def create_message_list(self):
        message_list = new_scrollarea()
        message_view = gtk.TreeView()
        message_view.set_headers_visible(False)

        cr1 = gtk.CellRendererPixbuf()
        cr2 = gtk.CellRendererText()

        col1 = gtk.TreeViewColumn('Message')
        col1.pack_start(cr1, False)        
        col1.pack_start(cr2, True)

        message_view.append_column(col1)
        col1.add_attribute(cr1, 'pixbuf', 0)
        col1.add_attribute(cr2, 'markup', 1)

        message_view.connect('row-activated', self.row_activated_cb)

        message_list.add(message_view)

        return message_list, message_view

    def create_new_cb(self, widget):
        # HACK: first try Hildon StackableWindow-style
        try:
            composer = Message_Composer(self)
        except:
            composer = Message_Composer(self.main_gui.get_main_window())
        composer.show_all()
        response = composer.run()
        
        if response == 1:
            sender = community.get_myself().tag()            
            subject = composer.get_subject()
            text = composer.get_text()
            cname = None
            if self.target != None:
                cname = self.target.get('name')
            self.publish_message(sender, subject, text, cname)

        composer.destroy()

    def refresh_cb(self, widget, showmine=False):
        if self.in_search_mode:
            self.search_close_cb(None)
        msgboard.query_messages(showmine=showmine, target=self.target)

    def my_messages_cb(self, widget):
        self.refresh_cb(widget, showmine=True)

    def search_cb(self, widget):
        self.in_search_mode = True
        self.search_hbox.show()
        self.search_entry.grab_focus()
        if self.search_store:
            self.message_view.set_model(self.search_store)

    def search_close_cb(self, widget):
        self.in_search_mode = False
        self.search_hbox.hide()
        if self.store:
            self.message_view.set_model(self.store)

    def search_now_cb(self, widget):
        self.search_activate_cb(self.search_entry)

    def search_activate_cb(self, entry):
        self.search_store = gtk.ListStore(gtk.gdk.Pixbuf, str, object)
        self.message_view.set_model(self.search_store)
        text = entry.get_text().strip()
        keywords = text.split(',')
        criteria = None
        if self.target != None:
            criteria = {'community': self.target.get('name')}
        msgboard.search(self.got_query_results, criteria=criteria, keywords=keywords)
        if len(text) > 0:
            msg = 'Searching for %s' % text
        else:
            msg = 'Searching for all messages'
        self.notify(msg, delay=500)

    def got_query_results(self, user, metas, ctx):
        self.append_messages_to_store(self.search_store, metas)

    def row_activated_cb(self, treeview, path, view_column):
        store = treeview.get_model()
        row_iter = store.get_iter(path)
        meta = store.get(row_iter, 2)[0]
        self.view_message(meta)

    def view_message(self, msg):
        page = self.message_pages.get(msg)
        if page == None:
            page = Message_Page(self, msg)
            self.message_pages[msg] = page
        self.main_gui.show_page(page)

    def start_messageboard_cb(self, target):
        if not isinstance(target, Community):
            target = None
        if target == community.get_default_community():
            target = None
        self.target = target
        msgboard.query_messages(target=self.target)
        subtitle = None
        if self.target != None:
            subtitle = target.get('name')
        self.set_page_title(subtitle, sub=True)
        self.main_gui.show_page(self)

    def update_message_list(self, metas):
        self.store = gtk.ListStore(gtk.gdk.Pixbuf, str, object)

        self.append_messages_to_store(self.store, metas)

        if not self.in_search_mode:
            self.message_view.set_model(self.store)

    def message_deleted_cb(self, meta):
        store = self.store
        if store == None:
            return

        for row in store:
            if row[2] == meta:
                store.remove(row.iter)

    def publish_message(self, sender, subject, msg, cname=None):
        d = {'from': sender,
             'subject': subject,
             'msg': msg,
             'timestart': int(time()),
            }
        if cname != None:
            d['community'] = cname
        msgboard.publish(d)
        self.refresh_cb(None)
Exemple #9
0
class User_Page(GUI_Page):

    def __init__(self, gui, community_gui, user):
        """User_Page class is for showing user's profile information
        defined in the Edit Profile dialog and for showing user's
        communities.

        update_user_page() have to be called after user's profile
        is changed or after user's communities are changed so that
        new values are loaded into GUI"""

        GUI_Page.__init__(self, user.get('nick'))

        self.main_gui = gui
        self.community_gui = community_gui
        self.user = user

        self.community = get_plugin_by_type(PLUGIN_TYPE_COMMUNITY)
        self.state_plugin = get_plugin_by_type(PLUGIN_TYPE_STATE)

        self.notebook = gtk.Notebook()
        self.notebook.set_show_tabs(True)
        self.notebook.set_show_border(False)
        self.initialize_profile_page()
        self.initialize_user_action_page()
        self.initialize_communities_page()
        self.pack_start(self.notebook)

        self.show_all()

    def get_user(self):
        return self.user

    def back_action(self):
        self.community_gui.user_pages.pop(self.user)
        self.main_gui.remove_page(self)
        self.destroy()
        return True

    def update_user_page(self):
        """ Function calls other functions to update user's
        profile, community, content and plugin pages. """

        self.update_profile_widgets()
        self.set_page_title(self.user.get('nick'))

    def initialize_user_action_page(self):
        vbox = gtk.VBox()

        add_user_icon = gtk.gdk.pixbuf_new_from_file_at_size(join(get_dir(ICON_DIR), "64px-plus_icon.png"), ACTION_IMAGE_SIZE, ACTION_IMAGE_SIZE)
        remove_user_icon = gtk.gdk.pixbuf_new_from_file_at_size(join(get_dir(ICON_DIR), "64px-no_icon.png"), ACTION_IMAGE_SIZE, ACTION_IMAGE_SIZE)
        exchange_keys_icon = gtk.gdk.pixbuf_new_from_file_at_size(join(get_dir(ICON_DIR), "key.png"), ACTION_IMAGE_SIZE, ACTION_IMAGE_SIZE)
        refetch_icon =  gtk.gdk.pixbuf_new_from_file_at_size(join(get_dir(ICON_DIR), "64px-edit_metadata_icon.png"), ACTION_IMAGE_SIZE, ACTION_IMAGE_SIZE)

        action_buttons = [(add_user_icon, 'Invite to\nCommunity', self.show_invite_dialog_cb),
                          (refetch_icon, 'Refetch\nProfile', self.refetch_profile_cb),
                         ]
        if self.state_plugin.options.personal_communities:
            action_buttons.insert(0, (add_user_icon, 'Add to\n Community',
                self.show_add_dialog_cb))
            action_buttons.insert(1, (remove_user_icon, 'Remove from\nCommunity',
                self.show_remove_dialog_cb))

        if self.state_plugin.options.key_exchange:
            action_buttons.insert(3, (exchange_keys_icon, 'Exchange\nKeys', self.show_exchange_keys_dialog_cb))

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        vbox.pack_start(self.actions.get_widget())

        self.announce_checkbox = gtk.CheckButton('Make an alarm when user appears')
        vbox.pack_start(self.announce_checkbox, False, False)
        self.announce_checkbox.set_active(self.user.get('friend'))
        self.announce_checkbox.connect('toggled', self.set_announce)

        self.notebook.append_page(vbox, gtk.Label('More actions'))

    def initialize_profile_page(self):
        
        profile_hbox = gtk.HBox()
        vbox = gtk.VBox()

        picture_hbox = gtk.HBox()
        self.profile_image = gtk.Image()
        self.profile_image.set_size_request(MAX_FACE_DIMENSION+10, MAX_FACE_DIMENSION+10)
        picture_hbox.pack_start(self.profile_image, False, True)
        self.status_label = gtk.Label()
        self.status_label.set_line_wrap(True)
        picture_hbox.pack_start(self.status_label)
        vbox.pack_start(picture_hbox)

        self.profile_info_label = gtk.Label()
        self.profile_info_label.set_alignment(0.1, 0.01) # 0.01 on purpose
        self.profile_info_label.set_line_wrap(True)
        vbox.pack_start(self.profile_info_label)

        profile_hbox.pack_start(vbox)

        self.user_action_list = User_Action_List(self.community_gui,
            self.get_user)
        profile_hbox.pack_start(self.user_action_list.action_view)

        swindow = new_scrollarea()
        swindow.set_border_width(0)
        swindow.add_with_viewport(profile_hbox)

        self.update_profile_widgets()

        self.notebook.append_page(swindow, gtk.Label('Profile'))

    def initialize_communities_page(self):
        vbox = gtk.VBox()

        self.list = Community_List(self.view_community)
        for com in self.community.get_user_communities(self.user):
            self.list.add_community(com)
        vbox.pack_start(self.list.get_widget())

        self.notebook.append_page(vbox, gtk.Label('User communities'))

    def view_community(self, com):
        self.community_gui.show_com_page(com)

    def update_profile_widgets(self):
        """ Reads new profile information from user and
        updates profile page's widgets."""
        image = get_user_profile_picture(self.user)
        if not self.user.present:
            image.saturate_and_pixelate(image, 0.0, True)
        self.profile_image.set_from_pixbuf(image)
        value = self.user.get('status')
        if value == None:
            value = ''
        self.status_label.set_text(value)
        self.profile_info_label.set_markup(self.construct_profile_info_str())

    def construct_profile_info_str(self):

        def heading(s):
            # Returns a heading string s formatted with pango markup and
            # a new-line
            return '<span color="slategray" weight="bold" size="large">%s</span>\n' % pango_escape(s)

        def field(s):
            value = self.user.get(s)
            if value != None:
                return '<b>%s:</b> %s\n' % (field_descriptions[s], pango_escape(str(value)))
            else:
                return ''

        def join_list(l):
            out = []
            for s in l:
                value = self.user.get(s)
                if value != None:
                    out.append(pango_escape(str(value)))
            if len(out) > 0:
                return ', '.join(out) + '\n'
            else:
                return ''

        s = heading(self.user.get('nick'))
        s += field('name')

        s += join_list(('age', 'gender'))
        s += field('birth_date')

        s += join_list(('city', 'state', 'country'))

        s += field('phone_numbers')
        s += field('email')
        s += field('www')

        s += field('occupation')
        s += field('languages')

        s += field('description')

        s += heading('Last contact')
        l = []
        for (t, location) in self.user.log():
            ss = t
            if len(location) > 0:
                ss += '\n(at %s)' %(location)
            l.append(ss)
        if len(l) == 0:
            l = ['never']
        s += pango_escape('\n'.join(l)) + '\n'

        if get_debug_mode():
            s += heading('Debug information')
            s += field('uid')
            s += field('ip')
            s += field('port')
            s += field('hops')
            s += field('status_icon')
            s += field('v')
            s += field('faceversion')
            s += field('myfaceversion')

        return s

    def show_add_dialog_cb(self, widget):
        Add_To_Community_Dialog(self.main_gui, self.user)

    def show_invite_dialog_cb(self, widget):
        Invite_To_Community_Dialog(self.main_gui, self.user)
        
    def show_remove_dialog_cb(self, widget):
        Remove_From_Community_Dialog(self.main_gui, self.user)
  
    def show_exchange_keys_dialog_cb(self, widget):
        keymanagement = get_plugin_by_type(PLUGIN_TYPE_KEY_MANAGEMENT)
        keymanagement.show_exchange_keys_gui(self.user)

    def refetch_profile_cb(self, widget):
        self.user.force_profile_update()
        notification = get_plugin_by_type(PLUGIN_TYPE_NOTIFICATION)
        notification.notify('Reloading profile for %s' %(self.user.tag()))

    def set_announce(self, widget):
        self.user.set('friend', widget.get_active())
Exemple #10
0
class Watches_GUI:

    # list store columns
    COL_ICON = 0
    COL_NAME = 1
    COL_TARGET = 2

    def __init__(self, main_gui, getstatecb, modifycb):
        self.main_gui = main_gui

        self.modifycb = modifycb
        self.getstatecb = getstatecb

        self.guistate = 0

        self.page = GUI_Page('Watches')

        # self.watch_list stores columns: possible picture, keyword, target
        self.watch_list = gtk.ListStore(gtk.gdk.Pixbuf, str, str)
        self.update()

        self.watch_view = gtk.TreeView(self.watch_list)
        self.pic_cell = gtk.CellRendererPixbuf()
        self.name_cell = gtk.CellRendererText()
        self.target_cell = gtk.CellRendererText()
        self.pic_column = self.watch_view.insert_column_with_attributes(
            self.COL_ICON, '', self.pic_cell, pixbuf=self.COL_ICON)
        self.name_column = self.watch_view.insert_column_with_attributes(
            self.COL_NAME, '', self.name_cell, text=self.COL_NAME)
        self.target_column = self.watch_view.insert_column_with_attributes(
            self.COL_TARGET, '', self.target_cell, text=self.COL_TARGET)
        self.page.pack_start(self.watch_view, True, True)

        add_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-add_content_icon.png"))
        remove_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-remove_content_icon.png"))

        action_buttons = [(add_icon, 'Add', self.add_clicked),
                          (remove_icon, 'Remove', self.remove_clicked)
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        self.page.pack_start(self.actions.get_widget(), False, True)

        self.main_gui.add_page(self.page)

    def show(self):
        self.page.show_all()
        self.main_gui.show_page(self.page)

    def add_clicked(self, widget):
        if self.guistate != 0:
            return
        self.guistate = 1
        Input_Dialog(self.main_gui.main_window, 'Add watch', 'Please give a keyword:', self.watch_added)

    def remove_clicked(self, widget):
        if self.guistate != 0:
            return
        selection = self.watch_view.get_selection()
        if selection == None:
            return
        model, selected = selection.get_selected()
        if selected != None:
            keyword = model[selected][1]
            self.modifycb(False, keyword)
        self.update()

    def update(self):
        self.watch_list.clear()
        for keyword in self.getstatecb():
            self.watch_list.append([None, keyword, ''])

    def watch_added(self, keyword, ctx=None):
        self.guistate = 0
        if keyword:
            self.modifycb(True, keyword)
        self.update()
class File_Sharing_Publish(GUI_Page):
    """ File_Sharing_Publish includes GUI window for adding shares. """

    COL_SHAREMETA = 0
    COL_SHAREPATH = 1
    COL_TYPE = 2
    COL_ICON = 3
    COL_GUINAME = 4
    COL_SIZE = 5

    def __init__(self, fs_gui):
        GUI_Page.__init__(self, 'Publish content')
        self.fs_gui = fs_gui

        self.icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), \
                                                      "64px-publish_content_icon.png"))
    
        self.vbox = gtk.VBox()
        self.pack_start(self.vbox)

        self.folders = {}

        self.sharelist = gtk.TreeStore(gobject.TYPE_PYOBJECT, str, bool, gtk.gdk.Pixbuf, str, str)

        self.initialize_share_list()
        self.initialize_action_list()

        self.title = gtk.Label("Content Publishing")
        self.vbox.pack_start(self.title, False, False)
        scrollwin = new_scrollarea()
        scrollwin.add_with_viewport(self.sharelist_view)
        self.vbox.pack_start(scrollwin, True, True)

        self.show_all()
        main_gui.add_page(self)

    def initialize_share_list(self):
        self.sharelist_view = gtk.TreeView(self.sharelist)
        self.sharelist_view.set_headers_visible(False)

        column = gtk.TreeViewColumn('')
        self.sharelist_view.append_column(column)
        column.set_expand(True)
        cr_icon = gtk.CellRendererPixbuf()
        cr_guiname = gtk.CellRendererText()
        column.pack_start(cr_icon, False)
        column.pack_start(cr_guiname)
        column.add_attribute(cr_icon, 'pixbuf', self.COL_ICON)
        column.add_attribute(cr_guiname, 'text', self.COL_GUINAME)

        column = gtk.TreeViewColumn('')
        self.sharelist_view.append_column(column)
        cr_size = gtk.CellRendererText()
        column.pack_start(cr_size)
        column.add_attribute(cr_size, 'text', self.COL_SIZE)

    def initialize_action_list(self):
        add_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-add_content_icon.png"))
        remove_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-remove_content_icon.png"))
        open_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-publish_content_icon.png"))
        metadata_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-edit_metadata_icon.png"))

        action_buttons = [(add_icon, 'Publish\nFile', self.add_file_cb),
                          (add_icon, 'Publish\nDirectory', self.add_dir_cb),
                          (remove_icon, 'Remove', self.remove_cb),
                          (open_icon, 'Open', self.open_cb),
                          (metadata_icon, 'Edit\nMetadata', self.edit_metadata_cb)
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        self.pack_start(self.actions.get_widget(), False, True)
            
    def show_publish_window(self, target, is_community):
        if is_community:
            target_name = target.get('name')
        else:
            target_name = target.get('nick')

        self.sharelist_view.set_model(self.sharelist)
        self.sharelist_view.show_all()
        
        self.title.set_text('Share Content with %s' %(target_name))

        self.update_sharelist()
        
        main_gui.show_page(self)

    def add_file_cb(self, widget):
        File_Chooser(main_gui.get_main_window(), FILE_CHOOSER_TYPE_FILE, True, self.add_file_chooser_cb)

    def add_file_chooser_cb(self, filenames, ctx):
        if filenames == None:
            return

        for f in filenames:
            if isfile(f):
                sharemeta = Share_Meta()
                sharemeta.set('description', basename(f))
                filesharing.add_share(f, sharemeta=sharemeta, stype=SHARE_FILE)
            else:
                warning('Invalid filename from file chooser dialog\n')

        self.update_sharelist()

    def add_dir_cb(self, widget):
        File_Chooser(main_gui.get_main_window(), FILE_CHOOSER_TYPE_DIR, False, self.add_dir_chooser_cb)

    def add_dir_chooser_cb(self, dir_name, ctx):
        if dir_name == None:
            return

        if isdir(dir_name):
            sharemeta = Share_Meta()
            sharemeta.set('description', basename(dir_name))
            filesharing.add_share(dir_name, sharemeta=sharemeta, stype=SHARE_DIR)
        else:
            warning('Invalid dirname from file chooser dialog\n')

        self.update_sharelist()

    def open_cb(self, widget):
        model, selected = self.sharelist_view.get_selection().get_selected_rows()
        if len(selected) == 0:
            notification.notify('No file selected!', highpri=True)
            return
        row = self.sharelist[selected[0]]

        meta = row[self.COL_SHAREMETA]
        sharepath = row[self.COL_SHAREPATH]
        guiname = row[self.COL_GUINAME]
        shareid = meta['id']

        fullpath = filesharing.native_path(shareid, sharepath)
        if fullpath == None:
            warning('FileSharingGUI: Can not open shareid %d sharepath %s\n' %(shareid, sharepath))
            return

        notification.notify('Opening content %s' %(guiname))

        if not open_file(fullpath):
            notification.ok_dialog('Can not open file', 'Can not open file: %s\nUnknown format, or not supported.' %(fullpath))

    def remove_cb(self, widget):
        model, selected = self.sharelist_view.get_selection().get_selected_rows()
        if len(selected) == 0:
            notification.notify('No share selected!', highpri=True)
            return
        row = self.sharelist[selected[0]]

        meta = row[self.COL_SHAREMETA]
        shareid = meta['id']
        filesharing.remove_share(filesharing.get_share(shareid))

        self.update_sharelist()
        
    def edit_metadata_cb(self, widget):
        model, selected = self.sharelist_view.get_selection().get_selected_rows()
        if len(selected) == 0:
            notification.notify('No share selected!', highpri=True)
            return
        row = self.sharelist[selected[0]]

        meta = row[self.COL_SHAREMETA]
        sharepath = row[self.COL_SHAREPATH]
        guiname = row[self.COL_GUINAME]
        shareid = meta['id']

        Edit_Metadata_Dialog(main_gui.get_main_window(), shareid, guiname, sharepath, self)

    def update_sharelist(self):
        self.sharelist.clear()
        self.folders = {}

        for (shareid, share) in filesharing.shares.items():
            for (sharepath, ftype) in share.list_recursively().items():
                size = ''
                if ftype == FTYPE_FILE:
                    nativepath = share.native_path(sharepath)
                    size = format_bytes(filesize(nativepath))
                self.add_item(share.meta, shareid, sharepath, size, ftype == FTYPE_DIRECTORY)

    def add_item(self, meta, shareid, sharepath, fsize, directory):
        key = (shareid, sharepath)
        riter = self.folders.get(key)
        if riter != None:
            return riter

        if sharepath != '/' and meta.get('type') == SHARE_DIR:
            parent_path = dirname(sharepath)
            parent = self.add_item(meta, shareid, parent_path, '', True)
        else:
            parent = None

        guiname = basename(sharepath)

        if directory:
            filetype = 'folder'
            if guiname == '' and meta.get('description'):
                guiname = meta.get('description')
            guiname += '/'
        else:
            filetype = get_filetype(sharepath)

        guiname = cut_text(guiname, MAX_GUI_NAME)

        ft_icon = get_filetype_icon(filetype)
        riter = self.sharelist.append(parent, [meta, sharepath, directory, ft_icon, guiname, fsize])

        if directory:
            self.folders[key] = riter

        return riter
class File_Sharing_Browse(GUI_Page):
    """ File_Sharing_Browse includes GUI window for browsing user's
    file shares. """

    COL_SHAREMETA = 0
    COL_SHAREPATH = 1
    COL_USER = 2
    COL_TYPE = 3
    COL_ICON = 4
    COL_GUINAME = 5
    COL_NICK = 6
    COL_SIZE = 7
    COL_COLOR = 8 # internal attribute: depends on hop count
    COL_HOPS = 9

    # colors for different hopcounts: 0 = 1 = foreground color, 2 = yellow, 3 or more = red
    HOP_COLORS = [None, None, "yellow", "red"]

    def __init__(self, fs_gui, title):
        GUI_Page.__init__(self, title)
        self.fs_gui = fs_gui
        self.target = None
        self.is_community = False
        self.items = 0

        self.vbox = gtk.VBox()
        self.pack_start(self.vbox)

        self.folders = {}

        self.content_list = gtk.TreeStore(gobject.TYPE_PYOBJECT, str, gobject.TYPE_PYOBJECT, bool, gtk.gdk.Pixbuf, str, str, str, str, int)
        self.content_list.set_sort_column_id(self.COL_HOPS, gtk.SORT_ASCENDING)

        self.initialize_browse_list()
        self.initialize_action_list()

        self.title = gtk.Label("Content Browsing")
        self.vbox.pack_start(self.title, False, False)
        scrollwin = new_scrollarea()
        scrollwin.add_with_viewport(self.browse_list_view)
        self.vbox.pack_start(scrollwin, True, True)

        self.show_all()
        main_gui.add_page(self)

    def back_action(self):
        if self == self.fs_gui.fs_results:
            return False
        key = (self.target, self.is_community)
        self.fs_gui.browse_pages.pop(key)
        main_gui.remove_page(self)
        self.destroy()
        return True

    def initialize_browse_list(self):
        self.browse_list_view = gtk.TreeView(self.content_list)
        # self.browse_list_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        self.browse_list_view.set_headers_visible(False)

        column = gtk.TreeViewColumn('')
        self.browse_list_view.append_column(column)
        column.set_expand(True)
        cr_icon = gtk.CellRendererPixbuf()
        cr_guiname = gtk.CellRendererText()
        column.pack_start(cr_icon, False)
        column.pack_start(cr_guiname)
        column.add_attribute(cr_icon, 'pixbuf', self.COL_ICON)
        column.add_attribute(cr_guiname, 'text', self.COL_GUINAME)
        column.add_attribute(cr_guiname, 'foreground', self.COL_COLOR)

        column = gtk.TreeViewColumn('')
        self.browse_list_view.append_column(column)
        cr_nick = gtk.CellRendererText()
        column.pack_start(cr_nick)
        column.add_attribute(cr_nick, 'text', self.COL_NICK)

        column = gtk.TreeViewColumn('')
        self.browse_list_view.append_column(column)
        cr_size = gtk.CellRendererText()
        column.pack_start(cr_size)
        column.add_attribute(cr_size, 'text', self.COL_SIZE)

    def initialize_action_list(self):
        download_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-browse_content_icon.png"))
        open_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-publish_content_icon.png"))
        metadata_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-edit_metadata_icon.png"))
        search_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-search_content_icon.png"))

        action_buttons = [(download_icon, 'Download', self.download_cb),
                          (open_icon, 'Stream', self.stream_cb),
                          (metadata_icon, 'Show\nMetadata', self.show_metadata_cb),
                          (search_icon, 'Search\nFrom User', self.show_search_cb),
                          (search_icon, 'Refresh', self.refresh_cb)
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        self.pack_start(self.actions.get_widget(), False, True)

    def download_cb(self, widget):
        model, selected = self.browse_list_view.get_selection().get_selected_rows()
        if len(selected) == 0:
            notification.notify('No file selected!', highpri=True)
            return
        row = self.content_list[selected[0]]

        meta = row[self.COL_SHAREMETA]
        user = row[self.COL_USER]
        sharepath = row[self.COL_SHAREPATH]
        guiname = row[self.COL_GUINAME]
        directory = row[self.COL_TYPE]

        if directory:
            what = 'directory'
        else:
            what = 'file'

        ctx = (user, meta['id'], sharepath, directory, meta)
        Download_Dialog(main_gui.get_main_window(),
                        'Download a %s' % what,
                        'Downloading a %s from %s: %s' % (what, user.tag(), guiname),
                        self.download_dialog_cb, ctx)

    def download_dialog_cb(self, accept, open_content, ctx):
        if not accept:
            return

        (user, shareid, sharepath, directory, meta) = ctx

        name = basename(sharepath)
        if directory and name == '' and meta.get('description'):
            name = meta.get('description')

        destname = filesharing.get_download_path(name)

        if directory:
            ctx = (sharepath, destname, name)
            if not filesharing.query(user, self.download_results, ctx, shareid=shareid, sharepath=sharepath):
                notification.notify('Unable to list directory contents from %s' % user.tag(), True)
            return

        notification.notify('Trying to download from %s' % user.tag())
        ctx = (user, destname, sharepath, open_content)
        if not filesharing.get_files(user, basename(sharepath), [(shareid, sharepath, destname)], download_complete, ctx):
            notification.ok_dialog('File sharing',
                'Unable to download a file from %s: %s' % (user.tag(), name))

    def download_results(self, user, allresults, metadict, ctx):
        (rootpath, destpath, name) = ctx

        if allresults == None:
            notification.notify('Unable to list directory contents from %s' % user.tag(), True)
            return

        files = []
        totallen = 0
        for (shareid, sharepath, fsize, ftype) in allresults:
            # NOTE: The sharepath should begin with rootpath
            i = len(rootpath)
            while i < len(sharepath):
                if sharepath[i] != '/':
                    break
                i += 1
            destname = join(destpath, sharepath[i:])
            files.append((shareid, sharepath, destname))
            totallen += fsize
        if not filesharing.get_files(user, name + '/', files, None, totallen=totallen):
            notification.ok_dialog('File sharing',
                'Unable to download a directory from %s: %s' % (user.tag(), name))

    def show_metadata_cb(self, widget):
        model, selected = self.browse_list_view.get_selection().get_selected_rows()
        if len(selected) == 0:
            notification.notify('No file selected!', highpri=True)
            return
        row = self.content_list[selected[0]]

        meta = row[self.COL_SHAREMETA]
        user = row[self.COL_USER]
        sharepath = row[self.COL_SHAREPATH]
        guiname = row[self.COL_GUINAME]
        shareid = meta['id']

        ctx = (user, guiname)
        filesharing.get_metas(user, [(shareid, sharepath)], self.got_metadata_for_file, ctx)
        filesharing.progress_update('Getting metadata for content...')

    def got_metadata_for_file(self, metas, ctx):
        (user, guiname) = ctx

        filesharing.progress_update(None)

        if len(metas) == 0:
            msg = 'No metadata for file: %s' %(guiname)
            notification.ok_dialog('Filesharing', msg)
            return

        if len(metas) != 1:
            debug('FileSharing: Found too many metadatas for file\n')
            return

        (shareid, fname, meta) = metas[0]

        Show_Metadata_Dialog(main_gui.get_main_window(), guiname, meta)

    def show_browse_window(self, target, is_community, criteria=None, keywords=None):
        if is_community:
            target_name = target.get('name')
        else:
            target_name = target.get('nick')

        self.is_community = is_community
        self.target = target
        self.criteria = criteria
        self.keywords = keywords

        self.set_page_title(target_name, sub=True)
        main_gui.show_page(self)
        self.update_content_list()
    
    def show_search_cb(self, widget):
        model, selected = self.browse_list_view.get_selection().get_selected_rows()
        if len(selected) == 0:
            notification.notify('No file selected!', highpri=True)
            return
        row = self.content_list[selected[0]]

        user = row[self.COL_USER]
        self.fs_gui.fs_search.show_file_sharing_search(user, False)

    def stream_cb(self, widget):
        model, selected = self.browse_list_view.get_selection().get_selected_rows()
        if len(selected) == 0:
            notification.notify('No file selected!', highpri=True)
            return
        row = self.content_list[selected[0]]

        meta = row[self.COL_SHAREMETA]
        user = row[self.COL_USER]
        sharepath = row[self.COL_SHAREPATH]

        filesharing.stream(user, meta['id'], sharepath)
        notification.notify('Trying to stream from %s' % user.tag())

    def update_content_list(self):
        self.content_list.clear()
        self.folders = {}

        self.items = 0
        self.title.set_text('No content found')

        if not self.is_community:
            if self.target == community.get_myself():
                # do not fetch own shares
                return
            if not filesharing.query(self.target, self.query_results, criteria=self.criteria, keywords=self.keywords):
                notification.notify('Unable to query shares from %s' % self.target.tag(), True)

        else: # Community
            filesharing.query_community(self.target, self.query_results, criteria=self.criteria, keywords=self.keywords)

    def refresh_cb(self, widget):
        self.update_content_list()

    def query_results(self, user, allresults, metadict, ctx):
        if allresults == None:
            notification.notify('Unable to query shares from %s' % user.tag(), True)
            return

        for (shareid, sharepath, fsize, ftype) in allresults:
            meta = metadict[shareid]

            size = ''
            if ftype == FTYPE_FILE:
                size = format_bytes(fsize)
            self.add_item(meta, user, shareid, sharepath, size, ftype == FTYPE_DIRECTORY)
            self.items += 1

        self.title.set_text('Showing %d items' % self.items)

    def add_item(self, meta, user, shareid, sharepath, fsize, directory):
        key = (user, shareid, sharepath)
        riter = self.folders.get(key)
        if riter != None:
            return riter

        if sharepath != '/' and meta.get('type') == SHARE_DIR:
            parent_path = dirname(sharepath)
            parent = self.add_item(meta, user, shareid, parent_path, '', True)
        else:
            parent = None

        guiname = basename(sharepath)

        if directory:
            filetype = 'folder'
            if guiname == '' and meta.get('description'):
                guiname = meta.get('description')
            guiname += '/'
        else:
            filetype = get_filetype(sharepath)

        guiname = cut_text(guiname, MAX_GUI_NAME)

        ft_icon = get_filetype_icon(filetype)
        nick = user.get('nick')
        hops = user.get('hops')
        if hops == None:
            hops = 0
        if hops < 4:
            color = self.HOP_COLORS[hops]
        else:
            color = self.HOP_COLORS[3]
        riter = self.content_list.append(parent, [meta, sharepath, user, directory, ft_icon, guiname, nick, fsize, color, hops])

        if directory:
            self.folders[key] = riter

        return riter
class File_Sharing_Search(GUI_Page):
    """ File_Sharing_Search includes GUI window for searching user's
    files. The window is in two parts, first the search window is shown
    and after search is clicked the gui moves to the next window which shows
    the search results.

    Currently one limitation: multiple search windows can not be open
    at the same time. If a new window is opened the old one is replaced
    with the new one."""

    def __init__(self, fs_gui):
        GUI_Page.__init__(self, 'Search content')
        self.fs_gui = fs_gui
        self.target = None
        self.is_community = False

        self.vbox = gtk.VBox()
        self.pack_start(self.vbox)

        self.entries = {}
        self.initialize_search_window()
        self.initialize_action_list()
        
        self.title = gtk.Label('Search Content')
        self.vbox.pack_start(self.title, False, False)
        self.vbox.pack_start(gtk.HSeparator(), False, False)
        self.vbox.pack_start(self.search_fwindow, True, True)

        self.show_all()
        main_gui.add_page(self)

    def initialize_search_window(self):
        self.search_fwindow = new_scrollarea()
        self.search_vbox = gtk.VBox()
        self.search_fwindow.add_with_viewport(self.search_vbox)

        entrylist = [('keywords', 'Keywords:'), \
                     ('fname', 'Filename:'), \
                     ('title', 'Title:'), \
                     ('author', 'Author:'), \
                     ('description', 'Description:')]

        for (key, header) in entrylist:
            hbox = gtk.HBox()
            label = gtk.Label(header)
            label.set_size_request(130, -1)
            label.set_alignment(0, 0)
            hbox.pack_start(label, False, False)

            entry = gtk.Entry()
            self.entries[key] = entry
            entry.connect("activate", self.search_cb)
            hbox.pack_start(entry, True, True)
            self.search_vbox.pack_start(hbox, False, False)

    def initialize_action_list(self):
        search_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-search_content_icon.png"))
        remove_icon = gtk.gdk.pixbuf_new_from_file(join(get_dir(ICON_DIR), "64px-remove_content_icon.png"))

        action_buttons = [(search_icon, 'Search', self.search_cb),
                          (remove_icon, 'Clear', self.clear_cb)
                         ]

        self.actions = Action_List()

        for action in action_buttons:
            (icon, text, cb) = action
            self.actions.add_button(icon, text, cb)

        self.pack_start(self.actions.get_widget(), False, True)

    def clear_cb(self, widget):
        for (key, entry) in self.entries.items():
            entry.set_text('')
        self.entries['keywords'].grab_focus()

    def search_cb(self, widget):
        query = {}
        for (key, entry) in self.entries.items():
            query[key] = entry.get_text()

        keywords = None
        criteria = []

        text = self.entries['keywords'].get_text().strip()
        if text != '':
            keywords = split_keywords(text)
            if len(keywords) == 0:
                keywords = None

        for field in ['fname', 'title', 'author', 'description']:
            text = self.entries[field].get_text().strip()
            if text != '':
                criteria.append((field, text))
        if len(criteria) == 0:
            criteria = None

        self.fs_gui.fs_results.show_browse_window(self.target, self.is_community, criteria=criteria, keywords=keywords)

    def show_file_sharing_search(self, target, is_community):
        """ Opens the whole file sharing window."""

        if is_community:
            target_name = target.get('name')
        else:
            target_name = target.get('nick')

        self.is_community = is_community
        self.target = target

        self.title.set_text('Search content from: %s' % target_name)

        self.entries['keywords'].set_property("can-focus", True)

        self.set_page_title(target_name, sub=True)
        main_gui.show_page(self)
        self.entries['keywords'].grab_focus()