Skip to content
This repository has been archived by the owner on Oct 18, 2019. It is now read-only.

Commit

Permalink
adding Docstrings...finally
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Swanson committed Jul 8, 2010
1 parent 4c985a3 commit 4723bcf
Showing 1 changed file with 69 additions and 12 deletions.
81 changes: 69 additions & 12 deletions StackTracker.py
Expand Up @@ -14,6 +14,11 @@
import StringIO, gzip

class QLineEditWithPlaceholder(QtGui.QLineEdit):
"""
Custom Qt widget that is required since PyQt does not yet
support Qt4.7 -- which adds native placeholder text functionality to
QLineEdits
"""
def __init__(self, parent = None):
QtGui.QLineEdit.__init__(self, parent)
self.placeholder = None
Expand All @@ -23,6 +28,7 @@ def setPlaceholderText(self, text):
self.update()

def paintEvent(self, event):
"""Overload paintEvent to draw placeholder text under certain conditions"""
QtGui.QLineEdit.paintEvent(self, event)
if self.placeholder and not self.hasFocus() and not self.text():
painter = QtGui.QPainter(self)
Expand All @@ -32,6 +38,7 @@ def paintEvent(self, event):
painter.end()

class QuestionDisplayWidget(QtGui.QWidget):
"""Custom Qt Widget to display pretty representations of Question objects"""
def __init__(self, question, parent = None):
QtGui.QWidget.__init__(self, parent)

Expand Down Expand Up @@ -97,6 +104,7 @@ def launchUrl(self, event):
QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.question.url))

class Question():
"""Application specific representation of a StackExchange question"""
def __init__(self, question_id, site, title = None, created = None, \
last_queried = None, already_answered = None, \
answer_count = None, submitter = None):
Expand Down Expand Up @@ -165,6 +173,10 @@ def __eq__(self, other):
return ((self.site == other.site) and (self.id == other.id))

class QSpinBoxRadioButton(QtGui.QRadioButton):
"""
Custom Qt Widget that allows for a spinbox to be used in
conjunction with a radio button
"""
def __init__(self, prefix = '', suffix = '', parent = None):
QtGui.QRadioButton.__init__(self, parent)
self.prefix = QtGui.QLabel(prefix)
Expand Down Expand Up @@ -213,6 +225,12 @@ def setValue(self, value):
self.spinbox.setValue(value)

class SettingsDialog(QtGui.QDialog):
"""
Settings window that allows the user to customize the application
Currently supports auto-removing questions and changing the refresh
interval.
"""
def __init__(self, parent = None):
QtGui.QDialog.__init__(self, parent)
self.setFixedSize(QtCore.QSize(400,250))
Expand Down Expand Up @@ -267,6 +285,7 @@ def __init__(self, parent = None):
self.setLayout(self.layout)

def updateSettings(self, settings):
"""Restore saved settings from a dictionary"""
#todo throw this in a try block
self.auto_remove.setChecked(settings['auto_remove'])
if settings['on_time']:
Expand All @@ -278,6 +297,7 @@ def updateSettings(self, settings):
self.update_input.setValue(settings['on_time'])

def getSettings(self):
"""Returns a dictionary of currently selected settings"""
settings = {}
settings['auto_remove'] = self.auto_remove.isChecked()
settings['on_time'] = self.time_option.value() if self.time_option.isChecked() else False
Expand All @@ -287,6 +307,11 @@ def getSettings(self):
return settings

class StackTracker(QtGui.QDialog):
"""
The 'main' dialog window for the application. Displays
the list of tracked questions and has the input controls for
adding new questions.
"""

API_KEY = '?key=Jv8tIPTrRUOqRe-5lk4myw'
API_VER = '0.9'
Expand Down Expand Up @@ -379,24 +404,29 @@ def __init__(self, parent = None):
self.worker.start()

def applySettings(self):
"""Send new settings to worker thread"""
settings = self.settings_dialog.getSettings()
interval = settings['update_interval'] * 1000 #convert to milliseconds
self.worker.setInterval(interval)
self.worker.applySettings(settings)

def trayClicked(self, event):
"""Shortcut to show list of question, not supported in Mac OS X"""
if event == QtGui.QSystemTrayIcon.DoubleClick:
self.showWindow()

def showWindow(self):
"""Show the list of tracked questions"""
self.show()
self.showMaximized()
self.displayQuestions()

def showSettings(self):
"""Show the settings dialog"""
self.settings_dialog.show()

