text: import wx
  import os
  import re
  import time
  import base64
  import calendar
  import urllib2
  import urlparse
  import threading
  import feedparser
  from htmlentitydefs import name2codepoint
  from settings import settings
  
  def set_icon(window):
      bundle = wx.IconBundle()
      bundle.AddIcon(wx.Icon('icons/16.png', wx.BITMAP_TYPE_PNG))
      bundle.AddIcon(wx.Icon('icons/24.png', wx.BITMAP_TYPE_PNG))
      bundle.AddIcon(wx.Icon('icons/32.png', wx.BITMAP_TYPE_PNG))
      bundle.AddIcon(wx.Icon('icons/48.png', wx.BITMAP_TYPE_PNG))
      bundle.AddIcon(wx.Icon('icons/256.png', wx.BITMAP_TYPE_PNG))
      window.SetIcons(bundle)
      
  def start_thread(func, *args):
      thread = threading.Thread(target=func, args=args)
      thread.setDaemon(True)
      thread.start()
      return thread
      
  def scale_bitmap(bitmap, width, height, color):
      bw, bh = bitmap.GetWidth(), bitmap.GetHeight()
      if bw == width and bh == height:
          return bitmap
      if width < 0:
          width = bw
      if height < 0:
          height = bh
      buffer = wx.EmptyBitmap(bw, bh)
      dc = wx.MemoryDC(buffer)
      dc.SetBackground(wx.Brush(color))
      dc.Clear()
      dc.DrawBitmap(bitmap, 0, 0, True)
      image = wx.ImageFromBitmap(buffer)
      image = image.Scale(width, height, wx.IMAGE_QUALITY_HIGH)
      result = wx.BitmapFromImage(image)
      return result
      
  def menu_item(menu, label, func, icon=None, kind=wx.ITEM_NORMAL):
      item = wx.MenuItem(menu, -1, label, kind=kind)
      if func:
          menu.Bind(wx.EVT_MENU, func, id=item.GetId())
      if icon:
          item.SetBitmap(wx.Bitmap(icon))
      menu.AppendItem(item)
      return item
      
  def select_choice(choice, data):
      for index in range(choice.GetCount()):
          if choice.GetClientData(index) == data:
              choice.Select(index)
              return
      choice.Select(wx.NOT_FOUND)
      
  def get_top_window(window):
      result = None
      while window:
          result = window
          window = window.GetParent()
      return result
      
  def get(obj, key, default):
      value = obj.get(key, None)
      return value or default
      
  def abspath(path):
      path = os.path.abspath(path)
      path = 'file:///%s' % path.replace('\\', '/')
      return path
      
  def parse(url, username=None, password=None, etag=None, modified=None):
      agent = settings.USER_AGENT
      handlers = [get_proxy()]
      if username and password:
          url = insert_credentials(url, username, password)
      return feedparser.parse(url, etag=etag, modified=modified, agent=agent, handlers=handlers)
      
  def is_valid_feed(data):
      entries = get(data, 'entries', [])
      title = get(data.feed, 'title', '')
      link = get(data.feed, 'link', '')
      return entries or title or link
      
  def insert_credentials(url, username, password):
      parts = urlparse.urlsplit(url)
      netloc = parts.netloc
      if '@' in netloc:
          netloc = netloc[netloc.index('@')+1:]
      netloc = '%s:%s@%s' % (username, password, netloc)
      parts = list(parts)
      parts[1] = netloc
      return urlparse.urlunsplit(tuple(parts))
      
  def encode_password(password):
      return base64.b64encode(password) if password else None
      
  def decode_password(password):
      try:
          return base64.b64decode(password) if password else None
      except Exception:
          return None
          
  def get_proxy():
      if settings.USE_PROXY:
          url = decode_password(settings.PROXY_URL)
          if url:
              # User-configured Proxy
              map = {
                  'http': url,
                  'https': url,
              }
              proxy = urllib2.ProxyHandler(map)
          else:
              # Windows-configured Proxy
              proxy = urllib2.ProxyHandler()
      else:
          # No Proxy
          proxy = urllib2.ProxyHandler({})
      return proxy
      
  def find_themes():
      return ['default'] # TODO: more themes!
      result = []
      names = os.listdir('themes')
      for name in names:
          if name.startswith('.'):
              continue
          path = os.path.join('themes', name)
          if os.path.isdir(path):
              result.append(name)
      return result
      
  def guess_polling_interval(entries):
      if len(entries) < 2:
          return settings.DEFAULT_POLLING_INTERVAL
      timestamps = []
      for entry in entries:
          timestamp = calendar.timegm(get(entry, 'date_parsed', time.gmtime()))
          timestamps.append(timestamp)
      timestamps.sort()
      durations = [b - a for a, b in zip(timestamps, timestamps[1:])]
      mean = sum(durations) / len(durations)
      choices = [
          60,
          60*5,
          60*10,
          60*15,
          60*30,
          60*60,
          60*60*2,
          60*60*4,
          60*60*8,
          60*60*12,
          60*60*24,
      ]
      desired = mean / 2
      if desired == 0:
          interval = settings.DEFAULT_POLLING_INTERVAL
      elif desired < choices[0]:
          interval = choices[0]
      else:
          interval = max(choice for choice in choices if choice <= desired)
      return interval
      
  def time_since(t):
      t = int(t)
      now = int(time.time())
      seconds = max(now - t, 0)
      if seconds == 1:
          return '1 second'
      if seconds < 60:
          return '%d seconds' % seconds
      minutes = seconds / 60
      if minutes == 1:
          return '1 minute'
      if minutes < 60:
          return '%d minutes' % minutes
      hours = minutes / 60
      if hours == 1:
          return '1 hour'
      if hours < 24:
          return '%d hours' % hours
      days = hours / 24
      if days == 1:
          return '1 day'
      return '%d days' % days
      
  def split_time(seconds):
      if seconds < 60:
          return seconds, 0
      minutes = seconds / 60
      if minutes < 60:
          return minutes, 1
      hours = minutes / 60
      days = hours / 24
      if days and hours % 24 == 0:
          return days, 3
      return hours, 2
      
  def split_time_str(seconds):
      interval, units = split_time(seconds)
      strings = ['second', 'minute', 'hour', 'day']
      string = strings[units]
      if interval != 1:
          string += 's'
      return '%d %s' % (interval, string)
      
  def pretty_name(name):
      name = ' '.join(s.title() for s in name.split('_'))
      last = '0'
      result = ''
      for c in name:
          if c.isdigit() and not last.isdigit():
              result += ' '
          result += c
          last = c
      return result
      
  def replace_entities1(text):
      entity = re.compile(r'&#(\d+);')
      def func(match):
          try:
              return unichr(int(match.group(1)))
          except Exception:
              return match.group(0)
      return entity.sub(func, text)
      
  def replace_entities2(text):
      entity = re.compile(r'&([a-zA-Z]+);')
      def func(match):
          try:
              return unichr(name2codepoint[match.group(1)])
          except Exception:
              return match.group(0)
      return entity.sub(func, text)
      
  def remove_markup(text):
      html = re.compile(r'<[^>]+>')
      return html.sub(' ', text)
      
  def format(text, max_length=400):
      previous = ''
      while text != previous:
          previous = text
          text = replace_entities1(text)
          text = replace_entities2(text)
      text = remove_markup(text)
      text = ' '.join(text.split())
      if len(text) > max_length:
          text = text[:max_length].strip()
          text = text.split()[:-1]
          text.append('[...]')
          text = ' '.join(text)
      return text
      