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)