def setup(self):
        """Setup and start the application"""
        # Connect some slots

        # Icon is clicked
        self.connect(self.trayIcon, \
            SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), \
            self.trayIconClicked)

        # Connnect slot emitted from CnfigureDialog to update preferences
        self.connect(self.configureDialog, SIGNAL('ConfigureDialogOk'), \
            self.saveConfig)

        # Set an initial icon for tray and weather dialog
        self.setTrayIcon(QIcon('images/22/dunno.png'))
        self.weatherDialog.labelIcon.setPixmap(QPixmap('images/64/dunno.png'))

        # Set the menu
        self.trayIcon.setContextMenu(self.createMenu())

        # Setup the config dialog with values loaded from config
        woeid = self.config.get('main', 'woeid')

        # If woeid is not valid set a default and use that
        try:
            self.configureDialog.setWoeid(woeid)
        except ValueError as ve:
            self.config.set('main', 'woeid', '2408842')
            self.configureDialog.setWoeid('2408842')

        # Set temperature units
        if self.config.get('units', 'temperature') == 'fahrenheit':
            self.configureDialog.setTemperature('fahrenheit')
        else:
            self.configureDialog.setTemperature('celcius')

        # Set distance units
        if self.config.get('units', 'distance') == 'km':
            self.configureDialog.setDistance('km')
        else:
            self.configureDialog.setDistance('mi')

        # Set wind units
        if self.config.get('units', 'wind') == 'kph':
            self.configureDialog.setWind('kph')
        else:
            self.configureDialog.setWind('mph')

        # Set pressure units
        if self.config.get('units', 'pressure') == 'mb':
            self.configureDialog.setPressure('mb')
        else:
            self.configureDialog.setPressure('in')

        # Start getWeather thread with Id from config
        # Connect two slots for the two signals emitted from thread
        self.getWeatherThread = GetWeatherQThread(self.config.get( \
            'main', 'woeid'))
        self.getWeatherThread.start()

        self.connect(self.getWeatherThread, SIGNAL('WeatherUpdate'), \
            self.processWeather)
        self.connect(self.getWeatherThread, SIGNAL('WeatherReadError'), \
            self.showErrorMessage)
