Merge pull request #19 from alexbutum/master

Be more Pythonic and Python 3.x compatible
This commit is contained in:
Nemo 2013-12-13 08:27:16 -08:00
commit e48521b1ed
5 changed files with 230 additions and 219 deletions

View File

@ -1,10 +1,10 @@
#!/usr/bin/python #!/usr/bin/env python
import pygtk import pygtk
pygtk.require('2.0') pygtk.require('2.0')
import gtk import gtk
import requests
import webbrowser import webbrowser
import json import json
import argparse import argparse
@ -20,133 +20,149 @@ from appindicator_replacement import get_icon_filename
##This is to get --version to work ##This is to get --version to work
try: try:
import pkg_resources import pkg_resources
__version = pkg_resources.require("hackertray")[0].version
except ImportError, e: __version = pkg_resources.require("hackertray")[0].version
__version = "Can't read version number." except ImportError:
__version = "Can't read version number."
from hackernews import HackerNews from hackernews import HackerNews
class HackerNewsApp: class HackerNewsApp:
HN_URL_PREFIX="https://news.ycombinator.com/item?id=" HN_URL_PREFIX = "https://news.ycombinator.com/item?id="
def __init__(self):
#Load the database
home = expanduser("~")
with open(home+'/.hackertray.json', 'a+') as content_file:
content_file.seek(0)
content = content_file.read()
try:
self.db = set(json.loads(content))
except:
self.db = set()
# create an indicator applet def __init__(self):
self.ind = appindicator.Indicator ("Hacker Tray", "hacker-tray", appindicator.CATEGORY_APPLICATION_STATUS) #Load the database
self.ind.set_status (appindicator.STATUS_ACTIVE) home = expanduser("~")
self.ind.set_icon(get_icon_filename("hacker-tray.png")) with open(home + '/.hackertray.json', 'a+') as content_file:
content_file.seek(0)
content = content_file.read()
try:
self.db = set(json.loads(content))
except ValueError:
self.db = set()
# create a menu # create an indicator applet
self.menu = gtk.Menu() self.ind = appindicator.Indicator("Hacker Tray", "hacker-tray", appindicator.CATEGORY_APPLICATION_STATUS)
self.ind.set_status(appindicator.STATUS_ACTIVE)
self.ind.set_icon(get_icon_filename("hacker-tray.png"))
#The default state is false, and it toggles when you click on it # create a menu
self.commentState = False self.menu = gtk.Menu()
# create items for the menu - refresh, quit and a separator #The default state is false, and it toggles when you click on it
menuSeparator = gtk.SeparatorMenuItem() self.commentState = False
menuSeparator.show()
self.menu.append(menuSeparator)
btnComments = gtk.CheckMenuItem("Show Comments") # create items for the menu - refresh, quit and a separator
btnComments.show() menuSeparator = gtk.SeparatorMenuItem()
btnComments.connect("activate", self.toggleComments) menuSeparator.show()
self.menu.append(btnComments) self.menu.append(menuSeparator)
btnAbout = gtk.MenuItem("About") btnComments = gtk.CheckMenuItem("Show Comments")
btnAbout.show() btnComments.show()
btnAbout.connect("activate", self.showAbout) btnComments.connect("activate", self.toggleComments)
self.menu.append(btnAbout) self.menu.append(btnComments)
btnRefresh = gtk.MenuItem("Refresh") btnAbout = gtk.MenuItem("About")
btnRefresh.show() btnAbout.show()
btnRefresh.connect("activate", self.refresh, True) #the last parameter is for not running the timer btnAbout.connect("activate", self.showAbout)
self.menu.append(btnRefresh) self.menu.append(btnAbout)
btnQuit = gtk.MenuItem("Quit") btnRefresh = gtk.MenuItem("Refresh")
btnQuit.show() btnRefresh.show()
btnQuit.connect("activate", self.quit) #the last parameter is for not running the timer
self.menu.append(btnQuit) btnRefresh.connect("activate", self.refresh, True)
self.menu.append(btnRefresh)
self.menu.show() btnQuit = gtk.MenuItem("Quit")
btnQuit.show()
btnQuit.connect("activate", self.quit)
self.menu.append(btnQuit)
self.ind.set_menu(self.menu) self.menu.show()
self.refresh()
'''Whether comments page is opened or not''' self.ind.set_menu(self.menu)
def toggleComments(self, widget): self.refresh()
self.commentState = not self.commentState
'''Handle the about btn''' def toggleComments(self, widget):
def showAbout(self, widget): """Whether comments page is opened or not"""
webbrowser.open("https://github.com/captn3m0/hackertray/") self.commentState = not self.commentState
''' Handler for the quit button''' def showAbout(self, widget):
#ToDo: Handle keyboard interrupt properly """Handle the about btn"""
def quit(self, widget, data=None): webbrowser.open("https://github.com/captn3m0/hackertray/")
l=list(self.db)
home = expanduser("~")
#truncate the file
with open(home+'/.hackertray.json', 'w+') as file:
file.write(json.dumps(l))
gtk.main_quit()
def run(self): #ToDo: Handle keyboard interrupt properly
signal.signal(signal.SIGINT, self.quit) def quit(self, widget, data=None):
gtk.main() """ Handler for the quit button"""
return 0 l = list(self.db)
home = expanduser("~")
'''Opens the link in the web browser''' #truncate the file
def open(self, widget, event=None, data=None): with open(home + '/.hackertray.json', 'w+') as file:
#We disconnect and reconnect the event in case we have file.write(json.dumps(l))
#to set it to active and we don't want the signal to be processed
if(widget.get_active() == False):
widget.disconnect(widget.signal_id)
widget.set_active(True)
widget.signal_id = widget.connect('activate', self.open)
self.db.add(widget.item_id)
webbrowser.open(widget.url)
if(self.commentState):
webbrowser.open(self.HN_URL_PREFIX+widget.hn_id)
'''Adds an item to the menu''' gtk.main_quit()
def addItem(self, item):
if(item['points'] == 0 or item['points'] == None): #This is in the case of YC Job Postings, which we skip def run(self):
return signal.signal(signal.SIGINT, self.quit)
i = gtk.CheckMenuItem("("+str(item['points']).zfill(3)+"/"+str(item['comments_count']).zfill(3)+") "+item['title']) gtk.main()
i.set_active(item['id'] in self.db) return 0
i.url = item['url']
i.signal_id = i.connect('activate', self.open) def open(self, widget, event=None, data=None):
i.hn_id = item['id'] """Opens the link in the web browser"""
i.item_id = item['id'] #We disconnect and reconnect the event in case we have
self.menu.prepend(i) #to set it to active and we don't want the signal to be processed
i.show() if not widget.get_active():
widget.disconnect(widget.signal_id)
widget.set_active(True)
widget.signal_id = widget.connect('activate', self.open)
self.db.add(widget.item_id)
webbrowser.open(widget.url)
if self.commentState:
webbrowser.open(self.HN_URL_PREFIX + widget.hn_id)
def addItem(self, item):
"""Adds an item to the menu"""
#This is in the case of YC Job Postings, which we skip
if item['points'] == 0 or item['points'] is None:
return
i = gtk.CheckMenuItem(
"(" + str(item['points']).zfill(3) + "/" + str(item['comments_count']).zfill(3) + ") " + item['title'])
i.set_active(item['id'] in self.db)
i.url = item['url']
i.signal_id = i.connect('activate', self.open)
i.hn_id = item['id']
i.item_id = item['id']
self.menu.prepend(i)
i.show()
def refresh(self, widget=None, no_timer=False):
"""Refreshes the menu """
data = reversed(HackerNews.getHomePage()[0:20])
#Remove all the current stories
for i in self.menu.get_children():
if hasattr(i, 'url'):
self.menu.remove(i)
#Add back all the refreshed news
for i in data:
self.addItem(i)
#Call every 5 minutes
if not no_timer:
gtk.timeout_add(10 * 60 * 1000, self.refresh)
'''Refreshes the menu '''
def refresh(self, widget=None, no_timer=False):
data = reversed(HackerNews.getHomePage()[0:20]);
#Remove all the current stories
for i in self.menu.get_children():
if(hasattr(i,'url')):
self.menu.remove(i)
#Add back all the refreshed news
for i in data:
self.addItem(i)
#Call every 5 minutes
if no_timer==False:
gtk.timeout_add(10*60*1000, self.refresh)
def main(): def main():
parser = argparse.ArgumentParser(description='Hacker News in your System Tray') parser = argparse.ArgumentParser(description='Hacker News in your System Tray')
parser.add_argument('--version', action='version', version=__version) parser.add_argument('--version', action='version', version=__version)
parser.parse_args() parser.parse_args()
indicator = HackerNewsApp() indicator = HackerNewsApp()
indicator.run() indicator.run()

