Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial commit, alpha version

  • Loading branch information...
commit 5b9c1a09b0000bf009e8e6fa98f0411ffef6c387 0 parents
@swanson authored
Showing with 241 additions and 0 deletions.
  1. +16 −0 LICENSE
  2. +6 −0 README
  3. +219 −0 StackTracker.py
  4. BIN  st.png
16 LICENSE
@@ -0,0 +1,16 @@
+ StackTracker, a desktop notifier for StackOverflow.com
+ Copyright (C) 2010, Matt Swanson
+
+ 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 3 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 <http://www.gnu.org/licenses/>.
+
6 README
@@ -0,0 +1,6 @@
+StackTracker, a desktop notifier for StackOverflow.com built with PyQt4
+
+The application will display a task tray notification when someone has posted an answer or a comment to a question you are tracking on StackOverflow.com. Clicking the notification will open the question in your browser.
+
+License: GPL
+Contact: mdswanso@purdue.edu
219 StackTracker.py
@@ -0,0 +1,219 @@
+from PyQt4 import QtCore, QtGui, QtWebKit
+from datetime import datetime, date
+import json
+import urllib2
+from datetime import datetime, date
+import time
+import os
+import copy
+
+class QuestionItem(QtGui.QWidget):
+ def __init__(self, title, id):
+ QtGui.QListWidgetItem.__init__(self)
+
+ self.setGeometry(QtCore.QRect(0,0,325,50))
+
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ font.setBold(True)
+ font.setFamily("Arial")
+
+ self.label = QtGui.QLabel(self)
+ self.label.setWordWrap(True)
+ self.label.setGeometry(QtCore.QRect(15,0,253,50))
+ self.label.setFont(font)
+
+ self.stop_button = QtGui.QPushButton(self)
+ self.stop_button.setGeometry(QtCore.QRect(265,12,25,25))
+ self.stop_button.setFont(font)
+ self.stop_button.setText("X")
+ self.stop_button.clicked.connect(self.remove)
+
+ self.label.setStyleSheet("background: #ff9900; border: 1px solid black; border-radius: 10px; margin: 2px; color: white;")
+ self.stop_button.setStyleSheet("QPushButton{background: #cccccc; border: 1px solid black; border-radius: 5px; color: white;} QPushButton:hover{background: #c03434;}")
+
+ self.label.setText(title)
+ self.id = id
+
+ def remove(self):
+ self.emit(QtCore.SIGNAL('removeQuestion'), self.id)
+
+ def __repr__(self):
+ return "%s: %s" % (self.id, self.title)
+
+
+class Question():
+ def __init__(self, question_id):
+
+ self.id = question_id
+
+ api_base = 'http://api.stackoverflow.com/0.8/questions/'
+ base = 'http://stackoverflow.com/questions/'
+ self.url = base + self.id
+
+ json_url = api_base + self.id + '/?key=Jv8tIPTrRUOqRe-5lk4myw'
+ so_data = json.loads(urllib2.urlopen(json_url).read())
+ self.title = so_data['questions'][0]['title']
+ if len(self.title) > 50:
+ self.title = self.title[:48] + '...'
+
+ def __repr__(self):
+ return "%s: %s" % (self.id, self.title)
+
+class StackTracker(QtGui.QMainWindow):
+ def __init__(self, parent = None):
+ QtGui.QMainWindow.__init__(self)
+ self.setWindowTitle("StackTracker")
+
+ self.setGeometry(QtCore.QRect(0, 0, 325, 400))
+ self.setFixedSize(QtCore.QSize(325,400))
+ self.display_list = QtGui.QListWidget(self)
+ self.display_list.resize(QtCore.QSize(325, 350))
+ self.display_list.setStyleSheet("QListWidget{show-decoration-selected: 0; background: #818185;}")
+ self.display_list.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
+
+ self.question_input = QtGui.QLineEdit(self)
+ self.question_input.setValidator(QtGui.QIntValidator(self))
+ self.question_input.setGeometry(QtCore.QRect(15, 360, 220, 30))
+ self.question_input.setText("Enter Question ID...")
+ #not supported until Qt4.7
+ #self.question_input.setPlaceholderText("Enter SO question ID...")
+
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ font.setBold(True)
+ font.setFamily("Arial")
+
+ self.track_button = QtGui.QPushButton(self)
+ self.track_button.setGeometry(QtCore.QRect(245, 360, 65, 30))
+ self.track_button.setText("Track")
+ self.track_button.clicked.connect(self.addQuestion)
+ self.track_button.setFont(font)
+ self.track_button.setStyleSheet("QPushButton{background: #e2e2e2; border: 1px solid #888888; color: black;} QPushButton:hover{background: #d6d6d6;}")
+
+
+ self.tracking_list = []
+
+ self.test_questions = ['1711','2349378']
+ for id in self.test_questions:
+ q = Question(id)
+ self.tracking_list.append(q)
+
+ self.displayQuestions()
+
+ key = "Jv8tIPTrRUOqRe-5lk4myw"
+ self.answers_url = "http://api.stackoverflow.com/0.8/questions/2901879/answers?key=%s" % key
+ self.comments_url = "http://api.stackoverflow.com/0.8/questions/2901879/comments?key=%s" % key
+
+ self.origin_time = datetime.utcnow()
+
+ path = os.getcwd()
+ self.notifier = QtGui.QSystemTrayIcon(QtGui.QIcon(path+'/st.png'), self)
+ self.notifier.messageClicked.connect(self.popupClicked)
+ self.notifier.show()
+
+ self.worker = WorkerThread(self.tracking_list)
+ self.connect(self.worker, QtCore.SIGNAL('newAnswer'), self.newAnswer)
+ self.connect(self.worker, QtCore.SIGNAL('newComment'), self.newComment)
+ self.worker.start()
+
+ def newAnswer(self, question):
+ self.popupUrl = question.url
+ self.notify("New answer(s): %s" % question.title)
+
+ def newComment(self, question):
+ self.popupUrl = question.url
+ self.notify("New comment(s): %s" % question.title)
+
+ def popupClicked(self):
+ QtGui.QDesktopServices.openUrl(QtCore.QUrl(self.popupUrl))
+
+ def displayQuestions(self):
+ self.display_list.clear()
+ n = 0
+ for question in self.tracking_list:
+ item = QtGui.QListWidgetItem(self.display_list)
+ item.setSizeHint(QtCore.QSize(100, 50))
+ self.display_list.addItem(item)
+ qitem = QuestionItem(question.title, question.id)
+ self.connect(qitem, QtCore.SIGNAL('removeQuestion'), self.removeQuestion)
+ self.display_list.setItemWidget(item, qitem)
+ n = n + 1
+
+ def removeQuestion(self, id):
+ for question in self.tracking_list:
+ if question.id == id:
+ self.tracking_list.remove(question)
+ self.displayQuestions()
+ self.worker.updateTrackingList(self.tracking_list)
+
+ def addQuestion(self):
+ id = self.question_input.text()
+ try:
+ int(id)
+ except ValueError:
+ self.question_input.setText("Enter Question ID...")
+ return
+ if len(id) > 0:
+ q = Question(str(id))
+ self.tracking_list.append(q)
+ self.displayQuestions()
+ self.worker.updateTrackingList(self.tracking_list)
+ else:
+ return
+ self.question_input.setText("Enter Question ID...")
+
+ def notify(self, msg):
+ self.notifier.showMessage("StackTracker", msg, 20000)
+
+class WorkerThread(QtCore.QThread):
+ def __init__(self, tracking_list, parent = None):
+ QtCore.QThread.__init__(self, parent)
+ self.tracking_list = tracking_list
+ self.origin_time = datetime.utcnow()
+
+ def run(self):
+ self.fetch()
+ self.timer = QtCore.QTimer()
+ self.connect(self.timer, QtCore.SIGNAL('timeout()'), self.fetch, QtCore.Qt.DirectConnection)
+ self.timer.start(20000)
+ self.exec_()
+
+ def __del__(self):
+ self.wait()
+
+ def updateTrackingList(self, tracking_list):
+ self.tracking_list = tracking_list
+
+ def fetch(self):
+ answers_base = 'http://api.stackoverflow.com/0.8/questions/%s/answers?key=Jv8tIPTrRUOqRe-5lk4myw'
+ comments_base = 'http://api.stackoverflow.com/0.8/questions/%s/comments?key=Jv8tIPTrRUOqRe-5lk4myw'
+
+ tracked_questions = copy.deepcopy(self.tracking_list)
+ for question in tracked_questions:
+ answers_url = answers_base % question.id
+ so_data = json.loads(urllib2.urlopen(answers_url).read())
+
+ for answer in so_data['answers']:
+ updated = datetime.utcfromtimestamp(answer['creation_date'])
+ if updated > self.origin_time:
+ self.emit(QtCore.SIGNAL('newAnswer'), question)
+
+ comments_url = comments_base % question.id
+ so_data = json.loads(urllib2.urlopen(comments_url).read())
+
+ for comment in so_data['comments']:
+ updated = datetime.utcfromtimestamp(comment['creation_date'])
+ if updated > self.origin_time:
+ self.emit(QtCore.SIGNAL('newComment'), question)
+
+ self.origin_time = datetime.utcnow()
+
+if __name__ == "__main__":
+
+ import sys
+
+ app = QtGui.QApplication(sys.argv)
+ st = StackTracker(app)
+ st.show()
+ sys.exit(app.exec_())
BIN  st.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Please sign in to comment.
Something went wrong with that request. Please try again.