class PigeonFeather(QMainWindow):
    """Main class for the application, inherits class genrated from pyuic"""

    def __init__(self, parent=None):
        super(PigeonFeather, self).__init__(parent)

        # Check that environment supports systemtray
        if not QSystemTrayIcon.isSystemTrayAvailable():
            print('FATAL: There is no system tray')
            sys.exit(1)

        # Make sure that we can load an icon list
        try:
            with open('code2iconlist.pkl', 'rb') as iconList:
                self.codeToIconList = pickle.load(iconList)
        except (IOError, pickle.PickleError):
            print('FATAL: Could not not load code2iconlist')
            sys.exit(1)

        # See if balloon messages are supported
        #print('Desktop support balloon messages = ' + \
        #    str(QSystemTrayIcon.supportsMessages()))

        # Set the user config fle
        self.USER_CONFIG = os.path.expanduser('~/.pigeonfeather')

        # Load preferences
        self.loadConfig()

        # Class properties
        self.trayIcon = QSystemTrayIcon(self)

        # Weather Dialog and Configure Dialog
        self.weatherDialog = WeatherDialog(self)
        self.configureDialog = ConfigureDialog(self)

        # Set up the application
        self.setup()

    def setup(self):
        """Setup and start the application"""
        # Connect some slots

        # Icon is clicked
        self.connect(self.trayIcon, \
            SIGNAL('activated(QSystemTrayIcon::ActivationReason)'), \
            self.trayIconClicked)

        # Connnect slot emitted from CnfigureDialog to update preferences
        self.connect(self.configureDialog, SIGNAL('ConfigureDialogOk'), \
            self.saveConfig)

        # Set an initial icon for tray and weather dialog
        self.setTrayIcon(QIcon('images/22/dunno.png'))
        self.weatherDialog.labelIcon.setPixmap(QPixmap('images/64/dunno.png'))

        # Set the menu
        self.trayIcon.setContextMenu(self.createMenu())

        # Setup the config dialog with values loaded from config
        woeid = self.config.get('main', 'woeid')

        # If woeid is not valid set a default and use that
        try:
            self.configureDialog.setWoeid(woeid)
        except ValueError as ve:
            self.config.set('main', 'woeid', '2408842')
            self.configureDialog.setWoeid('2408842')

        # Set temperature units
        if self.config.get('units', 'temperature') == 'fahrenheit':
            self.configureDialog.setTemperature('fahrenheit')
        else:
            self.configureDialog.setTemperature('celcius')

        # Set distance units
        if self.config.get('units', 'distance') == 'km':
            self.configureDialog.setDistance('km')
        else:
            self.configureDialog.setDistance('mi')

        # Set wind units
        if self.config.get('units', 'wind') == 'kph':
            self.configureDialog.setWind('kph')
        else:
            self.configureDialog.setWind('mph')

        # Set pressure units
        if self.config.get('units', 'pressure') == 'mb':
            self.configureDialog.setPressure('mb')
        else:
            self.configureDialog.setPressure('in')

        # Start getWeather thread with Id from config
        # Connect two slots for the two signals emitted from thread
        self.getWeatherThread = GetWeatherQThread(self.config.get( \
            'main', 'woeid'))
        self.getWeatherThread.start()

        self.connect(self.getWeatherThread, SIGNAL('WeatherUpdate'), \
            self.processWeather)
        self.connect(self.getWeatherThread, SIGNAL('WeatherReadError'), \
            self.showErrorMessage)

    def loadConfig(self):
        """Load preferences from defaults then self.USER_CONFIG if exists"""
        # Load a default set first
        defaultConfig = io.StringIO("""\
[main]
Woeid=2408842
[units]
temperature=celcius
wind=mph
pressure=mb
distance=mi
""")

        self.config = ConfigParser()

        # Load defaults
        self.config.readfp(defaultConfig)

        # Load config if it exists
        self.config.read(self.USER_CONFIG)

    def createMenu(self):
        """Create and return the applications menu"""
        menu = QMenu(self)
        menu.addAction(QIcon('images/22/sunny.png'), '&Show Weather Report', \
            self.showWeatherDialog)
        menu.addAction(QIcon('images/22/configure.png'), '&Configure', \
            self.showConfigureDialog)
        menu.addAction(QIcon('images/22/help.png'), '&About', \
            self.showAboutDialog)
        menu.addAction(QIcon('images/22/exit.png'), '&Exit', self.quitApp)
        return menu

    def saveConfig(self, config):
        """Save the recieved config back to the config file and update the
        local copy in the object

        Keyword arguments:
        config -- A dict. of config recieved from the configuration dialog
        """
        # Set the local config object and try and save it
        self.config.set('main', 'woeid', config['woeid'])
        self.config.set('units', 'temperature', config['temperature'])
        self.config.set('units', 'wind', config['wind'])
        self.config.set('units', 'pressure', config['pressure'])
        self.config.set('units', 'distance', config['distance'])

        # Update the Weoid in the get weather thread
        self.getWeatherThread.setWoeid(config['woeid'])

        # Try and save the config
        try:
            with open(self.USER_CONFIG, 'wb') as configfile:
                self.config.write(configfile)
        except IOError as ioe:
            self.showErrorMessage('Could not save configuration settings' + \
                'to disk')

    def showErrorMessage(self, message):
        """Show a error as a tray balloon message

        Keyword arguments:
        message -- Error message to display
        """
        self.trayIcon.showMessage('Application Error', message, \
            QSystemTrayIcon.Critical)

    def trayIconClicked(self, reason):
        """If the tray icon is left clicked, show/hide the weather dialog
        If this is called on a Darwin(mac) machine do not pop up.  This follows
        better mac convention

        Keyword arguments:
        reason -- A QSystemTrayIcon.ActivationReason enum
        """
        # If mac then ignore click
        if platform.system() == 'Darwin':
            return

        # Test for left click
        if reason == 3:
            if self.weatherDialog.isVisible():
                self.weatherDialog.hide()
            else:
                self.weatherDialog.show()

    def showWeatherDialog(self):
        """Show the weather report dialog"""
        self.weatherDialog.show()

    def showConfigureDialog(self):
        """Show the configure dialog"""
        self.configureDialog.show()

    def showAboutDialog(self):
        """Show the about pyqtweather dialog"""
        QMessageBox.about(None, 'About Pigeon Feather', 'Pigeon Feather\n \
            (c) 2010 Ben Sampson\nPigeon Feather uses the Yahoo! Weather API\n\
            License: GNU General Public License Version 3')

    def processWeather(self, weather):
        """Slot that is called by the weather thread, responsible for updating
        the GUI with the new weather data, this includes the trayicon and
        tooltip and the weather report dialog

        Keyword arguments:
        weather -- map of weather data
        """
        # TODO These should really call setter methods on weather dialog

        # Copy weather to local vars basd on preferences
        fetched = weather['fetched']

        code = weather['code']
        if self.config.get('units', 'temperature') == 'celcius':
            temp = weather['tempC']
            chill = weather['chillC']
            tempUnit = 'C'
        else:
            temp = weather['tempF']
            chill = weather['chillF']
            tempUnit = 'F'

        text = weather['text']
        city = weather['city']
        region = weather['region']
        country = weather['country']

        sunrise = weather['sunrise']
        sunset = weather['sunset']

        if self.config.get('units', 'wind') == 'mph':
            windSpeed = weather['windSpeedMph']
            speedUnit = 'mph'
        else:
            windSpeed = weather['windSpeedKph']
            speedUnit = 'kph'

        if self.config.get('units', 'pressure') == 'mb':
            pressure = weather['pressureMb']
            pressureUnit = 'mb'
        else:
            pressure = weather['pressureIn']
            pressureUnit = 'in'

        directionTextual = weather['directionTextual']
        pressureTendancy = weather['pressureTendancy']
        humidity = weather['humidity']

        if self.config.get('units', 'distance') == 'mi':
            visibility = weather['visibilityMi']
            distanceUnit = 'mi'
        else:
            visibility = weather['visibilityKm']
            distanceUnit = 'km'

        # Get the filename for the icon to disply from the icon list map
        # Generate the system tray icon and set it
        iconFileName = self.codeToIconList[int(code)][1]
        icon = self.createWeatherIcon('images/22/' + iconFileName, str(temp))
        self.setTrayIcon(icon)

        # Set the tool tip
        tempString = str(temp) + '°' + tempUnit + ' ' + text
        self.trayIcon.setToolTip(tempString)

        # Update the weather report dialog
        self.weatherDialog.labelLastUpdate.setText( \
            fetched.strftime('%H:%M:%S'))
        self.weatherDialog.setWindowTitle('Weather report for ' + city + \
            ', ' + region + ' ' + country)
        self.weatherDialog.labelTemp.setText(tempString)
        self.weatherDialog.labelSunrise.setText(sunrise)
        self.weatherDialog.labelSunset.setText(sunset)
        self.weatherDialog.labelWindChill.setText(str(chill) + \
            '°' + tempUnit)
        self.weatherDialog.labelWindSpeed.setText(str(windSpeed) + ' ' + \
            speedUnit)
        self.weatherDialog.labelWindDirection.setText(directionTextual)
        self.weatherDialog.labelHumidity.setText(str(humidity) + '%')
        self.weatherDialog.labelVisibility.setText(str(visibility) + ' ' + \
            distanceUnit)
        self.weatherDialog.labelPressure.setText(str(pressure) + ' ' + \
            pressureUnit)
        self.weatherDialog.labelRising.setText(pressureTendancy)

        # Set the image
        self.weatherDialog.labelIcon.setPixmap(QPixmap('images/64/' + \
          iconFileName))

    # TODO - this should really be in another class
    def createWeatherIcon(self, iconFileName, temp):
        """Create the icon to display in the tray"""
        # Create a map of what image to use based on code
        # Start by creating a transparent image to paint on
        print(('Using' + iconFileName))
        icon = QPixmap(22, 22)
        icon.fill(Qt.transparent)

        # Create a painter to paint on to the icon and draw on the text
        painter = QPainter(icon)
        painter.setOpacity(0.5)

        # Draw text of temperature
        font = QFont('Times', 10, QFont.Black)
        painter.setFont(font)
        painter.setPen(QColor('red'))
        painter.drawPixmap(QPoint(0, 0), QPixmap(iconFileName))
        painter.setOpacity(1)
        painter.drawText(5, 15, temp)
        painter.end()

        # Return the icon
        return QIcon(icon)

    def setTrayIcon(self, icon):
        """Set the tray icon"""
        self.trayIcon.setIcon(icon)
        self.trayIcon.show()

    def quitApp(self):
        """Exit the application"""
        sys.exit(0)