diff --git a/TODO b/TODO index 6445f07..1648b0e 100644 --- a/TODO +++ b/TODO @@ -15,3 +15,4 @@ bugs: * Direct Messages have no names, only screen names (may not be fixable without considerable tweaks to python-twitter) +* "ValueError: list.remove(x): x not in list" when trying to close a tab (error recurred after adding conversation support). Tabs can be reordered, then closed, as a workaround. diff --git a/apithreads.py b/apithreads.py index 976df83..4e8895a 100644 --- a/apithreads.py +++ b/apithreads.py @@ -159,21 +159,20 @@ class GetConversation(ApiThread): # Get the root tweet try: with self.api.lock: - last_tweet = self.api.GetStatus(self.single_tweet) + last_tweet = self.api.GetStatus(self.root_tweet_id) statuses.append(last_tweet) except (HTTPError, URLError): statuses = None last_tweet = None # get tweets in a loop, until there is no in_reply_to_status_id - if last_tweet is not None: - while last_tweet.in_reply_to_status_id is not None: - try: - with self.api.lock: - last_tweet = self.api.GetStatus(self.single_tweet) - statuses.append(last_tweet) - except (HTTPError, URLError): - last_tweet = None + while last_tweet and last_tweet.in_reply_to_status_id: + try: + with self.api.lock: + last_tweet = self.api.GetStatus(last_tweet.in_reply_to_status_id) + statuses.append(last_tweet) + except (HTTPError, URLError): + last_tweet = None # In case we've never seen some of these users, grab their profile images and cache them for status in statuses: diff --git a/avcache.py b/avcache.py index 9ca8031..18da168 100644 --- a/avcache.py +++ b/avcache.py @@ -61,5 +61,5 @@ def add_to_cache(user): with AvCache().lock: AvCache().map[user.screen_name] = image - except URLError: + except (URLError, ValueError): pass # Nothing needs be done, just catch & ignore diff --git a/mytwitter.py b/mytwitter.py index ccc7db1..4191d7e 100755 --- a/mytwitter.py +++ b/mytwitter.py @@ -68,7 +68,7 @@ class MyTwitter(): self.db['active_page'] = 0 if not self.db.has_key('open_tabs'): - self.db['open_tabs'] = [(self.username + '/Home', None), ('@' + self.username, None), (self.username + '/Direct Messages', None)] + self.db['open_tabs'] = [(self.username + '/Home', None, False), ('@' + self.username, None, False), (self.username + '/Direct Messages', None, False)] if self.refresh_time < 10: self.refresh_time = 10 @@ -115,8 +115,8 @@ class MyTwitter(): # Add the tabs from last session to the notebook page_num = self.db['active_page'] - for tab, single_tweet in self.db['open_tabs']: - self.add_to_notebook(tab, single_tweet) + for tab, single_tweet, conversation in self.db['open_tabs']: + self.add_to_notebook(tab, single_tweet, conversation) self.tweet_notebook.set_current_page(page_num) self.update_windows() @@ -257,9 +257,9 @@ class MyTwitter(): # Remove one of the views from the tweet notebook. # Called when the close button is clicked on one of the views # or Ctrl + W is pressed while the view is active - def remove_view(self, name, single_tweet): + def remove_view(self, name, single_tweet, conversation): ot = self.db['open_tabs'] - ot.remove((name,single_tweet)) + ot.remove((name,single_tweet,conversation)) self.db['open_tabs'] = ot for i in range(self.tweet_notebook.get_n_pages()): @@ -269,8 +269,8 @@ class MyTwitter(): return - def remove_view_callback(self, event, name, single_tweet): - self.remove_view(name, single_tweet) + def remove_view_callback(self, event, name, single_tweet, conversation): + self.remove_view(name, single_tweet, conversation) def add_to_notebook(self, name, single_tweet=None, conversation=False): @@ -289,15 +289,20 @@ class MyTwitter(): return # Add the pane to the persistent database of open panes - if (name, single_tweet) not in self.db['open_tabs']: + if (name, single_tweet, conversation) not in self.db['open_tabs']: ot = self.db['open_tabs'] - ot.append((name,single_tweet)) + ot.append((name,single_tweet,conversation)) self.db['open_tabs'] = ot is_user = False if re.match('user:', name): is_user = True - new_pane = TweetPane(name, num_entries=self.num_entries, single_tweet=single_tweet, is_user=is_user) + + entries=self.num_entries + if single_tweet and not conversation: + entries=1 + + new_pane = TweetPane(name, num_entries=entries, single_tweet=single_tweet, is_user=is_user, conversation=conversation) if is_user: apithreads.GetFollowing(api=self.api, pane=new_pane, user=name).start() @@ -305,7 +310,7 @@ class MyTwitter(): self.tweet_notebook.append_page_menu(new_pane, new_pane.get_tab_label(), gtk.Label(name)) self.tweet_notebook.set_tab_reorderable(new_pane, True) - new_pane.get_tab_label().connect('close-clicked', self.remove_view_callback, name, single_tweet) + new_pane.get_tab_label().connect('close-clicked', self.remove_view_callback, name, single_tweet, conversation) new_pane.connect('tweet-reply', self.on_reply) new_pane.connect('tweet-retweet', self.on_retweet) new_pane.connect('tweet-in-reply-to', self.on_reply_to) @@ -327,6 +332,9 @@ class MyTwitter(): else: self.update_single_window(new_pane) + # Switch to the new pane + self.tweet_notebook.set_current_page(-1) + def on_tab_change(self, event, page, page_num): last_page = self.db['active_page'] @@ -353,7 +361,7 @@ class MyTwitter(): for i in range(self.tweet_notebook.get_n_pages()): pane = self.tweet_notebook.get_nth_page(i) - tab_names.append((pane.get_list_name(), pane.get_single_tweet())) + tab_names.append((pane.get_list_name(), pane.get_single_tweet(), pane.get_conversation())) self.db['open_tabs'] = tab_names @@ -440,7 +448,7 @@ class MyTwitter(): def close_current_tab(self): current_pane = self.tweet_notebook.get_nth_page(self.tweet_notebook.get_current_page()) - self.remove_view(current_pane.get_list_name(), current_pane.get_single_tweet()) + self.remove_view(current_pane.get_list_name(), current_pane.get_single_tweet(), current_pane.get_conversation()) def on_account_changed(self, widget): diff --git a/twitterwidgets.py b/twitterwidgets.py index 0b05650..41dd1be 100644 --- a/twitterwidgets.py +++ b/twitterwidgets.py @@ -16,7 +16,7 @@ class TweetPane(gtk.ScrolledWindow): It also gets some data from its parent, including num_entries ''' - def __init__(self, list_name, num_entries=20, single_tweet=None, is_user=False): + def __init__(self, list_name, num_entries=20, single_tweet=None, is_user=False, conversation=False): gtk.ScrolledWindow.__init__(self) self.data_lock = RLock() @@ -25,11 +25,10 @@ class TweetPane(gtk.ScrolledWindow): self.list_name = list_name self.single_tweet = single_tweet + self.conversation = conversation self.num_entries = num_entries - if self.single_tweet is not None: - self.num_entries = 1 - + self.is_user = is_user self.following = False self.verified = False @@ -58,7 +57,7 @@ class TweetPane(gtk.ScrolledWindow): tweet_box.pack_start(self.message) for i in range(0, self.num_entries): - self.tweets.append(TweetBox()) + self.tweets.append(TweetBox(self.conversation)) tweet_box.pack_start(self.tweets[i], expand=False) self.tweets[i].connect('reply', self.on_tweet_reply) self.tweets[i].connect('retweet', self.on_retweet) @@ -98,7 +97,10 @@ class TweetPane(gtk.ScrolledWindow): # If this is our first load of this list, don't treat anything as new! if self.last_tweet_read is None: try: - self.last_tweet_read = statuses[0].id + ids = [status.id for status in statuses] + ids.sort() + ids.reverse() + self.last_tweet_read = ids[0] except IndexError: self.last_tweet_read = 0 @@ -158,6 +160,10 @@ class TweetPane(gtk.ScrolledWindow): return self.single_tweet + def get_conversation(self): + return self.conversation + + def updated_once(self): return self.updated_once @@ -243,7 +249,7 @@ class TweetBox(gtk.HBox): Also stores the data necessary for replying or retweeting (id, screen name) ''' - def __init__(self): + def __init__(self, conversation=False): gtk.HBox.__init__(self) self.screen_name = None @@ -251,6 +257,9 @@ class TweetBox(gtk.HBox): self.in_reply_to_id = None self.in_reply_to_screen_name = None + # Lets the tweetbox know if it is part of a conversation or not + self.conversation = conversation + self.init_widgets() @@ -308,7 +317,7 @@ class TweetBox(gtk.HBox): button_box.pack_start(self.reply_to_button, expand=False) self.reply_to_button.connect("clicked", self.on_in_reply_to_clicked) - self.conversation_button = gtk.Button("Conversation") + self.conversation_button = gtk.Button("(conversation)") self.conversation_button.set_relief(gtk.RELIEF_NONE) button_box.pack_start(self.conversation_button, expand=False) self.conversation_button.connect("clicked", self.on_conversation_clicked) @@ -366,7 +375,7 @@ class TweetBox(gtk.HBox): self.text.set_markup(new_text) # If this is in reply to something, set appropriate label - if self.in_reply_to_screen_name and self.in_reply_to_id: + if not self.conversation and self.in_reply_to_screen_name and self.in_reply_to_id: self.reply_to_button.set_label('in reply to ' + self.in_reply_to_screen_name) self.conversation_button.show() @@ -404,7 +413,7 @@ class TweetBox(gtk.HBox): def on_conversation_clicked(self, widget): - self.emit('in-reply-to', {'id': self.in_reply_to_id, 'name': self.in_reply_to_screen_name}) + self.emit('conversation', {'id': self.id, 'name': self.in_reply_to_screen_name}) def on_user_clicked(self, widget):