Chrosson

TSR Messages Application v0.3

Jul 22nd, 2012
118
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 11.76 KB | None | 0 0
  1. # coding:UTF-8
  2.  
  3. # Author: Chrosson (0.1 onwards)
  4. # 0.1 - Initial Release
  5. # 0.2 - Added compatibility for building on OSX with py2app
  6. # 0.3 - Better unknown charset handling with UnicodeDammit
  7.  
  8. # This program is free software. It comes without any warranty, to
  9. # the extent permitted by applicable law. You can redistribute it
  10. # and/or modify it under the terms of the Do What The Fuck You Want
  11. # To Public License, Version 2, as published by Sam Hocevar. See
  12. # http://sam.zoy.org/wtfpl/COPYING for more details.
  13.  
  14. # Known/intentional 'bugs'
  15. # - If the 60secs restriction is lifted from messages, two in the same minute from
  16. #   the same user will be considered the same. Not currently a problem.
  17. # - Throws an error if anything other than 'Inbox' and 'Sent Items' folders are
  18. #   detected to prevent undefined behaviour
  19. # - OSX cannot deal with the updateUI event, so double clicking columns
  20. #   simply doesn't resize the window
  21.  
  22. # Reading messages
  23. import csv
  24. import os
  25. from BeautifulSoup import UnicodeDammit
  26.  
  27. # Playing with messages
  28. import itertools
  29. import sys
  30.  
  31. #############
  32. # FUNCTIONS #
  33. #############
  34.  
  35. # Retrieve messages from all csv files in current dir
  36. def getmessages():
  37.     if sys.platform == 'darwin':
  38.         messagefiles = ["../../../" + name for name in os.listdir("../../..")
  39.                         if name[-4:] == ".csv"]
  40.     else:
  41.         messagefiles = [name for name in os.listdir(".")
  42.                         if name[-4:] == ".csv"]
  43.     messages = set()
  44.     for name in messagefiles:
  45.         # Try finding the encoding, falling back if unsuccessful
  46.         encoding = UnicodeDammit(open(name).read(),
  47.                                  smartQuotesTo=None).originalEncoding
  48.         if not encoding:
  49.             encoding = 'latin-1'
  50.         with open(name) as csvfile:
  51.             reader = csv.reader(csvfile)
  52.             # Skip header of csv file
  53.             reader.next()
  54.             for [Date,Folder,Title,From,To,Message] in reader:
  55.                 messages.add(tuple(
  56.                     map(lambda x: x.decode(encoding),
  57.                         [Date,Title,From,To,Folder,Message])
  58.                     ))
  59.     return sorted(messages, reverse=False)
  60.  
  61. # Retrieve a message, format and output it for displaying
  62. def msg_display((msg_date, msg_co)):
  63.     [Date,Title,From,To,Folder,Message] = itertools.ifilter(
  64.         lambda x: x[0]==msg_date and (x[2]==msg_co or x[3]==msg_co),
  65.         messages).next()
  66.     return ("Date: " + Date + "        " + "Folder: " + Folder + "\n"
  67.             "Title: " + Title + "\n" +
  68.             "From: " + From + "\n" +
  69.             "To: " + To + "\n" +
  70.             "\n======================\n\n" + Message)
  71.    
  72.  
  73. ################
  74. # WX GUI SHITE #
  75. ################
  76.  
  77. import wx
  78.  
  79. class tsr_msg_panel(wx.Panel):
  80.  
  81.     class msg_list_ctrl(wx.ListCtrl):
  82.         def __init__(self, *args, **kwargs):
  83.             kwargs['style'] = wx.LC_REPORT | wx.LC_SINGLE_SEL
  84.             wx.ListCtrl.__init__(self, *args, **kwargs)
  85.  
  86.             self.InsertColumn(0, 'Date')
  87.             self.InsertColumn(1, 'Title', width=100)
  88.             self.InsertColumn(2, 'Correspondant')
  89.             self.InsertColumn(3, 'Folder')
  90.             # Populate list boxes
  91.             for [Date,Title,From,To,Folder,Message] in messages:
  92.                 pos = self.InsertStringItem(0, Date)
  93.                 self.SetStringItem(pos, 1, Title)
  94.                 if Folder == 'Inbox':
  95.                     self.SetStringItem(pos, 2, From)
  96.                     self.SetStringItem(pos, 3, 'Inbox')
  97.                 elif Folder == 'Sent Items':
  98.                     self.SetStringItem(pos, 2, To)
  99.                     self.SetStringItem(pos, 3, 'Sent')
  100.                 else:
  101.                     raise Exception
  102.             # Sort out columns etc
  103.             # Autosize doesn't reliably work so add leeway
  104.             self.SetColumnWidth(0, wx.LIST_AUTOSIZE)
  105.             self.SetColumnWidth(0, self.GetColumnWidth(0)+3)
  106.             self.SetColumnWidth(2, wx.LIST_AUTOSIZE)
  107.             self.SetColumnWidth(2, self.GetColumnWidth(2)+3)
  108.             self.SetColumnWidth(3, wx.LIST_AUTOSIZE_USEHEADER)
  109.             self.SetColumnWidth(3, self.GetColumnWidth(3)+3)
  110.             self.column_widths = [self.GetColumnWidth(x)
  111.                                   for x in range(4)]
  112.             size = (
  113.                 reduce(lambda x,y: x+self.GetColumnWidth(y),
  114.                        range(4), 0) + 25, 0)
  115.             self.SetMinSize(size)
  116.             self.SetSize(size)
  117.  
  118.     def setup_controls(self):
  119.         # Give the buttons their own layout for ease
  120.         layout = wx.GridBagSizer()
  121.         # Swap between text and html preview
  122.         self.swap_preview_button = wx.Button(self, label="Full preview")
  123.         self.swap_preview_button.Disable()
  124.         # Help button
  125.         self.help_button = wx.Button(self, label="?", style=wx.BU_EXACTFIT)
  126.         # Creep people out
  127.         self.creepy_label_1 = wx.StaticText(self,label=u"Chrosson is watching you ")
  128.         self.creepy_label_2 = wx.StaticText(self,label=u"ಠ_ಠ")
  129.         self.creepy_label_2.SetFont(wx.Font(10,wx.FONTFAMILY_DEFAULT,
  130.                                           wx.FONTSTYLE_NORMAL,wx.FONTWEIGHT_NORMAL,
  131.                                           False,"Tunga",wx.FONTENCODING_SYSTEM))
  132.         # Lay it all out
  133.         layout.Add(self.help_button,(0,0),(1,1),
  134.                     flag=wx.ALIGN_CENTER_VERTICAL)
  135.         layout.Add(self.creepy_label_1,(0,1),(1,1),
  136.                     flag=wx.LEFT|wx.ALIGN_CENTER_VERTICAL,border=10)
  137.         layout.Add(self.creepy_label_2,(0,2),(1,1),
  138.                     flag=wx.RIGHT|wx.ALIGN_CENTER_VERTICAL,border=10)
  139.         layout.Add(self.swap_preview_button,(0,3),(1,1),
  140.                     flag=wx.EXPAND|wx.ALL|wx.ALIGN_CENTER_VERTICAL)
  141.         layout.AddGrowableCol(3)
  142.         return layout
  143.    
  144.     class msg_view_panel(wx.Panel):
  145.         # Place to show the messages
  146.         def __init__(self, *args, **kwargs):
  147.             wx.Panel.__init__(self, *args, **kwargs)
  148.             # Place to display messages
  149.             self.tb_msg = wx.TextCtrl(self,-1,
  150.                                       style=wx.TE_MULTILINE|wx.TE_READONLY)
  151.             # Arrange it, make it resize nicely
  152.             view_sizer = wx.GridBagSizer()
  153.             view_sizer.Add(self.tb_msg,(0,0),(1,1),flag=wx.EXPAND)
  154.             view_sizer.AddGrowableCol(0)
  155.             view_sizer.AddGrowableRow(0)
  156.             self.SetSizerAndFit(view_sizer)
  157.  
  158.     class help_frame(wx.Frame):
  159.         # Give some help to the (l)users
  160.         def __init__(self, *args, **kwargs):
  161.             wx.Frame.__init__(self, *args, **kwargs)
  162.  
  163.             self.help_text = wx.TextCtrl(self,-1,
  164.                                       style=wx.TE_MULTILINE|wx.TE_READONLY)
  165.             self.help_text.ChangeValue(u"""Welcome to the TSR messages app.
  166. To use, open your PMs on TSR and look near the bottom - you can download selected (or all) messages as a CSV file.
  167. e.g. This URL will get all your PMs in a CSV - http://bit.ly/NFFmi0
  168. Do one or the other and put the file in the same folder as this program.
  169. Close and reopen the program - your messages should now be listed.
  170.  
  171. You can download as many CSV files as you like, rename them, whatever. As long as they're in the same directory as this program and have .csv at the end of their name, they will be detected and displayed (and duplicates ignored).
  172. This program is only known to work for the particular format of CSV TSR uses at the moment (July 2012), although I'm optimistic about the future.
  173.  
  174. Foreign characters should work.
  175. If you don't want flickering don't resize columns.
  176.  
  177. This program will NOT save any images or external files - it's strictly for the text of messages.
  178.  
  179. That's all.
  180.  
  181. - Chrosson
  182.  
  183. And no, I'm not really watching you.""")
  184.             # Lay out the help box
  185.             help_sizer = wx.GridBagSizer()
  186.             help_sizer.Add(self.help_text,(0,0),(1,1),flag=wx.EXPAND)
  187.             help_sizer.AddGrowableCol(0)
  188.             help_sizer.AddGrowableRow(0)
  189.             self.SetSizerAndFit(help_sizer)
  190.             self.SetSize((410,440))
  191.             self.Centre()
  192.             # Show it in init so can forget about it
  193.             self.Show(True)
  194.  
  195.     def __init__(self, parent, *args, **kwargs):
  196.         wx.Panel.__init__(self, parent, *args, **kwargs)
  197.         # Initialisation of various parts of the GUI
  198.         self.msg_list = tsr_msg_panel.msg_list_ctrl(self)
  199.         self.msg_view = tsr_msg_panel.msg_view_panel(self)
  200.         controls_sizer = self.setup_controls()
  201.         # Set up events for selecting an item,
  202.         # clicking the help button and resizing a column
  203.         # and a final horrible one for double clicking a column
  204.         self.Bind(wx.EVT_LIST_ITEM_SELECTED,
  205.                   self.msg_select,
  206.                   self.msg_list)
  207.         self.Bind(wx.EVT_BUTTON,
  208.                   lambda x: self.help_frame(None, title="Help"),
  209.                   self.help_button)
  210.         self.Bind(wx.EVT_LIST_COL_END_DRAG,
  211.                   self.event_column_resize,
  212.                   self.msg_list)
  213.         if sys.platform == 'win32':
  214.             self.Bind(wx.EVT_UPDATE_UI,
  215.                       self.event_update_UI,
  216.                       self.msg_list)
  217.         # Arrange everything nicely with sane resizing behaviour
  218.         self.top_sizer = wx.GridBagSizer()
  219.         self.top_sizer.Add(self.msg_list, (0,0), (2,1), wx.EXPAND)
  220.         self.top_sizer.Add(controls_sizer, (0,1), (1,1), wx.EXPAND)
  221.         self.top_sizer.Add(self.msg_view, (1,1), (1,1), wx.EXPAND)
  222.         self.top_sizer.AddGrowableCol(1)
  223.         self.top_sizer.AddGrowableRow(1)
  224.         self.top_sizer.SetItemMinSize(self.msg_view, (250,0))
  225.         self.SetSizerAndFit(self.top_sizer)
  226.         # Let the window know it has some resize limits
  227.         self.top_sizer.SetSizeHints(parent)
  228.         parent.SetSize((800,600))
  229.         parent.Centre()
  230.  
  231.     def event_update_UI(self, event):
  232.         # Horrible event handler just to watch for a double click resize
  233.         new_column_widths = [self.msg_list.GetColumnWidth(x)
  234.                            for x in range(4)]
  235.         if new_column_widths != self.msg_list.column_widths:
  236.             self.msg_list.column_widths = new_column_widths
  237.             self.resize_list_ctrl((sum(new_column_widths) + 23, 0))
  238.        
  239.     def event_column_resize(self, event):
  240.         # Watch out for when a column has been resized
  241.         if sys.platform == 'win32':
  242.             size = (
  243.                 reduce(lambda x,y: x+self.msg_list.GetColumnWidth(y),
  244.                        range(4), 0) + 23 -
  245.                 self.msg_list.GetColumnWidth(event.GetColumn()) +
  246.                 event.GetItem().GetWidth(), 0)
  247.         else:
  248.             size = (
  249.                 reduce(lambda x,y: x+self.msg_list.GetColumnWidth(y),
  250.                        range(4), 0) + 23, 0)
  251.         self.resize_list_ctrl(size)
  252.  
  253.     def resize_list_ctrl(self, size):
  254.         # Resize the list control
  255.         self.msg_list.SetMinSize(size)
  256.         self.msg_list.SetSize(size)
  257.         # Recompute the layout of the window
  258.         self.TopLevelParent.Layout()
  259.         # Remind the window that it has some new resize limits
  260.         windowsize = self.TopLevelParent.GetSize()
  261.         self.top_sizer.SetSizeHints(self.TopLevelParent)
  262.         self.TopLevelParent.SetSize(windowsize)
  263.        
  264.     def msg_select(self, event):
  265.         # A message has been selected, let's display it
  266.         row = event.GetIndex()
  267.         msg_key = (self.msg_list.GetItem(event.GetIndex(), 0).Text,
  268.                    self.msg_list.GetItem(event.GetIndex(), 2).Text)
  269.  
  270.         self.msg_view.tb_msg.SetValue(msg_display(msg_key))
  271.  
  272. # Dirty global variable
  273. messages = getmessages()
  274.  
  275. app = wx.App(False)
  276. frame = wx.Frame(None, title="TSR Msg App")
  277. tsr_msg_panel(frame)
  278. frame.Show()
  279. sys.exit(app.MainLoop())
Advertisement
Add Comment
Please, Sign In to add comment