View File

@ -14,7 +14,6 @@ import gobject
# We also need os and sys # We also need os and sys
import os import os
import sys
from pkg_resources import resource_filename from pkg_resources import resource_filename
@ -22,100 +21,91 @@ from pkg_resources import resource_filename
CATEGORY_APPLICATION_STATUS = 0 CATEGORY_APPLICATION_STATUS = 0
# Status # Status
STATUS_ACTIVE = 0 STATUS_ACTIVE = 0
STATUS_ATTENTION = 1 STATUS_ATTENTION = 1
# Locations to search for the given icon # Locations to search for the given icon
def get_icon_filename(icon_name):
# Determine where the icon is def get_icon_filename(icon_name):
return os.path.abspath(resource_filename('hackertray.data', 'hacker-tray.png')) # Determine where the icon is
return os.path.abspath(resource_filename('hackertray.data', 'hacker-tray.png'))
# The main class # The main class
class Indicator: class Indicator:
# Constructor
def __init__(self, unknown, icon, category):
# Store the settings
self.inactive_icon = get_icon_filename(icon)
self.active_icon = "" # Blank until the user calls set_attention_icon
# Constructor # Create the status icon
self.icon = gtk.StatusIcon()
def __init__ (self,unknown,icon,category): # Initialize to the default icon
self.icon.set_from_file(self.inactive_icon)
# Store the settings # Set the rest of the vars
self.inactive_icon = get_icon_filename(icon) self.menu = None # We have no menu yet
self.active_icon = "" # Blank until the user calls set_attention_icon
# Create the status icon def set_menu(self, menu):
self.icon = gtk.StatusIcon() # Save a copy of the menu
self.menu = menu
# Initialize to the default icon # Now attach the icon's signal
self.icon.set_from_file(self.inactive_icon) # to the menu so that it becomes displayed
# whenever the user clicks it
self.icon.connect("activate", self.show_menu)
# Set the rest of the vars def set_status(self, status):
self.menu = None # We have no menu yet # Status defines whether the active or inactive
# icon should be displayed.
if status == STATUS_ACTIVE:
self.icon.set_from_file(self.inactive_icon)
else:
self.icon.set_from_file(self.active_icon)
def set_menu(self,menu): def set_label(self, label):
self.icon.set_title(label)
return
# Save a copy of the menu def set_icon(self, icon):
self.menu = menu # Set the new icon
self.icon.set_from_file(get_icon_filename(icon))
# Now attach the icon's signal def set_attention_icon(self, icon):
# to the menu so that it becomes displayed # Set the icon filename as the attention icon
# whenever the user clicks it self.active_icon = get_icon_filename(icon)
self.icon.connect("activate", self.show_menu)
def set_status(self, status): def show_menu(self, widget):
# Show the menu
self.menu.popup(None, None, None, 0, 0)
# Status defines whether the active or inactive # Get the location and size of the window
# icon should be displayed. mouse_rect = self.menu.get_window().get_frame_extents()
if status == STATUS_ACTIVE:
self.icon.set_from_file(self.inactive_icon)
else:
self.icon.set_from_file(self.active_icon)
def set_label(self, label): self.x = mouse_rect.x
self.icon.set_title(label) self.y = mouse_rect.y
return self.right = self.x + mouse_rect.width
self.bottom = self.y + mouse_rect.height
def set_icon(self, icon): # Set a timer to poll the menu
self.timer = gobject.timeout_add(100, self.check_mouse)
# Set the new icon def check_mouse(self):
self.icon.set_from_file(get_icon_filename(icon)) if not self.menu.get_window().is_visible():
return
def set_attention_icon(self, icon): # Now check the global mouse coords
root = self.menu.get_screen().get_root_window()
# Set the icon filename as the attention icon x, y, z = root.get_pointer()
self.active_icon = get_icon_filename(icon)
def show_menu(self, widget): if x < self.x or x > self.right or y < self.y or y > self.bottom:
self.hide_menu()
else:
return True
# Show the menu def hide_menu(self):
self.menu.popup(None,None,None,0,0) self.menu.popdown()
# Get the location and size of the window
mouse_rect = self.menu.get_window().get_frame_extents()
self.x = mouse_rect.x
self.y = mouse_rect.y
self.right = self.x + mouse_rect.width
self.bottom = self.y + mouse_rect.height
# Set a timer to poll the menu
self.timer = gobject.timeout_add(100, self.check_mouse)
def check_mouse(self):
if not self.menu.get_window().is_visible():
return
# Now check the global mouse coords
root = self.menu.get_screen().get_root_window()
x,y,z = root.get_pointer()
if x < self.x or x > self.right or y < self.y or y > self.bottom:
self.hide_menu()
else:
return True
def hide_menu(self):
self.menu.popdown()