def showAbout(self):
"""Show About Page, as if anyone actually cares about who made this..."""
s = """
<h3>StackTracker</h3>
<p>A desktop notifier using the StackExchange API built with PyQt4</p>
Expand All @@ -406,27 +436,38 @@ def showAbout(self):
QtGui.QMessageBox(QtGui.QMessageBox.Information, "About", s).exec_()

def showError(self, text):
"""
Pop-up an error box with a message
params:
text => msg to display
"""
QtGui.QMessageBox(QtGui.QMessageBox.Critical, "Error!", text).exec_()

def exitFromTray(self):
"""Event handler for 'Exit' menu option"""
self.serializeQuestions()
self.serializeSettings()
self.parent.exit()
self.parent.exit()

def cleanUp(self, event):
"""Perform last-minute operations before exiting"""
self.serializeQuestions()
self.serializeSettings()

def serializeQuestions(self):
datetime_to_json = lambda obj: calendar.timegm(obj.utctimetuple()) if isinstance(obj, datetime) else None
"""Persist currently tracked questions in external JSON file"""
a = []
for q in self.tracking_list:
a.append(q.__dict__)


#handler to convert datetime objects into epoch timestamps
datetime_to_json = lambda obj: calendar.timegm(obj.utctimetuple()) if isinstance(obj, datetime) else None
with open('tracking.json', 'w') as fp:
json.dump({'questions':a}, fp, default = datetime_to_json, indent = 4)

def deserializeQuestions(self):
"""Restore saved tracked questions from external JSON file"""
try:
with open('tracking.json', 'r') as fp:
data = fp.read()
Expand All @@ -442,11 +483,13 @@ def deserializeQuestions(self):
self.tracking_list.append(rebuilt_question)

def serializeSettings(self):
"""Persist application settings in external JSON file"""
settings = self.settings_dialog.getSettings()
with open('settings.json', 'w') as fp:
json.dump(settings, fp, indent = 4)

def deserializeSettings(self):
"""Restore saved application settings from external JSON file"""
try:
with open('settings.json', 'r') as fp:
data = fp.read()
Expand All @@ -457,6 +500,7 @@ def deserializeSettings(self):
self.settings_dialog.updateSettings(json.loads(data))

def updateQuestion(self, question, most_recent, answer_count, new_answer, new_comment):
"""Update questions in the tracking list with data fetched from worker thread"""
tracked = None
for q in self.tracking_list:
if q == question:
Expand All @@ -480,17 +524,21 @@ def updateQuestion(self, question, most_recent, answer_count, new_answer, new_co
self.displayQuestions()

def popupClicked(self):
"""Open the question in user's default browser"""
if self.popupUrl:
QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.popupUrl))

def displayQuestions(self):
"""Render the currently tracked questions in the display list"""
#hack to fix random disappearing questions
self.display_list = QtGui.QListWidget(self)
self.display_list.resize(QtCore.QSize(350, 350))
self.display_list.setStyleSheet("QListWidget{show-decoration-selected: 0; background: black;}")
self.display_list.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
self.display_list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel)
self.display_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
self.display_list.clear()
#/end hack

n = 0
for question in self.tracking_list:
Expand All @@ -506,6 +554,14 @@ def displayQuestions(self):
self.display_list.show()

def removeQuestion(self, q, notify = False):
"""
Remove a question from the tracking list
params:
notify => indicate if the user should be alerted that the
question is no longer being tracked, useful for
auto-removing
"""
for question in self.tracking_list[:]:
if question == q:
self.tracking_list.remove(question)
Expand All @@ -515,6 +571,8 @@ def removeQuestion(self, q, notify = False):
self.displayQuestions()

def extractDetails(self, url):
"""Strip out the site domain from given URL"""
#todo: consider using StackAuth
regex = re.compile("""(?:http://)?(?:www\.)?
(?P<site>(?:[A-Za-z\.])*\.[A-Za-z]*)
/.*?
Expand All @@ -531,6 +589,10 @@ def extractDetails(self, url):
return id, site

def addQuestion(self):
"""
Add a new question to the list of tracked questions and render
it on the display list
"""
url = self.question_input.text()
self.question_input.clear()
details = self.extractDetails(str(url))
Expand All @@ -552,6 +614,7 @@ def notify(self, msg):
self.notifier.showMessage("StackTracker", msg, 20000)

class APIHelper(object):
"""Helper class for API related functionality"""

@staticmethod
def callAPI(url):
Expand Down Expand Up @@ -589,12 +652,6 @@ def applySettings(self, settings):
self.settings = settings

def fetch(self):
#todo: better handling of multiple new answers with regards
#notifications and timestamps

#todo: sort by newest answers and break out once we get to the old answers
#to speed up

for question in self.tracker.tracking_list[:]:
new_answers = False
new_comments = False
Expand Down Expand Up @@ -622,13 +679,13 @@ def fetch(self):
self.autoRemoveQuestions()

def autoRemoveQuestions(self):
if self.settings['auto_remove']: #if autoremove is enabled
if self.settings['on_inactivity']: #remove if time - last_queried > threshold
if self.settings['auto_remove']:
if self.settings['on_inactivity']:
threshold = timedelta(hours = self.settings['on_inactivity'])
for question in self.tracker.tracking_list[:]:
if datetime.utcnow() - question.last_queried > threshold:
self.emit(QtCore.SIGNAL('autoRemove'), question, True)
elif self.settings['on_time']: #remove if time - created > threshold
elif self.settings['on_time']:
threshold = timedelta(hours = self.settings['on_time'])
for question in self.tracker.tracking_list[:]:
if datetime.utcnow() - question.created > threshold:
Expand Down

0 comments on commit 4723bcf

Please sign in to comment.