# -*- coding: utf-8 -*- ############################################################ # Copyright (c) 2013, 2014 Manuel Eduardo Cortéz Vallejo # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # ############################################################ import wx import gui.dialogs import twitter import webbrowser import config import sound import url_shortener import logging as original_logger import output import platform import datetime import menus from twitter import prettydate from multiplatform_widgets import widgets from mysc import event from mysc.thread_utils import call_threaded from twython import TwythonError log = original_logger.getLogger("buffers.base") class basePanel(wx.Panel): def bind_events(self): self.Bind(event.MyEVT_OBJECT, self.update) self.Bind(event.MyEVT_DELETED, self.Remove) self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.showMenu, self.list.list) self.Bind(wx.EVT_LIST_KEY_DOWN, self.showMenuByKey, self.list.list) self.list.list.Bind(wx.EVT_CHAR_HOOK, self.interact) if self.system == "Windows": self.list.list.Bind(wx.EVT_LIST_ITEM_FOCUSED, self.onFocus) else: self.list.list.Bind(wx.EVT_LISTBOX, self.onFocus) def get_message(self, dialog=False): if dialog == False: return " ".join(self.compose_function(self.db.settings[self.name_buffer][self.list.get_selected()], self.db)) else: list = self.compose_function(self.db.settings[self.name_buffer][self.list.get_selected()], self.db) return " ".join(list[1:-2]) def create_list(self): self.list = widgets.list(self, _(u"User"), _(u"Text"), _(u"Date"), _(u"Client"), style=wx.LC_REPORT|wx.LC_SINGLE_SEL|wx.LC_VRULES) if self.system == "Windows": self.list.set_windows_size(0, 30) self.list.set_windows_size(1, 160) self.list.set_windows_size(2, 55) self.list.set_windows_size(3, 42) self.list.set_size() def __init__(self, parent, window, name_buffer, function=None, argumento=None, sound="", timeline=False): if timeline == False: self.type = "buffer" elif timeline == True: self.type = "timeline" self.db = window.db self.twitter = window.twitter self.name_buffer = name_buffer self.function = function self.argumento = argumento self.sound = sound self.parent = window self.compose_function = twitter.compose.compose_tweet self.system = platform.system() wx.Panel.__init__(self, parent) self.sizer = wx.BoxSizer(wx.VERTICAL) self.create_list() self.btn = wx.Button(self, -1, _(u"Tweet")) self.btn.Bind(wx.EVT_BUTTON, self.post_status) self.retweetBtn = wx.Button(self, -1, _(u"Retweet")) self.retweetBtn.Bind(wx.EVT_BUTTON, self.onRetweet) self.responseBtn = wx.Button(self, -1, _(u"Reply")) self.responseBtn.Bind(wx.EVT_BUTTON, self.onResponse) self.dmBtn = wx.Button(self, -1, _(u"Direct message")) self.dmBtn.Bind(wx.EVT_BUTTON, self.onDm) btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer.Add(self.btn, 0, wx.ALL, 5) btnSizer.Add(self.retweetBtn, 0, wx.ALL, 5) btnSizer.Add(self.responseBtn, 0, wx.ALL, 5) btnSizer.Add(self.dmBtn, 0, wx.ALL, 5) self.sizer.Add(btnSizer, 0, wx.ALL, 5) self.sizer.Add(self.list.list, 0, wx.ALL, 5) self.bind_events() self.SetSizer(self.sizer) def remove_buffer(self): if self.type == "timeline": dlg = wx.MessageDialog(self, _(u"Do you really want to delete this timeline?"), _(u"Attention"), style=wx.ICON_QUESTION|wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: names = config.main["other_buffers"]["timelines"] user = self.name_buffer log.info(u"Deleting %s's timeline" % user) if user in names: names.remove(user) self.db.settings.pop(user) pos = self.db.settings["buffers"].index(user) self.db.settings["buffers"].remove(user) return pos elif self.type == "buffer": output.speak(_(u"This buffer is not a timeline; it can't be deleted.")) return None def remove_invalid_buffer(self): if self.type == "timeline": names = config.main["other_buffers"]["timelines"] user = self.name_buffer log.info(u"Deleting %s's timeline" % user) if user in names: names.remove(user) self.db.settings.pop(user) pos = self.db.settings["buffers"].index(user) self.db.settings["buffers"].remove(user) return pos def Remove(self, ev): # try: self.list.remove_item(ev.GetItem()) # except: # log.error(u"Cannot delete the %s item from list " % str(ev.GetItem())) def destroy_status(self, ev): index = self.list.get_selected() try: self.twitter.twitter.destroy_status(id=self.db.settings[self.name_buffer][index]["id"]) self.db.settings[self.name_buffer].pop(index) self.list.remove_item(index) if index > 0: self.list.select_item(index-1) except: sound.player.play("error.ogg") def onFocus(self, ev): if self.db.settings[self.name_buffer][self.list.get_selected()].has_key("retweeted_status"): tweet = self.db.settings[self.name_buffer][self.list.get_selected()]["retweeted_status"] else: tweet = self.db.settings[self.name_buffer][self.list.get_selected()] if config.main["general"]["relative_times"] == True: # On windows we need only put the new date on the column, but under linux and mac it isn't possible. if self.system == "Windows": original_date = datetime.datetime.strptime(tweet["created_at"], "%a %b %d %H:%M:%S +0000 %Y") date = original_date-datetime.timedelta(seconds=-self.db.settings["utc_offset"]) ts = prettydate(original_date) self.list.list.SetStringItem(self.list.get_selected(), 2, ts) else: self.list.list.SetString(self.list.get_selected(), " ".join(self.compose_function(self.db.settings[self.name_buffer][self.list.get_selected()], self.db))) if twitter.utils.is_audio(tweet): sound.player.play("audio.ogg", False) def start_streams(self): if self.name_buffer == "sent": num = twitter.starting.start_sent(self.db, self.twitter, self.name_buffer, self.function, param=self.argumento) else: # try: if self.argumento != None: num = twitter.starting.start_stream(self.db, self.twitter, self.name_buffer, self.function, param=self.argumento) else: num = twitter.starting.start_stream(self.db, self.twitter, self.name_buffer, self.function) # except TwythonError: # raise TwythonError # self.parent.delete_invalid_timeline() if self.sound != "" and num > 0 and self.name_buffer != "home_timeline" and self.name_buffer != "sent": sound.player.play(self.sound) return num def get_more_items(self): if config.main["general"]["reverse_timelines"] == False: last_id = self.db.settings[self.name_buffer][0]["id"] else: last_id = self.db.settings[self.name_buffer][-1]["id"] try: items = twitter.starting.get_more_items(self.function, self.twitter, count=config.main["general"]["max_tweets_per_call"], max_id=last_id, screen_name=self.argumento) except TwythonError as e: output.speak(e.message) for i in items: if config.main["general"]["reverse_timelines"] == False: self.db.settings[self.name_buffer].insert(0, i) else: self.db.settings[self.name_buffer].append(i) if config.main["general"]["reverse_timelines"] == False: for i in items: tweet = self.compose_function(i, self.db) self.list.insert_item(True, *tweet) else: for i in items: tweet = self.compose_function(i, self.db) self.list.insert_item(False, *tweet) output.speak(_(u"%s items retrieved") % (len(items))) def put_items(self, num): if self.list.get_count() == 0: for i in self.db.settings[self.name_buffer]: tweet = self.compose_function(i, self.db) self.list.insert_item(False, *tweet) self.set_list_position() elif self.list.get_count() > 0: if config.main["general"]["reverse_timelines"] == False: for i in self.db.settings[self.name_buffer][:num]: tweet = self.compose_function(i, self.db) self.list.insert_item(False, *tweet) else: for i in self.db.settings[self.name_buffer][0:num]: tweet = self.compose_function(i, self.db) self.list.insert_item(True, *tweet) def onDm(self, ev): if self.name_buffer == "sent": return if self.name_buffer == "direct_messages": self.onResponse(ev) else: user = self.db.settings[self.name_buffer][self.list.get_selected()]["user"]["screen_name"] dlg = gui.dialogs.message.dm(_("Direct message to %s") % (user,), _(u"New direct message"), "", self) if dlg.ShowModal() == wx.ID_OK: call_threaded(self.twitter.api_call, call_name="send_direct_message", _sound="dm_sent.ogg", text=dlg.text.GetValue(), screen_name=dlg.cb.GetValue()) # dlg.Destroy() if ev != None: self.list.list.SetFocus() def post_status(self, ev=None): text = gui.dialogs.message.tweet(_(u"Write the tweet here"), _(u"Tweet"), "", self) if text.ShowModal() == wx.ID_OK: if text.image == None: call_threaded(self.twitter.api_call, call_name="update_status", _sound="tweet_send.ogg", status=text.text.GetValue()) else: call_threaded(self.twitter.api_call, call_name="update_status_with_media", _sound="tweet_send.ogg", status=text.text.GetValue(), media=text.file) # text.Destroy() if ev != None: self.list.list.SetFocus() def onRetweet(self, ev): if self.name_buffer != "direct_messages": id=self.db.settings[self.name_buffer][self.list.get_selected()]["id"] ask = wx.MessageDialog(self.parent, _(u"Would you like to add a comment to this tweet?"), _("Retweet"), wx.YES_NO|wx.CANCEL|wx.ICON_QUESTION) response = ask.ShowModal() if response == wx.ID_YES: dlg = gui.dialogs.message.retweet(_(u"Add your comment to the tweet"), _(u"Retweet"), u"“@%s: %s ”" % (self.db.settings[self.name_buffer][self.list.get_selected()]["user"]["screen_name"], self.db.settings[self.name_buffer][self.list.get_selected()]["text"]), self) if dlg.ShowModal() == wx.ID_OK: if dlg.image == None: call_threaded(self.twitter.api_call, call_name="update_status", _sound="retweet_send.ogg", status=dlg.text.GetValue(), in_reply_to_status_id=dlg.in_reply_to) else: call_threaded(self.twitter.call_api, call_name="update_status_with_media", _sound="retweet_send.ogg", status=dlg.text.GetValue(), in_reply_to_status_id=text.in_reply_to, media=dlg.file) # dlg.Destroy() if ev != None: self.list.list.SetFocus() elif response == wx.ID_NO: call_threaded(self.twitter.api_call, call_name="retweet", _sound="retweet_send.ogg", id=id) if ev != None: self.list.list.SetFocus() ask.Destroy() def onResponse(self, ev): if self.name_buffer == "sent": return dlg = gui.dialogs.message.reply(_(u"Reply to %s") % (self.db.settings[self.name_buffer][self.list.get_selected()]["user"]["screen_name"]), _(u"Reply"), u"@%s " % (self.db.settings[self.name_buffer][self.list.get_selected()]["user"]["screen_name"]), self) if dlg.ShowModal() == wx.ID_OK: if dlg.image == None: call_threaded(self.twitter.api_call, call_name="update_status", _sound="reply_send.ogg", in_reply_to_status_id=dlg.in_reply_to, status=dlg.text.GetValue()) else: call_threaded(self.twitter.api_call, call_name="update_status_with_media", _sound="reply_send.ogg", in_reply_to_status_id=dlg.in_reply_to, status=dlg.text.GetValue(), media=dlg.file) # dlg.Destroy() if ev != None: self.list.list.SetFocus() def update(self, ev): data = ev.GetItem() announce = ev.GetAnnounce() if config.main["general"]["reverse_timelines"] == False: self.db.settings[self.name_buffer].append(data) else: self.db.settings[self.name_buffer].insert(0, data) tweet = self.compose_function(data, self.db) self.list.insert_item(config.main["general"]["reverse_timelines"], *tweet) if self.name_buffer not in config.main["other_buffers"]["muted_buffers"]: if self.sound != "": sound.player.play(self.sound) if announce != "": output.speak(announce) if self.name_buffer in config.main["other_buffers"]["autoread_buffers"]: output.speak(" ".join(tweet[:2])) def interact(self, ev): try: if self.db.settings[self.name_buffer][self.list.get_selected()].has_key("retweeted_status"): tweet = self.db.settings[self.name_buffer][self.list.get_selected()]["retweeted_status"] else: tweet = self.db.settings[self.name_buffer][self.list.get_selected()] urls = twitter.utils.find_urls_in_text(tweet["text"]) except: urls = [] if type(ev) is str: event = ev else: if ev.GetKeyCode() == wx.WXK_RETURN and ev.ControlDown(): event = "audio" elif ev.GetKeyCode() == wx.WXK_RETURN: event = "url" elif ev.GetKeyCode() == wx.WXK_F5: event = "volume_down" elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up" elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list" elif ev.GetKeyCode() == wx.WXK_DELETE: event = "delete_item" else: ev.Skip() return if event == "audio" and len(urls) > 0: if len(urls) == 1: self.streamer(urls[0]) elif len(urls) > 1: urlList = gui.dialogs.urlList.urlList(urls) if urlList.ShowModal() == wx.ID_OK: self.streamer(urls[urlList.lista.GetSelection()]) elif event == "url": if len(urls) == 0: return elif len(urls) == 1: output.speak(_(u"Opening URL..."), True) webbrowser.open(urls[0]) elif len(urls) > 1: urlList = gui.dialogs.urlList.urlList(urls) if urlList.ShowModal() == wx.ID_OK: webbrowser.open_new_tab(urls[urlList.lista.GetSelection()]) elif event == "volume_down": if config.main["sound"]["volume"] > 0.05: config.main["sound"]["volume"] = config.main["sound"]["volume"]-0.05 sound.player.play("volume_changed.ogg", False) if hasattr(self.parent, "audioStream"): self.parent.audioStream.stream.volume = config.main["sound"]["volume"] elif event == "volume_up": if config.main["sound"]["volume"] < 0.95: config.main["sound"]["volume"] = config.main["sound"]["volume"]+0.05 sound.player.play("volume_changed.ogg", False) if hasattr(self.parent, "audioStream"): self.parent.audioStream.stream.volume = config.main["sound"]["volume"] elif event == "clear_list" and self.list.get_count() > 0: dlg = wx.MessageDialog(self, _(u"Do you really want to empty this buffer? It's tweets will be removed from the list but not from Twitter"), _(u"Empty buffer"), wx.ICON_QUESTION|wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: self.db.settings[self.name_buffer] = [] self.list.clear() elif event == "delete_item": dlg = wx.MessageDialog(self, _(u"Do you really want to delete this message?"), _(u"Delete"), wx.ICON_QUESTION|wx.YES_NO) if dlg.ShowModal() == wx.ID_YES: self.destroy_status(wx.EVT_MENU) else: return try: ev.Skip() except: pass def streamer(self, url): if hasattr(self.parent, "audioStream"): if self.parent.audioStream.stream.is_active() == 0: output.speak(_(u"Playing...")) self.parent.audioStream = sound.urlStream(url) try: self.parent.audioStream.prepare() self.parent.audioStream.play() except: del self.parent.audioStream output.speak(_(u"Unable to play audio.")) else: output.speak(_(u"Audio stopped.")) self.parent.audioStream.stream.stop() else: output.speak(_(u"Playing...")) self.parent.audioStream = sound.urlStream(url) try: self.parent.audioStream.prepare() self.parent.audioStream.play() except: output.speak(_(u"Unable to play audio.")) del self.parent.audioStream def set_list_position(self): if config.main["general"]["reverse_timelines"] == False: self.list.select_item(len(self.db.settings[self.name_buffer])-1) else: self.list.select_item(0) def showMenu(self, ev): if self.list.get_count() == 0: return if self.name_buffer == "sent": self.PopupMenu(menus.sentPanelMenu(self), ev.GetPosition()) else: self.PopupMenu(menus.basePanelMenu(self), ev.GetPosition()) def showMenuByKey(self, ev): if self.list.get_count() == 0: return if ev.GetKeyCode() == wx.WXK_WINDOWS_MENU: if self.name_buffer == "sent": self.PopupMenu(menus.sentPanelMenu(self), self.list.list.GetPosition()) else: self.PopupMenu(menus.basePanelMenu(self), self.list.list.GetPosition())