View File

@ -2,16 +2,17 @@ import random
import requests import requests
urls = [ urls = [
'https://node-hnapi.herokuapp.com/' 'https://node-hnapi.herokuapp.com/'
]; ]
class HackerNews: class HackerNews:
@staticmethod @staticmethod
def getHomePage(): def getHomePage():
random.shuffle(urls) random.shuffle(urls)
for i in urls: for i in urls:
r = requests.get(i+"news") r = requests.get(i + "news")
try: try:
return r.json() return r.json()
except ValueError: except ValueError:
continue; continue

View File

@ -1,10 +1,12 @@
import unittest import unittest
from hackernews import HackerNews from hackernews import HackerNews
class HNTest(unittest.TestCase): class HNTest(unittest.TestCase):
def runTest(self): def runTest(self):
data = HackerNews.getHomePage() data = HackerNews.getHomePage()
self.assertTrue(len(data)>0) self.assertTrue(len(data) > 0)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -1,10 +1,12 @@
import sys
from setuptools import setup from setuptools import setup
from setuptools import find_packages from setuptools import find_packages
import sys
requirements = ['requests'] requirements = ['requests']
if sys.version_info < (2, 7): if sys.version_info < (2, 7):
requirements.append('argparse') requirements.append('argparse')
setup(name='hackertray', setup(name='hackertray',
version='1.9.5', version='1.9.5',
@ -17,12 +19,12 @@ setup(name='hackertray',
license='MIT', license='MIT',
packages=find_packages(), packages=find_packages(),
package_data={ package_data={
'hackertray.data':['hacker-tray.png'] 'hackertray.data': ['hacker-tray.png']
}, },
install_requires=[ install_requires=[
'requests>=2.0', 'requests>=2.0',
], ],
entry_points={ entry_points={
'console_scripts': ['hackertray = hackertray:main'], 'console_scripts': ['hackertray = hackertray:main'],
}, },
zip_safe=False) zip_safe=False)