Permalink
Browse files

added floating commentary window

  • Loading branch information...
1 parent acb049a commit 59dd0d721ab4e1ac82559cdf5de1e25fe39397b7 @Vhati committed Sep 25, 2012
Showing with 330 additions and 53 deletions.
  1. +2 −0 README.md
  2. +58 −7 lib/killable_threading.py
  3. +84 −2 lib/tkwidgets.py
  4. +91 −16 lib/tsgui.py
  5. +8 −3 readme.txt
  6. +87 −25 tweetsubs.py
View
@@ -4,6 +4,8 @@ A frontend to launch VLC, follow someone on Twitter, and display any live @tweet
Geographically separated people can schedule a video to play simultaneously, follow the same account, compose tweets, and see each other's commentary. (Example: [MockTM Events](http://twitter.com/MockTM))
+Alternatively, if the video isn't playable in VLC, commentary can be sent to a window that can float over proprietary players.
+
Requirements
------------
View
@@ -336,22 +336,46 @@ def get_ignored_users(self):
return self._ignored_users[:]
-class SocketConnectThread(KillableThread):
- """Connects to a server and returns a socket.
- :param description: A descriptive string for logging.
- :param server_addr: Server's ip address string.
- :param server_port: Server's port number.
- :param retries: Maximum connection attempts.
- :param retry_interval: Delay in seconds between retries.
+class TweetsListener():
+ """A base class for TweetsThread listeners.
+ Listeners' methods will be called by the TweetsThread,
+ so subclasses must be thread-safe and not block.
"""
+ def __init__(self):
+ object.__init__(self)
+
+ def on_tweet(self, user_str, msg_str, tweet_json):
+ """Responds to incoming tweets.
+ This should be overridden.
+
+ See: TweetsThread.add_tweet_listener().
+
+ :param user_str: The cleaned "screen_name" of the user.
+ :param msg_str: The cleaned "text" of the tweet.
+ :param tweet_json: The raw json object.
+ """
+ pass
+
+
+class SocketConnectThread(KillableThread):
+ """Connects to a server and returns a socket."""
def __init__(self, description, server_addr, server_port, retries, retry_interval):
+ """Constructor.
+
+ :param description: A descriptive string for logging.
+ :param server_addr: Server's ip address string.
+ :param server_port: Server's port number.
+ :param retries: Maximum connection attempts.
+ :param retry_interval: Delay in seconds between retries.
+ """
KillableThread.__init__(self)
self._description = description
self._server_addr = server_addr
self._server_port = server_port
self._retries = retries
self._retry_interval = retry_interval
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self._socket.settimeout(5.0)
self._success_func = None
self._failure_func = None
@@ -368,6 +392,7 @@ def run(self):
break
except (socket.error) as err:
failed_connects += 1
+ logging.warning("%s (%s) failed to connect (%d/%d attempts): %s" % (self.__class__.__name__, self._description, failed_connects, self._retries, str(err)))
if (failed_connects >= self._retries):
logging.error("%s (%s) gave up repeated attempts to connect to %s:%s." % (self.__class__.__name__, self._description, self._server_addr, self._server_port))
self.keep_alive = False
@@ -383,3 +408,29 @@ def set_failure_callback(self, failure_func):
It will receive one arg, an empty dict.
"""
self._failure_func = failure_func
+
+
+class ProcessWatchThread(KillableThread):
+ """Polls a subprocess and executes a callback when the process dies."""
+ def __init__(self, description, proc, done_func):
+ """Constructor.
+
+ :param description: A descriptive string for logging.
+ :param proc: A subprocess object.
+ :param done_func: A callback to execute when the process dies.
+ """
+ KillableThread.__init__(self)
+ self._description = description
+ self.proc = proc
+ self.done_func = done_func
+
+ def run(self):
+ if (self.proc is None): return
+
+ while (self.keep_alive):
+ self.nap(0.5)
+ if (not self.keep_alive): break
+ if (self.proc.poll() is not None):
+ logging.debug("%s (%s) is calling its done func..." % (self.__class__.__name__, self._description))
+ if (self.done_func): self.done_func()
+ break
View
@@ -1,14 +1,95 @@
from datetime import datetime, timedelta
import logging
import re
+import ScrolledText
import threading
+import tkFont
import Tkinter as tk
import tkMessageBox
+class CaptionWindow(tk.Toplevel):
+ """A window with a vertically scrollable text area."""
+ def __init__(self, parent, *args, **kwargs):
+ """Constructor.
+
+ :param title: An optional window title.
+ :param delete_func: An optional callback for when this window closes.
+ """
+ self.custom_args = {"title":None, "delete_func":None}
+ for k in self.custom_args.keys():
+ if (k in kwargs):
+ self.custom_args[k] = kwargs[k]
+ del kwargs[k]
+ tk.Toplevel.__init__(self, parent, *args, **kwargs)
+
+ if (self.custom_args["title"]): self.wm_title(self.custom_args["title"])
+
+ self._size_font = tkFont.Font(family="Helvetica", size=11, weight=tkFont.BOLD)
+ self._caption_font = tkFont.Font(family="Helvetica", size=18, weight=tkFont.NORMAL)
+
+ _pane = tk.Frame(self)
+
+ size_frame = tk.Frame(_pane)
+ self._bigger_btn = tk.Button(size_frame, text="+", font=self._size_font, command=self._bigger_font)
+ self._bigger_btn.pack(fill="x",expand="no")
+ size_spacer = tk.Frame(size_frame)
+ size_spacer.pack(fill="y",expand="yes")
+ self._smaller_btn = tk.Button(size_frame, text=u"\u2013", font=self._size_font, command=self._smaller_font)
+ self._smaller_btn.pack(fill="x",expand="no")
+ size_frame.pack(fill="y",expand="no",side="left",padx=("0","5"))
+
+ self._text_area = ScrolledText.ScrolledText(_pane, relief="groove",width="35",height="4",wrap="word",font=self._caption_font)
+ self._text_area.pack(fill="both",expand="yes",side="right")
+
+ _pane.pack(fill="both",expand="yes",padx=("5","5"),pady=("5","5"))
+
+ self.wm_protocol("WM_DELETE_WINDOW", self._on_delete)
+
+ def get_text_area(self):
+ """Returns the ScrolledList widget."""
+ return self._get_text_area
+
+ def _bigger_font(self):
+ size = self._caption_font['size']
+ if (size >= 50): return
+ self._caption_font.configure(size=size+2)
+
+ def _smaller_font(self):
+ size = self._caption_font['size']
+ if (size <= 8): return
+ self._caption_font.configure(size=size-2)
+
+ def clear(self):
+ self._text_area.delete("1.0", tk.END)
+
+ def append_line(self, text):
+ """Appends to the text area.
+ If there is already text present, a newline will be added first.
+ """
+ if (len(self._text_area.get("1.0", tk.END)[:-1]) > 0):
+ self._text_area.insert(tk.END, "\n")
+ self._text_area.insert(tk.END, text)
+
+ def scroll_to_start(self):
+ self._text_area.see("1.0")
+
+ def scroll_to_end(self):
+ self._text_area.see(tk.END)
+
+ def get_text_area(self):
+ return self._text_area
+
+ def _on_delete(self):
+ if (self.custom_args["delete_func"] is not None):
+ self.custom_args["delete_func"]()
+ self.destroy()
+
+
class CheckboxList(tk.Frame):
+ """A scrollable list of checkboxes."""
def __init__(self, parent, *args, **kwargs):
- """Constructs a scrollable list of checkboxes.
+ """Constructor.
:param namelist: A dict of names and (0 or 1) values.
:param metacols: The number of columns of options.
@@ -266,8 +347,9 @@ def apply(self):
class CheckboxListDialog(CenteredDialog):
+ """A popup window containing a list of checkboxes."""
def __init__(self, parent, *args, **kwargs):
- """Constructs a popup window containing a list of checkboxes.
+ """Constructor.
:param title: Window title (passed to superclass).
:param namelist: A dict of names and (0 or 1) values.
Oops, something went wrong.

0 comments on commit 59dd0d7

Please sign in to comment.