Esempio n. 1
0
class Main(BaseGui, gtk.Window):
    __gsignals__ = dict(mykeypress=(gobject.SIGNAL_RUN_LAST | gobject.SIGNAL_ACTION, None, (str,)))

    def __init__(self, controller, extend=False):
        BaseGui.__init__(self, controller)
        gtk.Window.__init__(self)
        
        self.extend = extend and extend_mode
        
        self.set_title('Turpial')
        self.set_size_request(280, 350)
        self.set_default_size(320, 480)
        self.current_width = 320
        self.current_height = 480
        self.set_icon(self.load_image('turpial.png', True))
        self.set_position(gtk.WIN_POS_CENTER)
        self.set_gravity(gtk.gdk.GRAVITY_STATIC)
        self.connect('delete-event', self.__close)
        self.connect('size-request', self.size_request)
        self.connect('configure-event', self.move_event)
        self.connect('key-press-event', self.__on_key_press)
        self.connect('focus-in-event', self.__on_focus)
        self.hnd_state = None
        
        self.mode = 0
        self.vbox = None
        self.contentbox = gtk.VBox(False)
        
        # Valores de config. por defecto
        self.showed = True
        self.win_state = 'windowed'
        self.minimize = 'on'
        self.workspace = 'single'
        self.link_color = '#ff6633'
        self.home_interval = -1
        self.replies_interval = -1
        self.directs_interval = -1
        self.me = None
        self.version = None
        
        self.home_timer = None
        self.replies_timer = None
        self.directs_timer = None
        
        self.sound = Sound(controller.no_sound)
        self.notify = Notification(controller.no_notif)
        self.indicator = Indicators()
        self.indicator.connect('main-clicked', self.__on_main_indicator_clicked)
        self.indicator.connect('indicator-clicked', self.__on_indicator_clicked)
        
        self.home = Home(self, self.workspace)
        self.profile = Profile(self)
        self.contenido = self.home
        self.updatebox = UpdateBox(self)
        self.uploadpic = UploadPicBox(self)
        self.replybox = ConversationBox(self)
        self.oauthwin = OAuthWindow(self)
        
        if self.extend:
            log.debug('Cargado modo GTK Extendido')
        else:
            log.debug('Cargado modo GTK Simple')
        
        self.dock = Dock(self, self.workspace)
        self.__create_trayicon()
        
    def __create_trayicon(self):
        if gtk.check_version(2, 10, 0) is not None:
            log.debug("Disabled Tray Icon. It needs PyGTK >= 2.10.0")
            return
        self.tray = gtk.StatusIcon()
        self.tray.set_from_pixbuf(self.load_image('turpial-tray.png', True))
        self.tray.set_tooltip('Turpial')
        self.tray.connect("activate", self.__on_trayicon_click)
        self.tray.connect("popup-menu", self.__show_tray_menu)
        
    def __on_trayicon_click(self, widget):
        if self.showed:
            self.showed = False
            self.hide()
        else:
            self.showed = True
            self.show()
            if self.workspace == 'wide':
                self.move(self.win_wide_pos[0], self.win_wide_pos[1])
            else:
                self.move(self.win_single_pos[0], self.win_single_pos[1])
    
    def __on_main_indicator_clicked(self, indicator):
        self.showed = True
        self.show()
        self.present()
        
    def __on_indicator_clicked(self, indicator, data):
        self.indicator.clean()
        self.__on_main_indicator_clicked(indicator)
    
    def __on_focus(self, widget, event):
        self.tray.set_from_pixbuf(self.load_image('turpial-tray.png', True))
    
    def __on_key_press(self, widget, event):
        keyname = gtk.gdk.keyval_name(event.keyval)
        if (event.state & gtk.gdk.CONTROL_MASK) and keyname.lower() == 'n':
            self.show_update_box()
            return True
        return False
    
    def __on_change_state(self, widget, event, data=None):
        if event.type != gtk.gdk.WINDOW_STATE:
            return False
        
        if event.new_window_state == gtk.gdk.WINDOW_STATE_ICONIFIED:
            self.win_state = 'minimized'
        elif event.new_window_state == gtk.gdk.WINDOW_STATE_MAXIMIZED:
            self.win_state = 'maximized'
        elif event.new_window_state == 0:
            self.win_state = 'windowed'
        
    def __show_tray_menu(self, widget, button, activate_time):
        menu = gtk.Menu()
        tweet = gtk.MenuItem(_('Tweet'))
        follow = gtk.MenuItem(_('Follow'))
        exit = gtk.MenuItem(_('Exit'))
        if self.mode == 2:
            menu.append(tweet)
            menu.append(follow)
            menu.append(gtk.SeparatorMenuItem())
        menu.append(exit)
        
        exit.connect('activate', self.main_quit)
        tweet.connect('activate', self.__show_update_box_from_menu)
        follow.connect('activate', self.__show_follow_box_from_menu)
            
        menu.show_all()
        menu.popup(None, None, None, button, activate_time)
        
    def __show_update_box_from_menu(self, widget):
        self.show_update_box()
        
    def __show_follow_box_from_menu(self, widget):
        self.show_follow_box()
        
    def __close(self, widget, event=None):
        if self.minimize == 'on':
            self.showed = False
            self.hide()
        else:
            self.quit(widget)
        return True
        
    def __save_config(self):
        if self.mode < 2: return
        
        wide_value = "%i, %i" % (self.wide_win_size[0], self.wide_win_size[1])
        single_value = "%i, %i" % (self.single_win_size[0], self.single_win_size[1])
        single_pos = "%i, %i" % (self.win_single_pos[0], self.win_single_pos[1])
        wide_pos = "%i, %i" % (self.win_wide_pos[0], self.win_wide_pos[1])
        visibility = 'show' if self.showed else 'hide'
        log.debug('Guardando configuración de la ventana')
        log.debug('--Single: %s' % single_value)
        log.debug('--Wide: %s' % wide_value)
        log.debug('--Single Position: %s' % single_pos)
        log.debug('--Wide Position: %s' % wide_pos)
        log.debug('--State: %s' % self.win_state)
        
        self.save_config({
            'Window': {
                'single-win-size': single_value,
                'wide-win-size': wide_value, 
                'window-single-position': single_pos,
                'window-wide-position': wide_pos,
                'window-state': self.win_state,
                'window-visibility': visibility,
            },
            'Columns': {
                'column1': self.home.timeline.get_combo_item(),
                'column2': self.home.replies.get_combo_item(),
                'column3': self.home.direct.get_combo_item(),
            },
        }, update=False)
        
    def _notify_new_tweets(self, column, tweets, last, count):
        if count <= 0:
            return
        
        tweet = None
        for twt in tweets.items:
            if twt.username != self.me:
                if not util.has_tweet(last, twt):
                    tweet = twt
                    break
        
        if not tweet:
            return
        
        if count == 1:
            tobject = column.single_unit
        else:
            tobject = column.plural_unit
        
        p = self.parse_tweet(tweet)
        icon = self.current_avatar_path(p.avatar)
        twt = util.unescape_text(p.text)
        text = "<b>@%s</b> %s" % (p.username, twt)
        
        self.notify.new_tweets(column.title, count, tobject, text, icon)
        self.indicator.add_update(column.title, count)
        
        if self.read_config_value('Notifications', 'sound') == 'on':
            if column.id == 'replies':
                self.sound.replies()
            elif column.id == 'directs':
                self.sound.directs()
            else:
                self.sound.tweets()
        
        if not self.get_property('is-active'):
            self.tray.set_from_pixbuf(self.load_image('turpial-tray-update.png', True))
                
    def load_image(self, path, pixbuf=False):
        img_path = os.path.realpath(os.path.join(os.path.dirname(__file__),
            '..', '..', 'data', 'pixmaps', path))
        pix = gtk.gdk.pixbuf_new_from_file(img_path)
        if pixbuf: return pix
        avatar = gtk.Image()
        avatar.set_from_pixbuf(pix)
        del pix
        return avatar
        
    def load_avatar(self, dir, path, image=False):
        img_path = os.path.join(dir, path)
        pix = gtk.gdk.pixbuf_new_from_file(img_path)
        if not image: return pix
        avatar = gtk.Image()
        avatar.set_from_pixbuf(pix)
        del pix
        return avatar
    
    def resize_avatar(self, pic):
        ext = pic[-3:].lower()
        fullname = os.path.join(self.imgdir, pic)
        
        orig = gtk.gdk.pixbuf_new_from_file(fullname)
        pw, ph = orig.get_width(), orig.get_height()
        
        if pw >= ph:
            ratio = float(ph) / pw
            fw = util.AVATAR_SIZE
            fh = int(fw * ratio)
        else:
            ratio = float(pw) / ph
            fh = util.AVATAR_SIZE
            fw = int(fh * ratio)
            
        dest = orig.scale_simple(fw, fh, gtk.gdk.INTERP_BILINEAR)
        dest.save(fullname, 'png')
        
        del orig
        del dest
        
    def request_conversation(self, twt_id, user):
        self.replybox.show(twt_id, user)
        BaseGui.request_conversation(self, twt_id, user)
        
    def get_user_avatar(self, user, pic_url):
        pix = self.request_user_avatar(user, pic_url)
        if pix:
            # Try to load user avatar from file. If fail (by corrupt data, etc)
            # then load default image
            try:
                return self.load_avatar(self.imgdir, pix)
            except:
                return self.load_image('unknown.png', pixbuf=True)
        else:
            return self.load_image('unknown.png', pixbuf=True)
    
    def get_gdk_color_from_base(self, key):
        base_color = self.update_color[key]
        r = int(base_color[1:3], 16)
        g = int(base_color[3:5], 16)
        b = int(base_color[5:7], 16)
        #return r, g, b
        return gtk.gdk.Color(r * 257, g * 257, b * 257)

    def main_quit(self, widget=None):
        self.__save_config()
        self.destroy()
        self.tray = None
        if widget:
            gtk.main_quit()
        self.request_signout()
        
    def main_loop(self):
        gtk.main()
        
    def show_login(self):

        self.mode = 1
        if self.vbox is not None: self.remove(self.vbox)
        
        self.vbox = LoginBox(self)
        
        self.add(self.vbox)
        self.show_all()
        
    def cancel_login(self, error):
        self.vbox.cancel_login(error)
        
    def show_oauth_pin_request(self, url):
        gobject.idle_add(self.oauthwin.open, url)
        
    def show_main(self, config, global_cfg, p):
        log.debug('Cargando ventana principal')
        self.mode = 2
        
        self.update_config(config, global_cfg, True)
        
        gtk.gdk.threads_enter()
        self.contentbox.add(self.contenido)
        
        self.statusbar = gtk.Statusbar()
        self.statusbar.push(0, _('Wait a few seconds while I load everything...'))
        if (self.vbox is not None): self.remove(self.vbox)
        
        self.vbox = gtk.VBox(False, 0)
        self.vbox.pack_start(self.contentbox, True, True, 0)
        self.vbox.pack_start(self.dock, False, False, 0)
        self.vbox.pack_start(self.statusbar, False, False, 0)
        
        self.profile.set_user_profile(p)
        self.me = p.username
        title = 'Turpial - %s (%s)' % (self.me, self.get_current_protocol())
        self.set_title(title)
        self.tray.set_tooltip(title)
        
        if config.read('General', 'profile-color') == 'on':
            self.link_color = p.profile_link_color
        
        self.add(self.vbox)
        self.show_all()
        
        '''
        if self.win_state == 'minimized':
            self.iconify()
        elif self.win_state == 'maximized':
            self.maximize()
        elif self.win_visibility == 'hide':
            self.hide()
        '''
        
        self.hnd_state = self.connect('window-state-event', self.__on_change_state)
        
        #if (self.win_pos[0] > 0 and self.win_pos[1] > 0):
        #    self.move(self.win_pos[0], self.win_pos[1])
        gtk.gdk.threads_leave()
        
        if config.read('Notifications', 'login') == 'on':
            self.notify.login(p)
            if config.read('Notifications', 'sound') == 'on':
                self.sound.login()
        
        gobject.timeout_add(6 * 60 * 1000, self.download_rates)
        gobject.timeout_add(12 * 60 * 1000, self.download_favorites)
        gobject.timeout_add(15 * 60 * 1000, self.download_friends)
        
    def set_lists(self, lists, viewed):
        self.columns_lists = lists
        self.columns_viewed = viewed
        self.home.set_viewed_columns(lists, viewed)
        
    def set_column_item(self, index, reset=False):
        self.home.set_combo_item(index, reset)
        
    def show_home(self, widget):
        self.contentbox.remove(self.contenido)
        self.contenido = self.home
        self.contentbox.add(self.contenido)
        
    def show_profile(self, widget):
        self.contentbox.remove(self.contenido)
        self.contenido = self.profile
        self.contentbox.add(self.contenido)
        
    def show_update_box(self, text='', id='', user=''):
        if self.updatebox.get_property('visible'): 
            self.updatebox.present()
            return
        self.updatebox.show(text, id, user)
        
    def show_follow_box(self):
        f = Follow(self)
        
    def show_uploadpic_box(self):
        if self.uploadpic.get_property('visible'): 
            self.uploadpic.present()
            return
        self.uploadpic.show()
        
    def show_preferences(self, widget, mode='user'):
        prefs = Preferences(self, mode)
        
    def start_updating_column1(self):
        self.home.timeline.start_update()
        
    def start_updating_column2(self):
        self.home.replies.start_update()
        
    def start_updating_column3(self):
        self.home.direct.start_update()
        
    def start_search(self):
        self.profile.search.start_update()
        
    def update_column1(self, tweets):
        gtk.gdk.threads_enter()
        
        last = self.home.timeline.statuslist.last
        count = self.home.timeline.update_tweets(tweets)
        column = self.request_viewed_columns()[0]
        show_notif = self.read_config_value('Notifications', 'home')
        
        log.debug(u'Actualizando %s' % column.title)
        if self.updating[0] and show_notif == 'on':
            self._notify_new_tweets(column, tweets, last, count)
            
        gtk.gdk.threads_leave()
        self.updating[0] = False
        
    def update_column2(self, tweets):
        gtk.gdk.threads_enter()
        
        last = self.home.replies.statuslist.last
        count = self.home.replies.update_tweets(tweets)
        column = self.request_viewed_columns()[1]
        show_notif = self.read_config_value('Notifications', 'replies')
        
        log.debug(u'Actualizando %s' % column.title)
        if self.updating[1] and show_notif == 'on':
            self._notify_new_tweets(column, tweets, last, count)
        
        gtk.gdk.threads_leave()
        self.updating[1] = False
        
    def update_column3(self, tweets):
        gtk.gdk.threads_enter()
        
        last = self.home.direct.statuslist.last
        count = self.home.direct.update_tweets(tweets)
        column = self.request_viewed_columns()[2]
        show_notif = self.read_config_value('Notifications', 'directs')
        
        log.debug(u'Actualizando %s' % column.title)
        if self.updating[2] and show_notif == 'on':
            self._notify_new_tweets(column, tweets, last, count)
            
        gtk.gdk.threads_leave()
        self.updating[2] = False
        
    def update_favorites(self, favs):
        log.debug(u'Actualizando favoritos')
        gtk.gdk.threads_enter()
        #self.home.timeline.update_tweets(tweets)
        #self.home.replies.update_tweets(replies)
        self.profile.favorites.update_tweets(favs)
        gtk.gdk.threads_leave()
        
    def update_user_profile(self, profile):
        log.debug(u'Actualizando perfil del usuario')
        gtk.gdk.threads_enter()
        self.profile.set_user_profile(profile)
        gtk.gdk.threads_leave()
        
    def update_follow(self, user, follow):
        self.notify.following(user, follow)
        
    def update_rate_limits(self, val):
        if val is None or val == []: return
        gtk.gdk.threads_enter()
        try:
            self.statusbar.push(0, util.get_rates(val))
        except TypeError:
            log.debug(u'Error imprimiendo el mensaje en la barra de estado')
        gtk.gdk.threads_leave()
        
    def update_search(self, val):
        log.debug(u'Mostrando resultados de la búsqueda')
        gtk.gdk.threads_enter()
        self.profile.search.update_tweets(val)
        gtk.gdk.threads_leave()
        
    def update_user_avatar(self, user, pic):
        self.home.timeline.update_user_pic(user, pic)
        self.home.replies.update_user_pic(user, pic)
        self.home.direct.update_user_pic(user, pic)
        self.profile.favorites.update_user_pic(user, pic)
        self.profile.user_form.update_user_pic(user, pic)
        self.profile.search.update_user_pic(user, pic)
        
    def update_in_reply_to(self, tweet):
        gtk.gdk.threads_enter()
        self.replybox.update([tweet])
        gtk.gdk.threads_leave()
        
    def update_conversation(self, tweets):
        gtk.gdk.threads_enter()
        self.replybox.update(tweets)
        gtk.gdk.threads_leave()
        
    def tweet_changed(self, timeline, replies, favs):
        log.debug(u'Tweet modificado')
        gtk.gdk.threads_enter()
        log.debug(u'--Actualizando el timeline')
        self.home.timeline.update_tweets(timeline)
        log.debug(u'--Actualizando las replies')
        self.home.replies.update_tweets(replies)
        log.debug(u'--Actualizando favoritos')
        self.profile.favorites.update_tweets(favs)
        gtk.gdk.threads_leave()
        
    def tweet_done(self, tweets):
        log.debug(u'Actualizando nuevo tweet')
        gtk.gdk.threads_enter()
        if tweets.type == 'status':
            if self.updatebox.get_property('visible'):
                self.updatebox.release()
                self.updatebox.done()
            if self.uploadpic.get_property('visible'): 
                self.uploadpic.release()
                self.uploadpic.done()
        else:
            if self.updatebox.get_property('visible'):
                self.updatebox.release(tweets.errmsg)
            if self.uploadpic.get_property('visible'):
                self.uploadpic.release()
        gtk.gdk.threads_leave()
        
        self.update_timeline(tweets)
        
    def set_mode(self):
        # Necesario para que se calculen bien los valores
        # Solo debe llamarse UNA vez
        self.show_all()
        
        cur_x, cur_y = self.get_position()
        cur_w, cur_h = self.get_size()
        
        if self.workspace == 'wide':
            size = self.wide_win_size
            self.resize(size[0], size[1])
            self.set_default_size(size[0], size[1])
            if self.win_wide_pos[0] == -1 and self.win_wide_pos[1] == -1:
                x = (size[0] - cur_w) / 2
                self.move(cur_x - x, cur_y)
            else:
                self.move(self.win_wide_pos[0], self.win_wide_pos[1])
        else:
            size = self.single_win_size
            self.resize(size[0], size[1])
            self.set_default_size(size[0], size[1])
            if self.win_single_pos[0] == -1 and self.win_single_pos[1] == -1:
                x = (cur_w - size[0]) / 2
                self.move(cur_x + x, cur_y)
            else:
                self.move(self.win_single_pos[0], self.win_single_pos[1])
        
        log.debug('Cambiando a modo %s %s' % (self.workspace, size))
        self.dock.change_mode(self.workspace)
        self.home.change_mode(self.workspace)
        self.home.update_wrap(cur_w, self.workspace)
        self.profile.change_mode(self.workspace)
        
    def update_config(self, config, global_cfg=None, thread=False):
        log.debug('Actualizando configuracion')
        self.minimize = config.read('General', 'minimize-on-close')
        home_interval = int(config.read('General', 'home-update-interval'))
        replies_interval = int(config.read('General', 'replies-update-interval'))
        directs_interval = int(config.read('General', 'directs-update-interval'))
        
        if thread: 
            self.version = global_cfg.read('App', 'version')
            self.imgdir = config.imgdir
            single_size = config.read('Window', 'single-win-size').split(',')
            wide_size = config.read('Window', 'wide-win-size').split(',')
            s_pos = config.read('Window', 'window-single-position').split(',')
            w_pos = config.read('Window', 'window-wide-position').split(',')
            self.win_state = config.read('Window', 'window-state')
            self.win_visibility = config.read('Window', 'window-visibility')
            self.single_win_size = (int(single_size[0]), int(single_size[1]))
            self.wide_win_size = (int(wide_size[0]), int(wide_size[1]))
            self.win_single_pos = (int(s_pos[0]), int(s_pos[1]))
            self.win_wide_pos = (int(w_pos[0]), int(w_pos[1]))
            gtk.gdk.threads_enter()
        
        if self.workspace <> config.read('General', 'workspace'):
            self.workspace = config.read('General', 'workspace')
        
        self.set_mode()
        
        if (self.home_interval != home_interval):
            if self.home_timer: gobject.source_remove(self.home_timer)
            self.home_interval = home_interval
            self.home_timer = gobject.timeout_add(self.home_interval * 60 * 1000, self.download_column1)
            log.debug('--Creado timer de Timeline cada %i min' % self.home_interval)
            
        if (self.replies_interval != replies_interval):
            if self.replies_timer: gobject.source_remove(self.replies_timer)
            self.replies_interval = replies_interval
            self.replies_timer = gobject.timeout_add(self.replies_interval * 60 * 1000, self.download_column2)
            log.debug('--Creado timer de Replies cada %i min' % self.replies_interval)
            
        if (self.directs_interval != directs_interval):
            if self.directs_timer: gobject.source_remove(self.directs_timer)
            self.directs_interval = directs_interval
            self.directs_timer = gobject.timeout_add(self.directs_interval * 60 * 1000, self.download_column3)
            log.debug('--Creado timer de Directs cada %i min' % self.directs_interval)
            
        if thread: 
            gtk.gdk.threads_leave()
        
    def size_request(self, widget, event, data=None):
        """Callback when the window changes its sizes. We use it to set the
        proper word-wrapping for the message column."""
        if self.mode < 2: return
        
        w, h = self.get_size()
        
        if self.workspace == 'wide':
            if (w, h) == self.wide_win_size: return
            self.wide_win_size = (w, h)
            self.win_wide_pos = self.get_position()
        else:
            if (w, h) == self.single_win_size: return
            self.single_win_size = (w, h)
            self.win_single_pos = self.get_position()
        
        self.contenido.update_wrap(w, self.workspace)
        return
    
    def move_event(self, widget, event):
        if self.workspace == 'wide':
            self.win_wide_pos = self.get_position()
        else:
            self.win_single_pos = self.get_position()
        
    def following_error(self, message, follow):
        self.notify.following_error(message, follow)