<commit_before>#!/bin/python
'''

This file provides the endpoints for the demo interface.
The functions in this file should be called from NodeJS to
obtain and update datapoints.

Description of functionality
---

data model:
	datapoint = { str(noun phrase) : { 'score': float(controversy score), 'confidence':float(confidence)}}
	estimates = l st(datapoint1, .... , datapointN)
	labelled  = { str(noun phrase) : { 'label' : 'controversial' OR 'noncontroversial') , 'ip' : str(ip address of user) } }

included endpoints:

GET /controversial
	returns the estimates for top-10 most and least controversial topics

GET /unsure
	returns a list of datapoints to label

PUT /unsure
	updates model
	redirects to /controversial (which should yield new ranks)

Usage
---

You can call this script from the commandline and parse the result as json
use the following syntax:

$> endpoints.py <endpoint> [data]

Examples:

$> endpoints.py controversial

OR

$> endpoints.py unsure

OR

$> endpoints.py unsure '[{"Steve Jobs":{"label":"controversial", "ip":""}}]'

Notes on implementation
---

Controversy is labelled on the noun-phrase (here considered topic) level.
Timestamps should be implemented on the backend side.

'''
import random # while real data lacks
import json
import sys
import os
from elasticsearch import Elasticsearch
from pprint import pprint

es = Elasticsearch(
    ['http://controcurator.org/ess/'],
    port=80)

ELASTIC_CREDENTIALS = '#elastic.json'
ELASTIC_INDEX       = ''

#es = Elasticsearch(**json.load(open(ELASTI_CREDENTIALS)))

### Placeholder functions
if not 'fake_data.json' in os.listdir('.'):

	nounphrases = [item['_source']['text'] for item in json.load(open(os.getcwd()+'/python_code/es_1000_dump.json'))]
	fake_data = [{x:{'score':random.betavariate(1,1),'confidence':random.betavariate(1,1)}} for x in nounphrases]
	json.dump(fake_data, open('fake_data.json','w'))
else:
	fake_data = json.load(open('fake_data.json'))

#### End placeholder functions

def controversial(method='GET'):
	'''
	implements the /controversial endpoint.

	parameters
	----
	none

	returns
	---

	{
		controversial    : estimates,
		noncontroversial : estimates
	}
	'''

	# get top 10 topics
	topQuery = {"query": {
	    "filtered": {
	      "query": {
	        "query_string": {
	          "analyze_wildcard": True,
	          "query": "*"
	        }
	      },
	      "filter": {
	        "bool": {
	          "must": [
	            {
	              "range": {
	                "count": {
	                  "gt": 20,
	                }
	              }
	            }
	          ],
	          "must_not": []
	        }
	      }
	    }
	  },
	  "size": 0,
	  "aggs": {
	    "topics": {
	      "terms": {
	        "field": "topic",
	        "size": 20,
	        "order": {
	          "cont": "desc"
	        }
	      },
	      "aggs": {
	        "cont": {
	          "avg": {
	            "field": "controversy"
	          }
	        },
	        "pos": {
	          "sum": {
	            "field": "positive"
	          }
	        },
	        "neg": {
	          "sum": {
	            "field": "negative"
	          }
	        },
	        "count": {
	          "sum": {
	            "field": "count"
	          }
	        }
	      }
	    }
	  }
	}

	# get top 10 topics
	bottomQuery = {"query": {
	    "filtered": {
	      "query": {
	        "query_string": {
	          "analyze_wildcard": True,
	          "query": "*"
	        }
	      },
	      "filter": {
	        "bool": {
	          "must": [
	            {
	              "range": {
	                "count": {
	                  "gte": 10,
	                }
	              }
	            }
	          ],
	          "must_not": []
	        }
	      }
	    }
	  },
	  "size": 0,
	  "aggs": {
	    "topics": {
	      "terms": {
	        "field": "topic",
	        "size": 10,
	        "order": {
	          "cont": "asc"
	        }
	      },
	      "aggs": {
	        "cont": {
	          "avg": {
	            "field": "controversy"
	          }
	        },
	        "pos": {
	          "sum": {
	            "field": "positive"
	          }
	        },
	        "neg": {
	          "sum": {
	            "field": "negative"
	          }
	        },
	        "count": {
	          "sum": {
	            "field": "count"
	          }
	        }
	      }
	    }
	  }
	}

	top = es.search(index="topics", doc_type="vaccination", body=topQuery)
	topTopics = top['aggregations']['topics']['buckets']

	bottom = es.search(index="topics", doc_type="vaccination", body=bottomQuery)
	bottomTopics = bottom['aggregations']['topics']['buckets']

	results = {'controversial':topTopics, 'noncontroversial':bottomTopics}
	return json.dumps(results)

def unsure(method='GET', data='[]'):
	'''
	implements the /controversial endpoint.

	parameters
	----
	data: labelled

	returns
	---

	{
		controversial    : estimates,
		noncontroversial : estimates
	}
	'''
	if method == 'GET':
		datapoints       = sorted(fake_data, key=lambda x : list(x.values())[0].get('confidence',0))
		unsure  = datapoints[0]
		results = { 'unsure' : unsure }
		return json.dumps(results)
	else:
		''' expects an object like: {'nounphrase': {'label':'controversial'/'noncrontroversial','ip':'127.0.01'}}'''
		data = json.loads(data)
		if type(data)==dict:
			data = [data]
		global fake_date
		for fdata in fake_data:
			fdatakey = list(fdata.keys())[0]
			fdataval = list(fdata.values())[0]
			update = [list(d.values())[0] for d in data if list(d.keys())[0]==fdatakey]
			if update:
				nscore = update[0].get('labelled','none') == 'controversial' and 1 or 0
				oldscore = fdataval['score']
				fdataval['score'] += (oldscore - nscore) / 5
				fdata = {fdatakey:fdataval}
		json.dump(fake_data, open('fake_data.json','w'))
		return controversial() # hacky hacky redirect-ey

if __name__=='__main__':

	if len(sys.argv)==1:
		print(json.dumps({"ERROR":"NO ENDPOINT SPECIFIED"}))
	if len(sys.argv)==2:
		#try:
		if sys.argv[1]   == 'controversial': print(controversial())
		elif sys.argv[1] == 'unsure': print(unsure())
		else: print(json.dumps({"404":"Unknown endpoint"}))

		#except:
		#	print( json.dumps({'502':'stuff died'}))
	if len(sys.argv)==3:
		try:
			print(unsure(method='PUT', data=sys.argv[2]))
		except:
			raise
			print(json.dumps({'502':'stuff died'}))
<commit_msg>Added temporary endpoint queries<commit_after>#!/bin/python
'''

This file provides the endpoints for the demo interface.
The functions in this file should be called from NodeJS to
obtain and update datapoints.

Description of functionality
---

data model:
	datapoint = { str(noun phrase) : { 'score': float(controversy score), 'confidence':float(confidence)}}
	estimates = l st(datapoint1, .... , datapointN)
	labelled  = { str(noun phrase) : { 'label' : 'controversial' OR 'noncontroversial') , 'ip' : str(ip address of user) } }

included endpoints:

GET /controversial
	returns the estimates for top-10 most and least controversial topics

GET /unsure
	returns a list of datapoints to label

PUT /unsure
	updates model
	redirects to /controversial (which should yield new ranks)

Usage
---

You can call this script from the commandline and parse the result as json
use the following syntax:

$> endpoints.py <endpoint> [data]

Examples:

$> endpoints.py controversial

OR

$> endpoints.py unsure

OR

$> endpoints.py unsure '[{"Steve Jobs":{"label":"controversial", "ip":""}}]'

Notes on implementation
---

Controversy is labelled on the noun-phrase (here considered topic) level.
Timestamps should be implemented on the backend side.

'''
import random # while real data lacks
import json
import sys
import os
from elasticsearch import Elasticsearch
from pprint import pprint

es = Elasticsearch(
    ['http://controcurator.org/ess/'],
    port=80)

ELASTIC_CREDENTIALS = '#elastic.json'
ELASTIC_INDEX       = ''

#es = Elasticsearch(**json.load(open(ELASTI_CREDENTIALS)))

### Placeholder functions
if not 'fake_data.json' in os.listdir('.'):
	nounphrases = [item['_source']['text'] for item in json.load(open(os.getcwd()+'/python_code/es_1000_dump.json'))]
	fake_data = [{x:{'score':random.betavariate(1,1),'confidence':random.betavariate(1,1)}} for x in nounphrases]
	json.dump(fake_data, open('fake_data.json','w'))
else:
	fake_data = json.load(open('fake_data.json'))

#### End placeholder functions

def controversial(method='GET'):
	'''
	implements the /controversial endpoint.

	parameters
	----
	none

	returns
	---

	{
		controversial    : estimates,
		noncontroversial : estimates
	}
	'''

	# get top 10 topics
	topQuery = {"query": {
	    "filtered": {
	      "query": {
	        "query_string": {
	          "analyze_wildcard": True,
	          "query": "*"
	        }
	      },
	      "filter": {
	        "bool": {
	          "must": [
	            {
	              "range": {
	                "count": {
	                  "gt": 20,
	                }
	              }
	            }
	          ],
	          "must_not": []
	        }
	      }
	    }
	  },
	  "size": 0,
	  "aggs": {
	    "topics": {
	      "terms": {
	        "field": "topic",
	        "size": 20,
	        "order": {
	          "cont": "desc"
	        }
	      },
	      "aggs": {
	        "cont": {
	          "avg": {
	            "field": "controversy"
	          }
	        },
	        "pos": {
	          "sum": {
	            "field": "positive"
	          }
	        },
	        "neg": {
	          "sum": {
	            "field": "negative"
	          }
	        },
	        "count": {
	          "sum": {
	            "field": "count"
	          }
	        }
	      }
	    }
	  }
	}

	# get top 10 topics
	bottomQuery = {"query": {
	    "filtered": {
	      "query": {
	        "query_string": {
	          "analyze_wildcard": True,
	          "query": "*"
	        }
	      },
	      "filter": {
	        "bool": {
	          "must": [
	            {
	              "range": {
	                "count": {
	                  "gte": 10,
	                }
	              }
	            }
	          ],
	          "must_not": []
	        }
	      }
	    }
	  },
	  "size": 0,
	  "aggs": {
	    "topics": {
	      "terms": {
	        "field": "topic",
	        "size": 10,
	        "order": {
	          "cont": "asc"
	        }
	      },
	      "aggs": {
	        "cont": {
	          "avg": {
	            "field": "controversy"
	          }
	        },
	        "pos": {
	          "sum": {
	            "field": "positive"
	          }
	        },
	        "neg": {
	          "sum": {
	            "field": "negative"
	          }
	        },
	        "count": {
	          "sum": {
	            "field": "count"
	          }
	        }
	      }
	    }
	  }
	}

	top = es.search(index="topics", doc_type="vaccination", body=topQuery)
	topTopics = top['aggregations']['topics']['buckets']

	bottom = es.search(index="topics", doc_type="vaccination", body=bottomQuery)
	bottomTopics = bottom['aggregations']['topics']['buckets']

	results = {'controversial':topTopics, 'noncontroversial':bottomTopics}
	return json.dumps(results)

def unsure(method='GET', data='[]'):
	'''
	implements the /controversial endpoint.

	parameters
	----
	data: labelled

	returns
	---

	{
		controversial    : estimates,
		noncontroversial : estimates
	}
	'''
	if method == 'GET':
		# get top 10 topics
		bottomQuery = {"query": {
		    "filtered": {
		      "query": {
		        "query_string": {
		          "analyze_wildcard": True,
		          "query": "*"
		        }
		      },
		      "filter": {
		        "bool": {
		          "must": [
		            {
		              "range": {
		                "count": {
		                  "gte": 5,
		                }
		              }
		            }
		          ],
		          "must_not": []
		        }
		      }
		    }
		  },
		  "size": 0,
		  "aggs": {
		    "topics": {
		      "terms": {
		        "field": "topic",
		        "size": 1,
		        "order": {
		          "cont": "asc"
		        }
		      },
		      "aggs": {
		        "cont": {
		          "avg": {
		            "field": "controversy"
		          }
		        },
		        "pos": {
		          "sum": {
		            "field": "positive"
		          }
		        },
		        "neg": {
		          "sum": {
		            "field": "negative"
		          }
		        },
		        "count": {
		          "sum": {
		            "field": "count"
		          }
		        }
		      }
		    }
		  }
		}
		bottom = es.search(index="topics", doc_type="vaccination", body=bottomQuery)
		unsure = bottom['aggregations']['topics']['buckets']
		results = { 'unsure' : unsure }
		return json.dumps(results)
	else:
		''' expects an object like: {'nounphrase': {'label':'controversial'/'noncrontroversial','ip':'127.0.01'}}'''
		data = json.loads(data)
		if type(data)==dict:
			data = [data]
		global fake_date
		for fdata in fake_data:
			fdatakey = list(fdata.keys())[0]
			fdataval = list(fdata.values())[0]
			update = [list(d.values())[0] for d in data if list(d.keys())[0]==fdatakey]
			if update:
				nscore = update[0].get('labelled','none') == 'controversial' and 1 or 0
				oldscore = fdataval['score']
				fdataval['score'] += (oldscore - nscore) / 5
				fdata = {fdatakey:fdataval}
		json.dump(fake_data, open('fake_data.json','w'))
		return controversial() # hacky hacky redirect-ey

if __name__=='__main__':

	if len(sys.argv)==1:
		print(json.dumps({"ERROR":"NO ENDPOINT SPECIFIED"}))
	if len(sys.argv)==2:
		#try:
		if sys.argv[1]   == 'controversial': print(controversial())
		elif sys.argv[1] == 'unsure': print(unsure())
		else: print(json.dumps({"404":"Unknown endpoint"}))

		#except:
		#	print( json.dumps({'502':'stuff died'}))
	if len(sys.argv)==3:
		try:
			print(unsure(method='PUT', data=sys.argv[2]))
		except:
			raise
			print(json.dumps({'502':'stuff died'}))
<|endoftext|>


<commit_before>__version__ = '1.1'
__author__ = 'Rick van Hattem'
__author_email__ = 'Rick.van.Hattem@Fawo.nl'
__description__ = ('Python Utils is a module with some convenient utilities '
    'not included with the standard Python install')
<commit_msg>bumped version<commit_after>__version__ = '1.1.0'
__author__ = 'Rick van Hattem'
__author_email__ = 'Rick.van.Hattem@Fawo.nl'
__description__ = ('Python Utils is a module with some convenient utilities '
    'not included with the standard Python install')
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
'''Contains the `VowsDefaultReporter` class, which handles output after tests
have been run.
'''
# pyvows testing engine
# https://github.com/heynemann/pyvows

# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license
# Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com
from __future__ import division

import sys

from pyvows.color import yellow, red, blue
from pyvows.errors import VowsInternalError
from pyvows.reporting.common import (
    ensure_encoded,
    V_EXTRA_VERBOSE,
    V_VERBOSE,
    VowsReporter,)

class VowsTestReporter(VowsReporter):
    '''A VowsReporter which prints test results.'''

    def __init__(self, result, verbosity):
        super(VowsTestReporter, self).__init__(result, verbosity)

    @property
    def status_symbol(self):
        '''Returns the symbol indicating whether all tests passed.'''
        if self.result.successful:
            return VowsReporter.HONORED
        else:
            return VowsReporter.BROKEN

    #-------------------------------------------------------------------------
    #   Class Methods
    #-------------------------------------------------------------------------
    @classmethod
    def on_vow_success(cls, vow):
        #   FIXME: Add Docstring / Comment description
        #
        #       *   Why is `vow` unused?
        sys.stdout.write(VowsReporter.HONORED)

    @classmethod
    def on_vow_error(cls, vow):
        #   FIXME: Add Docstring / Comment description
        #
        #       *   Why is `vow` unused?
        sys.stdout.write(VowsReporter.BROKEN)

    #-------------------------------------------------------------------------
    #   Printing Methods
    #-------------------------------------------------------------------------
    def pretty_print(self):
        '''Prints PyVows test results.'''
        print self.header('Vows Results')

        if not self.result.contexts:
            # FIXME:
            #   If no vows are found, how could any be broken?
            print '{indent}{broken} No vows found! » 0 honored • 0 broken (0.0s)'.format(
                indent=self.TAB * self.indent,
                broken=VowsReporter.BROKEN,
            )
            return

        if self.verbosity >= V_VERBOSE or self.result.errored_tests:
            print

        for context in self.result.contexts:
            self.print_context(context['name'], context)

        print '{0}{1} OK » {honored:d} honored • {broken:d} broken ({time:.6f}s)'.format(
            self.TAB * self.indent,
            self.status_symbol,
            honored=self.result.successful_tests,
            broken=self.result.errored_tests,
            time=self.result.elapsed_time)

        print

    def print_context(self, name, context):
        #   FIXME: Add Docstring
        #
        #       *   Is this only used in certain cases?
        #           *   If so, which?
        self.indent += 1

        if (self.verbosity >= V_VERBOSE or
                not self.result.eval_context(context)):
            self.humanized_print(name)

        def _print_successful_context():
            honored = ensure_encoded(VowsReporter.HONORED)
            topic = ensure_encoded(test['topic'])
            name = ensure_encoded(test['name'])

            if self.verbosity == V_VERBOSE:
                self.humanized_print('{0} {1}'.format(honored, name))
            elif self.verbosity >= V_EXTRA_VERBOSE:
                if test['enumerated']:
                    self.humanized_print('{0} {1} - {2}'.format(honored, topic, name))
                else:
                    self.humanized_print('{0} {1}'.format(honored, name))

        def _print_failed_context():
            ctx = test['context_instance']

            def _print_traceback():
                self.indent += 2

                try:
                    traceback_args = None

                    if hasattr(test, 'topic') \
                            and hasattr(test['topic'], 'error')  \
                            and test['topic']['error'] is not None:
                        print '\n' + self.indent_msg(blue('Topic Error:'))
                        traceback_args = tuple(*test['topic'].error)
                    else:
                        traceback_args = (test['error']['type'],
                                          test['error']['value'],
                                          test['error']['traceback'])
                    self.print_traceback(*traceback_args)
                except Exception:
                    # should never occur!
                    err_msg = '''Unexpected error in PyVows!
                                 PyVows error occurred in: ({0!s})
                                 Context was: {1!r}

                              '''
                    # from os.path import abspath
                    raise VowsInternalError(err_msg,
                                            'pyvows.reporting.test',
                                            ctx)

                # print file and line number
                if 'file' in test:
                    file_msg = 'found in {test[file]} at line {test[lineno]}'.format(test=test)
                    print
                    print self.indent_msg(red(file_msg))
                    print

                self.indent -= 2

            self.humanized_print('{0} {test}'.format(
                VowsReporter.BROKEN,
                test=test['name']))

            # print generated topic (if applicable)
            if ctx.generated_topic:
                value = yellow(test['topic'])
                self.humanized_print('')
                self.humanized_print('\tTopic value:')
                self.humanized_print('\t{value}'.format(value=value))
                self.humanized_print('\n' * 2)

            # print traceback
            _print_traceback()

        for test in context['tests']:
            if test['succeeded']:
                _print_successful_context()
            else:
                _print_failed_context()

        # I hereby (re)curse you...!
        for context in context['contexts']:
            self.print_context(context['name'], context)

        self.indent -= 1
<commit_msg>reporting.test: commented out problematic `except:` block<commit_after># -*- coding: utf-8 -*-
'''Contains the `VowsDefaultReporter` class, which handles output after tests
have been run.
'''
# pyvows testing engine
# https://github.com/heynemann/pyvows

# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license
# Copyright (c) 2011 Bernardo Heynemann heynemann@gmail.com
from __future__ import division

import sys

from pyvows.color import yellow, red, blue
from pyvows.errors import VowsInternalError
from pyvows.reporting.common import (
    ensure_encoded,
    V_EXTRA_VERBOSE,
    V_VERBOSE,
    VowsReporter,)

class VowsTestReporter(VowsReporter):
    '''A VowsReporter which prints test results.'''

    def __init__(self, result, verbosity):
        super(VowsTestReporter, self).__init__(result, verbosity)

    @property
    def status_symbol(self):
        '''Returns the symbol indicating whether all tests passed.'''
        if self.result.successful:
            return VowsReporter.HONORED
        else:
            return VowsReporter.BROKEN

    #-------------------------------------------------------------------------
    #   Class Methods
    #-------------------------------------------------------------------------
    @classmethod
    def on_vow_success(cls, vow):
        #   FIXME: Add Docstring / Comment description
        #
        #       *   Why is `vow` unused?
        sys.stdout.write(VowsReporter.HONORED)

    @classmethod
    def on_vow_error(cls, vow):
        #   FIXME: Add Docstring / Comment description
        #
        #       *   Why is `vow` unused?
        sys.stdout.write(VowsReporter.BROKEN)

    #-------------------------------------------------------------------------
    #   Printing Methods
    #-------------------------------------------------------------------------
    def pretty_print(self):
        '''Prints PyVows test results.'''
        print self.header('Vows Results')

        if not self.result.contexts:
            # FIXME:
            #   If no vows are found, how could any be broken?
            print '{indent}{broken} No vows found! » 0 honored • 0 broken (0.0s)'.format(
                indent=self.TAB * self.indent,
                broken=VowsReporter.BROKEN,
            )
            return

        if self.verbosity >= V_VERBOSE or self.result.errored_tests:
            print

        for context in self.result.contexts:
            self.print_context(context['name'], context)

        print '{0}{1} OK » {honored:d} honored • {broken:d} broken ({time:.6f}s)'.format(
            self.TAB * self.indent,
            self.status_symbol,
            honored=self.result.successful_tests,
            broken=self.result.errored_tests,
            time=self.result.elapsed_time)

        print

    def print_context(self, name, context):
        #   FIXME: Add Docstring
        #
        #       *   Is this only used in certain cases?
        #           *   If so, which?
        self.indent += 1

        if (self.verbosity >= V_VERBOSE or
                not self.result.eval_context(context)):
            self.humanized_print(name)

        def _print_successful_context():
            honored = ensure_encoded(VowsReporter.HONORED)
            topic = ensure_encoded(test['topic'])
            name = ensure_encoded(test['name'])

            if self.verbosity == V_VERBOSE:
                self.humanized_print('{0} {1}'.format(honored, name))
            elif self.verbosity >= V_EXTRA_VERBOSE:
                if test['enumerated']:
                    self.humanized_print('{0} {1} - {2}'.format(honored, topic, name))
                else:
                    self.humanized_print('{0} {1}'.format(honored, name))

        def _print_failed_context():
            ctx = test['context_instance']

            def _print_traceback():
                self.indent += 2

                ### NOTE:
                ###     Commented out try/except; potential debugging hinderance

                #try:

                traceback_args = None
                if (hasattr(test, 'topic')
                        and hasattr(test['topic'], 'error')
                        and test['topic']['error'] is not None):
                    print '\n' + self.indent_msg(blue('Topic Error:'))
                    traceback_args = tuple(*test['topic'].error)
                else:
                    traceback_args = (test['error']['type'],
                                      test['error']['value'],
                                      test['error']['traceback'])
                self.print_traceback(*traceback_args)

                # except Exception:
                #     # should never occur!
                #     err_msg = '''Unexpected error in PyVows!
                #                  PyVows error occurred in: ({0!s})
                #                  Context was: {1!r}
                #
                #               '''
                #     # from os.path import abspath
                #     raise VowsInternalError(err_msg, 'pyvows.reporting.test', ctx)

                # print file and line number
                if 'file' in test:
                    file_msg = 'found in {test[file]} at line {test[lineno]}'.format(test=test)
                    print
                    print self.indent_msg(red(file_msg))
                    print

                self.indent -= 2

            self.humanized_print('{0} {test}'.format(
                VowsReporter.BROKEN,
                test=test['name']))

            # print generated topic (if applicable)
            if ctx.generated_topic:
                value = yellow(test['topic'])
                self.humanized_print('')
                self.humanized_print('\tTopic value:')
                self.humanized_print('\t{value}'.format(value=value))
                self.humanized_print('\n' * 2)

            # print traceback
            _print_traceback()

        for test in context['tests']:
            if test['succeeded']:
                _print_successful_context()
            else:
                _print_failed_context()

        # I hereby (re)curse you...!
        for context in context['contexts']:
            self.print_context(context['name'], context)

        self.indent -= 1
<|endoftext|>


<commit_before># GUI Application automation and testing library
# Copyright (C) 2006 Mark Mc Mahon
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2.1
# of the License, or (at your option) any later version.
#
# This library 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
#    Free Software Foundation, Inc.,
#    59 Temple Place,
#    Suite 330,
#    Boston, MA 02111-1307 USA

"""Functions to retrieve properties from a window handle

These are implemented in a procedural way so as to to be
useful to other modules with the least conceptual overhead
"""

__revision__ = "$Revision$"

import ctypes

import win32functions
import win32defines
import win32structures

import findwindows # for children

#=========================================================================
def text(handle):
    "Return the text of the window"
    length = win32functions.GetWindowTextLength(handle,)

    textval = ''
    if length:
        length += 1

        buffer_ = (ctypes.c_wchar * length)()

        ret =  win32functions.GetWindowText(
            handle, ctypes.byref(buffer_), length)

        if ret:
            textval = buffer_.value

    return textval

#=========================================================================
def classname(handle):
    "Return the class name of the window"
    class_name = (ctypes.c_wchar * 257)()
    win32functions.GetClassName (handle, ctypes.byref(class_name), 256)
    return class_name.value

#=========================================================================
def parent(handle):
    "Return the handle of the parent of the window"
    return win32functions.GetParent(handle)

#=========================================================================
def style(handle):
    "Return the style of the window"
    return win32functions.GetWindowLong (handle, win32defines.GWL_STYLE)

#=========================================================================
def exstyle(handle):
    "Return the extended style of the window"
    return win32functions.GetWindowLong (handle, win32defines.GWL_EXSTYLE)

#=========================================================================
def controlid(handle):
    "Return the ID of the control"
    return win32functions.GetWindowLong (handle, win32defines.GWL_ID)

#=========================================================================
def userdata(handle):
    "Return the value of any userdata associated with the window"
    return win32functions.GetWindowLong (handle, win32defines.GWL_USERDATA)

#=========================================================================
def contexthelpid(handle):
    "Return the context help id of the window"
    return win32functions.GetWindowContextHelpId (handle)

#=========================================================================
def isvisible(handle):
    "Return True if the window is visible"
    return bool(win32functions.IsWindowVisible(handle))

#=========================================================================
def isunicode(handle):
    "Teturn True if the window is a unicode window"
    return bool(win32functions.IsWindowUnicode(handle))

#=========================================================================
def isenabled(handle):
    "Return True if the window is enabled"
    return bool(win32functions.IsWindowEnabled(handle))

#=========================================================================
def clientrect(handle):
    "Return the client rectangle of the control"
    client_rect = win32structures.RECT()
    win32functions.GetClientRect(handle, ctypes.byref(client_rect))
    return client_rect

#=========================================================================
def rectangle(handle):
    "Return the rectangle of the window"
    rect = win32structures.RECT()
    win32functions.GetWindowRect(handle, ctypes.byref(rect))
    return rect

#=========================================================================
def font(handle):
    "Return the font as a LOGFONTW of the window"

    # get the font handle
    font_handle = win32functions.SendMessage(
        handle, win32defines.WM_GETFONT, 0, 0)

    # if the fondUsed is 0 then the control is using the
    # system font
    if not font_handle:
        font_handle = win32functions.GetStockObject(win32defines.SYSTEM_FONT)

    # Get the Logfont structure of the font of the control
    fontval = win32structures.LOGFONTW()
    ret = win32functions.GetObject(
        font_handle, ctypes.sizeof(fontval), ctypes.byref(fontval))

    # The function could not get the font - this is probably
    # because the control does not have associated Font/Text
    # So we should make sure the elements of the font are zeroed.
    if not ret:
        fontval = win32structures.LOGFONTW()

    # if it is a main window
    if (has_style(handle, win32defines.WS_OVERLAPPED) or \
        has_style(handle, win32defines.WS_CAPTION)) and \
        not has_style(handle, win32defines.WS_CHILD):

        if "MS Shell Dlg" in fontval.lfFaceName or \
            fontval.lfFaceName == "System":
            # these are not usually the fonts actaully used in for
            # title bars so we need to get the default title bar font

            # get the title font based on the system metrics rather
            # than the font of the control itself
            ncms = win32structures.NONCLIENTMETRICSW()
            ncms.cbSize = ctypes.sizeof(ncms)
            win32functions.SystemParametersInfo(
                win32defines.SPI_GETNONCLIENTMETRICS,
                ctypes.sizeof(ncms),
                ctypes.byref(ncms),
                0)

            # with either of the following 2 flags set the font of the
            # dialog isthe small one (but there is normally no difference!
            if has_style(handle, win32defines.WS_EX_TOOLWINDOW) or \
               has_style(handle, win32defines.WS_EX_PALETTEWINDOW):

                fontval = ncms.lfSmCaptionFont
            else:
                fontval = ncms.lfCaptionFont

    return fontval

#=========================================================================
def processid(handle):
    "Retrun the ID of process that controls this window"
    process_id = ctypes.c_int()
    win32functions.GetWindowThreadProcessId(handle, ctypes.byref(process_id))

    return process_id.value

#=========================================================================
def children(handle):
    "Return a list of handles to the children of this window"
    return findwindows.enum_child_windows(handle)

#=========================================================================
def has_style(handle, tocheck):
    "Return True if the control has style tocheck"
    hwnd_style = style(handle)
    return tocheck & hwnd_style == tocheck

#=========================================================================
def has_exstyle(handle, tocheck):
    "Return True if the control has extended style tocheck"
    hwnd_exstyle = exstyle(handle)
    return tocheck & hwnd_exstyle == tocheck

#=========================================================================
def is_toplevel_window(handle):
    "Return whether the window is a top level window or not"

    # I don't like this so I have commented it out
    #if classname(handle) in ['tooltips_class32']:
    #    return False

    if (has_style(handle, win32defines.WS_OVERLAPPED) or \
        has_style(handle, win32defines.WS_CAPTION)) and \
        not has_style(handle, win32defines.WS_CHILD):
        return True
    else:
        return False

def get_button_friendlyclassname(handle):
    "Return the friendly class name of a button control"

    # get the least significant bit
    style_lsb = style(handle) & 0xF

    # default to "Button"
    f_classname = "Button"

    if style_lsb == win32defines.BS_3STATE or \
        style_lsb == win32defines.BS_AUTO3STATE or \
        style_lsb == win32defines.BS_AUTOCHECKBOX or \
        style_lsb == win32defines.BS_CHECKBOX:
        f_classname = "CheckBox"

    elif style_lsb == win32defines.BS_RADIOBUTTON or \
        style_lsb == win32defines.BS_AUTORADIOBUTTON:
        f_classname = "RadioButton"

    elif style_lsb == win32defines.BS_GROUPBOX:
        f_classname = "GroupBox"

    if style(handle) & win32defines.BS_PUSHLIKE:
        f_classname = "Button"

    return f_classname

def friendlyclassname(handle):
    """Return the friendly class name of the window

    The friendly class name might be subjective, but it
    tries to be what a normal user would call a window
    rather then the windows class name for the window.
    """

    # if it's a dialog then return that
    if is_toplevel_window(handle):
        return "Dialog"

    class_name = classname(handle)

    from controls import wraphandle
    info = wraphandle._find_wrapper(class_name)

    if info:
        return info.friendlyclassname

    else:
        return class_name
#
#
#    # Check if the class name is in the known classes
#    for cls_name, f_cls_name in _class_names.items():
#
#        # OK we found it
#        if re.match(cls_name, classname(handle)):
#            # If it is a string then just return it
#            if isinstance(f_cls_name, basestring):
#                return f_cls_name
#            # otherwise it is a function so call it
#            else:
#                return f_cls_name(handle)
#
#    # unknown class - just return it's classname
#    return classname(handle)

#=========================================================================
def dumpwindow(handle):
    "Dump a window to a set of properties"
    props = {}

    for func in (
        text,
        classname,
        rectangle,
        clientrect,
        style,
        exstyle,
        contexthelpid,
        controlid,
        userdata,
        font,
        parent,
        processid,
        isenabled,
        isunicode,
        isvisible,
        children,
        ):

        props[func.__name__] = func(handle)

    return props

#=========================================================================
def _unittests():
    "Quick test of module"
    handle = win32functions.GetDesktopWindow()

    for name, value in dumpwindow(handle).items():
        print "%15s\t%s" % (name, value)

if __name__ == "__main__":
    _unittests()
<commit_msg>Added an iswindow() function<commit_after># GUI Application automation and testing library
# Copyright (C) 2006 Mark Mc Mahon
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public License
# as published by the Free Software Foundation; either version 2.1
# of the License, or (at your option) any later version.
#
# This library 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
#    Free Software Foundation, Inc.,
#    59 Temple Place,
#    Suite 330,
#    Boston, MA 02111-1307 USA

"""Functions to retrieve properties from a window handle

These are implemented in a procedural way so as to to be
useful to other modules with the least conceptual overhead
"""

__revision__ = "$Revision$"

import ctypes

import win32functions
import win32defines
import win32structures

import findwindows # for children

#=========================================================================
def text(handle):
    "Return the text of the window"
    length = win32functions.GetWindowTextLength(handle,)

    textval = ''
    if length:
        length += 1

        buffer_ = (ctypes.c_wchar * length)()

        ret =  win32functions.GetWindowText(
            handle, ctypes.byref(buffer_), length)

        if ret:
            textval = buffer_.value

    return textval

#=========================================================================
def classname(handle):
    "Return the class name of the window"
    class_name = (ctypes.c_wchar * 257)()
    win32functions.GetClassName (handle, ctypes.byref(class_name), 256)
    return class_name.value

#=========================================================================
def parent(handle):
    "Return the handle of the parent of the window"
    return win32functions.GetParent(handle)

#=========================================================================
def style(handle):
    "Return the style of the window"
    return win32functions.GetWindowLong (handle, win32defines.GWL_STYLE)

#=========================================================================
def exstyle(handle):
    "Return the extended style of the window"
    return win32functions.GetWindowLong (handle, win32defines.GWL_EXSTYLE)

#=========================================================================
def controlid(handle):
    "Return the ID of the control"
    return win32functions.GetWindowLong (handle, win32defines.GWL_ID)

#=========================================================================
def userdata(handle):
    "Return the value of any userdata associated with the window"
    return win32functions.GetWindowLong (handle, win32defines.GWL_USERDATA)

#=========================================================================
def contexthelpid(handle):
    "Return the context help id of the window"
    return win32functions.GetWindowContextHelpId (handle)

#=========================================================================
def iswindow(handle):
    "Return True if the handle is a window"
    return bool(win32functions.IsWindow(handle))

#=========================================================================
def isvisible(handle):
    "Return True if the window is visible"
    return bool(win32functions.IsWindowVisible(handle))

#=========================================================================
def isunicode(handle):
    "Teturn True if the window is a unicode window"
    return bool(win32functions.IsWindowUnicode(handle))

#=========================================================================
def isenabled(handle):
    "Return True if the window is enabled"
    return bool(win32functions.IsWindowEnabled(handle))

#=========================================================================
def clientrect(handle):
    "Return the client rectangle of the control"
    client_rect = win32structures.RECT()
    win32functions.GetClientRect(handle, ctypes.byref(client_rect))
    return client_rect

#=========================================================================
def rectangle(handle):
    "Return the rectangle of the window"
    rect = win32structures.RECT()
    win32functions.GetWindowRect(handle, ctypes.byref(rect))
    return rect

#=========================================================================
def font(handle):
    "Return the font as a LOGFONTW of the window"

    # get the font handle
    font_handle = win32functions.SendMessage(
        handle, win32defines.WM_GETFONT, 0, 0)

    # if the fondUsed is 0 then the control is using the
    # system font
    if not font_handle:
        font_handle = win32functions.GetStockObject(win32defines.SYSTEM_FONT)

    # Get the Logfont structure of the font of the control
    fontval = win32structures.LOGFONTW()
    ret = win32functions.GetObject(
        font_handle, ctypes.sizeof(fontval), ctypes.byref(fontval))

    # The function could not get the font - this is probably
    # because the control does not have associated Font/Text
    # So we should make sure the elements of the font are zeroed.
    if not ret:
        fontval = win32structures.LOGFONTW()

    # if it is a main window
    if (has_style(handle, win32defines.WS_OVERLAPPED) or \
        has_style(handle, win32defines.WS_CAPTION)) and \
        not has_style(handle, win32defines.WS_CHILD):

        if "MS Shell Dlg" in fontval.lfFaceName or \
            fontval.lfFaceName == "System":
            # these are not usually the fonts actaully used in for
            # title bars so we need to get the default title bar font

            # get the title font based on the system metrics rather
            # than the font of the control itself
            ncms = win32structures.NONCLIENTMETRICSW()
            ncms.cbSize = ctypes.sizeof(ncms)
            win32functions.SystemParametersInfo(
                win32defines.SPI_GETNONCLIENTMETRICS,
                ctypes.sizeof(ncms),
                ctypes.byref(ncms),
                0)

            # with either of the following 2 flags set the font of the
            # dialog isthe small one (but there is normally no difference!
            if has_style(handle, win32defines.WS_EX_TOOLWINDOW) or \
               has_style(handle, win32defines.WS_EX_PALETTEWINDOW):

                fontval = ncms.lfSmCaptionFont
            else:
                fontval = ncms.lfCaptionFont

    return fontval

#=========================================================================
def processid(handle):
    "Retrun the ID of process that controls this window"
    process_id = ctypes.c_int()
    win32functions.GetWindowThreadProcessId(handle, ctypes.byref(process_id))

    return process_id.value

#=========================================================================
def children(handle):
    "Return a list of handles to the children of this window"
    return findwindows.enum_child_windows(handle)

#=========================================================================
def has_style(handle, tocheck):
    "Return True if the control has style tocheck"
    hwnd_style = style(handle)
    return tocheck & hwnd_style == tocheck

#=========================================================================
def has_exstyle(handle, tocheck):
    "Return True if the control has extended style tocheck"
    hwnd_exstyle = exstyle(handle)
    return tocheck & hwnd_exstyle == tocheck

#=========================================================================
def is_toplevel_window(handle):
    "Return whether the window is a top level window or not"

    # I don't like this so I have commented it out
    #if classname(handle) in ['tooltips_class32']:
    #    return False

    if (has_style(handle, win32defines.WS_OVERLAPPED) or \
        has_style(handle, win32defines.WS_CAPTION)) and \
        not has_style(handle, win32defines.WS_CHILD):
        return True
    else:
        return False

def get_button_friendlyclassname(handle):
    "Return the friendly class name of a button control"

    # get the least significant bit
    style_lsb = style(handle) & 0xF

    # default to "Button"
    f_classname = "Button"

    if style_lsb == win32defines.BS_3STATE or \
        style_lsb == win32defines.BS_AUTO3STATE or \
        style_lsb == win32defines.BS_AUTOCHECKBOX or \
        style_lsb == win32defines.BS_CHECKBOX:
        f_classname = "CheckBox"

    elif style_lsb == win32defines.BS_RADIOBUTTON or \
        style_lsb == win32defines.BS_AUTORADIOBUTTON:
        f_classname = "RadioButton"

    elif style_lsb == win32defines.BS_GROUPBOX:
        f_classname = "GroupBox"

    if style(handle) & win32defines.BS_PUSHLIKE:
        f_classname = "Button"

    return f_classname

def friendlyclassname(handle):
    """Return the friendly class name of the window

    The friendly class name might be subjective, but it
    tries to be what a normal user would call a window
    rather then the windows class name for the window.
    """

    # if it's a dialog then return that
    if is_toplevel_window(handle):
        return "Dialog"

    class_name = classname(handle)

    from controls import wraphandle
    info = wraphandle._find_wrapper(class_name)

    if info:
        return info.friendlyclassname

    else:
        return class_name
#
#
#    # Check if the class name is in the known classes
#    for cls_name, f_cls_name in _class_names.items():
#
#        # OK we found it
#        if re.match(cls_name, classname(handle)):
#            # If it is a string then just return it
#            if isinstance(f_cls_name, basestring):
#                return f_cls_name
#            # otherwise it is a function so call it
#            else:
#                return f_cls_name(handle)
#
#    # unknown class - just return it's classname
#    return classname(handle)

#=========================================================================
def dumpwindow(handle):
    "Dump a window to a set of properties"
    props = {}

    for func in (
        text,
        classname,
        rectangle,
        clientrect,
        style,
        exstyle,
        contexthelpid,
        controlid,
        userdata,
        font,
        parent,
        processid,
        isenabled,
        isunicode,
        isvisible,
        children,
        ):

        props[func.__name__] = func(handle)

    return props

#=========================================================================
def _unittests():
    "Quick test of module"
    handle = win32functions.GetDesktopWindow()

    for name, value in dumpwindow(handle).items():
        print "%15s\t%s" % (name, value)

if __name__ == "__main__":
    _unittests()
<|endoftext|>


<commit_before>"""
Files
-----

A module containing common Windows file functions.
"""

from collections import namedtuple

from six import integer_types

from pywincffi.core.ffi import Library
from pywincffi.core.checks import Enums, input_check, error_check, NoneType
from pywincffi.exceptions import WindowsAPIError

PeekNamedPipeResult = namedtuple(
    "PeekNamedPipeResult",
    ("lpBuffer", "lpBytesRead", "lpTotalBytesAvail",
     "lpBytesLeftThisMessage")
)

def CreatePipe(nSize=0, lpPipeAttributes=None):
    """
    Creates an anonymous pipe and returns the read and write handles.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365152
        https://msdn.microsoft.com/en-us/library/aa379560

    >>> from pywincffi.core.ffi import Library
    >>> ffi, library = Library.load()
    >>> lpPipeAttributes = ffi.new(
    ...     "SECURITY_ATTRIBUTES[1]", [{
    ...     "nLength": ffi.sizeof("SECURITY_ATTRIBUTES"),
    ...     "bInheritHandle": True,
    ...     "lpSecurityDescriptor": ffi.NULL
    ...     }]
    ... )
    >>> reader, writer = CreatePipe(lpPipeAttributes=lpPipeAttributes)

    :keyword int nSize:
        The size of the buffer in bytes.  Passing in 0, which is the default
        will cause the system to use the default buffer size.

    :keyword lpPipeAttributes:
        The security attributes to apply to the handle. By default
        ``NULL`` will be passed in meaning then handle we create
        cannot be inherited.  For more detailed information see the links
        below.

    :return:
        Returns a tuple of handles containing the reader and writer
        ends of the pipe that was created.  The user of this function
        is responsible for calling CloseHandle at some point.
    """
    input_check("nSize", nSize, integer_types)
    input_check("lpPipeAttributes", lpPipeAttributes, (NoneType, dict))
    ffi, library = Library.load()

    hReadPipe = ffi.new("PHANDLE")
    hWritePipe = ffi.new("PHANDLE")

    if lpPipeAttributes is None:
        lpPipeAttributes = ffi.NULL

    code = library.CreatePipe(hReadPipe, hWritePipe, lpPipeAttributes, nSize)
    error_check("CreatePipe", code=code, expected=Enums.NON_ZERO)

    return hReadPipe[0], hWritePipe[0]

def SetNamedPipeHandleState(
        hNamedPipe,
        lpMode=None, lpMaxCollectionCount=None, lpCollectDataTimeout=None):
    """
    Sets the read and blocking mode of the specified ``hNamedPipe``.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365787

    :param handle hNamedPipe:
        A handle to the named pipe instance.

    :keyword int lpMode:
        The new pipe mode which is a combination of read mode:

            * ``PIPE_READMODE_BYTE``
            * ``PIPE_READMODE_MESSAGE``

        And a wait-mode flag:

            * ``PIPE_WAIT``
            * ``PIPE_NOWAIT``

    :keyword int lpMaxCollectionCount:
        The maximum number of bytes collected.

    :keyword int lpCollectDataTimeout:
        The maximum time, in milliseconds, that can pass before a
        remote named pipe transfers information
    """
    input_check("hNamedPipe", hNamedPipe, Enums.HANDLE)
    ffi, library = Library.load()

    if lpMode is None:
        lpMode = ffi.NULL
    else:
        input_check("lpMode", lpMode, integer_types)
        lpMode = ffi.new("LPDWORD", lpMode)

    if lpMaxCollectionCount is None:
        lpMaxCollectionCount = ffi.NULL
    else:
        input_check("lpMaxCollectionCount", lpMaxCollectionCount, integer_types)
        lpMaxCollectionCount = ffi.new("LPDWORD", lpMaxCollectionCount)

    if lpCollectDataTimeout is None:
        lpCollectDataTimeout = ffi.NULL
    else:
        input_check("lpCollectDataTimeout", lpCollectDataTimeout, integer_types)
        lpCollectDataTimeout = ffi.new("LPDWORD", lpCollectDataTimeout)

    code = library.SetNamedPipeHandleState(
        hNamedPipe,
        lpMode,
        lpMaxCollectionCount,
        lpCollectDataTimeout
    )
    error_check("SetNamedPipeHandleState", code=code, expected=Enums.NON_ZERO)

def PeekNamedPipe(hNamedPipe, nBufferSize):
    """
    Copies data from a pipe into a buffer without removing it
    from the pipe.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365779

    :param handle hNamedPipe:
        The handele to the pipe object we want to peek into.

    :param int nBufferSize:
        The number of bytes to 'peek' into the pipe.

    :rtype: :class:`PeekNamedPipeResult`
    :return:
        Returns an instance of :class:`PeekNamedPipeResult` which
        contains the buffer read, number of bytes read and the result.
    """
    input_check("hNamedPipe", hNamedPipe, Enums.HANDLE)
    input_check("nBufferSize", nBufferSize, integer_types)
    ffi, library = Library.load()

    # Outputs
    lpBuffer = ffi.new("LPVOID[%d]" % nBufferSize)
    lpBytesRead = ffi.new("LPDWORD")
    lpTotalBytesAvail = ffi.new("LPDWORD")
    lpBytesLeftThisMessage = ffi.new("LPDWORD")

    code = library.PeekNamedPipe(
        hNamedPipe,
        lpBuffer,
        nBufferSize,
        lpBytesRead,
        lpTotalBytesAvail,
        lpBytesLeftThisMessage
    )
    error_check("PeekNamedPipe", code=code, expected=Enums.NON_ZERO)

    return PeekNamedPipeResult(
        lpBuffer=lpBuffer,
        lpBytesRead=lpBytesRead[0],
        lpTotalBytesAvail=lpTotalBytesAvail[0],
        lpBytesLeftThisMessage=lpBytesLeftThisMessage[0]
    )

def WriteFile(hFile, lpBuffer, lpOverlapped=None):
    """
    Writes data to ``hFile`` which may be an I/O device for file.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365747

    :param handle hFile:
        The handle to write to

    :type lpBuffer: bytes, string or unicode.
    :param lpBuffer:
        The data to be written to the file or device. We should be able
        to convert this value to unicode.

    :type lpOverlapped: None or OVERLAPPED
    :param lpOverlapped:
        None or a pointer to a ``OVERLAPPED`` structure.  See Microsoft's
        documentation for intended usage and below for an example of this
        struct.

        >>> from pywincffi.core.ffi import ffi
        >>> reader = None # normally, this would be a handle
        >>> struct = ffi.new(
        ...     "OVERLAPPED[1]", [{
        ...         "hEvent": reader
        ...     }]
        ... )

    :returns:
        Returns the number of bytes written
    """
    ffi, library = Library.load()

    if lpOverlapped is None:
        lpOverlapped = ffi.NULL

    input_check("hFile", hFile, Enums.HANDLE)
    input_check("lpBuffer", lpBuffer, Enums.UTF8)
    input_check("lpOverlapped", lpOverlapped, Enums.OVERLAPPED)

    # Prepare string and outputs
    nNumberOfBytesToWrite = len(lpBuffer)
    lpBuffer = ffi.new("wchar_t[%d]" % nNumberOfBytesToWrite, lpBuffer)
    bytes_written = ffi.new("LPDWORD")

    code = library.WriteFile(
        hFile, lpBuffer, ffi.sizeof(lpBuffer), bytes_written, lpOverlapped)
    error_check("WriteFile", code=code, expected=Enums.NON_ZERO)

    return bytes_written[0]

def ReadFile(hFile, nNumberOfBytesToRead, lpOverlapped=None):
    """
    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365467

    :param handle hFile:
        The handle to read from

    :param int nNumberOfBytesToRead:
        The number of bytes to read from ``hFile``

    :type lpOverlapped: None or OVERLAPPED
    :param lpOverlapped:
        None or a pointer to a ``OVERLAPPED`` structure.  See Microsoft's
        documentation for intended usage and below for an example of this
        struct.

        >>> from pywincffi.core.ffi import Library
        >>> ffi, library = Library.load()
        >>> reader = None # normally, this would be a handle
        >>> struct = ffi.new(
        ...     "OVERLAPPED[1]", [{
        ...         "hEvent": reader
        ...     }]
        ... )

    :returns:
        Returns the data read from ``hFile``
    """
    ffi, library = Library.load()

    if lpOverlapped is None:
        lpOverlapped = ffi.NULL

    input_check("hFile", hFile, Enums.HANDLE)
    input_check("nNumberOfBytesToRead", nNumberOfBytesToRead, integer_types)
    input_check("lpOverlapped", lpOverlapped, Enums.OVERLAPPED)

    lpBuffer = ffi.new("wchar_t[%d]" % nNumberOfBytesToRead)
    bytes_read = ffi.new("LPDWORD")
    code = library.ReadFile(
        hFile, lpBuffer, ffi.sizeof(lpBuffer), bytes_read, lpOverlapped
    )
    error_check("ReadFile", code=code, expected=Enums.NON_ZERO)
    return ffi.string(lpBuffer)

def CloseHandle(hObject):
    """
    Closes an open object handle.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/ms724211

    :param handle hObject:
        The handle object to close.
    """
    input_check("hObject", hObject, Enums.HANDLE)
    ffi, library = Library.load()

    code = library.CloseHandle(hObject)
    error_check("CloseHandle", code=code, expected=Enums.NON_ZERO)

def GetStdHandle(nStdHandle):
    """
    Retrieves a handle to the specified standard
    device (standard input, standard output, or standard error).

    .. seealso::

        https://msdn.microsoft.com/en-us/library/ms683231

    :param int nStdHandle:
        The standard device to retrieve

    :rtype: handle
    :return:
        Returns a handle to the standard device retrieved.
    """
    ffi, library = Library.load()
    input_check("nStdHandle", nStdHandle,
                allowed_values=(library.STD_INPUT_HANDLE,
                                library.STD_OUTPUT_HANDLE,
                                library.STD_ERROR_HANDLE))

    handle = library.GetStdHandle(nStdHandle)

    if handle == library.INVALID_HANDLE_VALUE:  # pragma: no cover
        raise WindowsAPIError(
            "GetStdHandle", "Invalid Handle", library.INVALID_HANDLE_VALUE,
            "not %s" % library.INVALID_HANDLE_VALUE)

    return handle
<commit_msg>fixed example for WriteFile()<commit_after>"""
Files
-----

A module containing common Windows file functions.
"""

from collections import namedtuple

from six import integer_types

from pywincffi.core.ffi import Library
from pywincffi.core.checks import Enums, input_check, error_check, NoneType
from pywincffi.exceptions import WindowsAPIError

PeekNamedPipeResult = namedtuple(
    "PeekNamedPipeResult",
    ("lpBuffer", "lpBytesRead", "lpTotalBytesAvail",
     "lpBytesLeftThisMessage")
)

def CreatePipe(nSize=0, lpPipeAttributes=None):
    """
    Creates an anonymous pipe and returns the read and write handles.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365152
        https://msdn.microsoft.com/en-us/library/aa379560

    >>> from pywincffi.core.ffi import Library
    >>> ffi, library = Library.load()
    >>> lpPipeAttributes = ffi.new(
    ...     "SECURITY_ATTRIBUTES[1]", [{
    ...     "nLength": ffi.sizeof("SECURITY_ATTRIBUTES"),
    ...     "bInheritHandle": True,
    ...     "lpSecurityDescriptor": ffi.NULL
    ...     }]
    ... )
    >>> reader, writer = CreatePipe(lpPipeAttributes=lpPipeAttributes)

    :keyword int nSize:
        The size of the buffer in bytes.  Passing in 0, which is the default
        will cause the system to use the default buffer size.

    :keyword lpPipeAttributes:
        The security attributes to apply to the handle. By default
        ``NULL`` will be passed in meaning then handle we create
        cannot be inherited.  For more detailed information see the links
        below.

    :return:
        Returns a tuple of handles containing the reader and writer
        ends of the pipe that was created.  The user of this function
        is responsible for calling CloseHandle at some point.
    """
    input_check("nSize", nSize, integer_types)
    input_check("lpPipeAttributes", lpPipeAttributes, (NoneType, dict))
    ffi, library = Library.load()

    hReadPipe = ffi.new("PHANDLE")
    hWritePipe = ffi.new("PHANDLE")

    if lpPipeAttributes is None:
        lpPipeAttributes = ffi.NULL

    code = library.CreatePipe(hReadPipe, hWritePipe, lpPipeAttributes, nSize)
    error_check("CreatePipe", code=code, expected=Enums.NON_ZERO)

    return hReadPipe[0], hWritePipe[0]

def SetNamedPipeHandleState(
        hNamedPipe,
        lpMode=None, lpMaxCollectionCount=None, lpCollectDataTimeout=None):
    """
    Sets the read and blocking mode of the specified ``hNamedPipe``.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365787

    :param handle hNamedPipe:
        A handle to the named pipe instance.

    :keyword int lpMode:
        The new pipe mode which is a combination of read mode:

            * ``PIPE_READMODE_BYTE``
            * ``PIPE_READMODE_MESSAGE``

        And a wait-mode flag:

            * ``PIPE_WAIT``
            * ``PIPE_NOWAIT``

    :keyword int lpMaxCollectionCount:
        The maximum number of bytes collected.

    :keyword int lpCollectDataTimeout:
        The maximum time, in milliseconds, that can pass before a
        remote named pipe transfers information
    """
    input_check("hNamedPipe", hNamedPipe, Enums.HANDLE)
    ffi, library = Library.load()

    if lpMode is None:
        lpMode = ffi.NULL
    else:
        input_check("lpMode", lpMode, integer_types)
        lpMode = ffi.new("LPDWORD", lpMode)

    if lpMaxCollectionCount is None:
        lpMaxCollectionCount = ffi.NULL
    else:
        input_check("lpMaxCollectionCount", lpMaxCollectionCount, integer_types)
        lpMaxCollectionCount = ffi.new("LPDWORD", lpMaxCollectionCount)

    if lpCollectDataTimeout is None:
        lpCollectDataTimeout = ffi.NULL
    else:
        input_check("lpCollectDataTimeout", lpCollectDataTimeout, integer_types)
        lpCollectDataTimeout = ffi.new("LPDWORD", lpCollectDataTimeout)

    code = library.SetNamedPipeHandleState(
        hNamedPipe,
        lpMode,
        lpMaxCollectionCount,
        lpCollectDataTimeout
    )
    error_check("SetNamedPipeHandleState", code=code, expected=Enums.NON_ZERO)

def PeekNamedPipe(hNamedPipe, nBufferSize):
    """
    Copies data from a pipe into a buffer without removing it
    from the pipe.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365779

    :param handle hNamedPipe:
        The handele to the pipe object we want to peek into.

    :param int nBufferSize:
        The number of bytes to 'peek' into the pipe.

    :rtype: :class:`PeekNamedPipeResult`
    :return:
        Returns an instance of :class:`PeekNamedPipeResult` which
        contains the buffer read, number of bytes read and the result.
    """
    input_check("hNamedPipe", hNamedPipe, Enums.HANDLE)
    input_check("nBufferSize", nBufferSize, integer_types)
    ffi, library = Library.load()

    # Outputs
    lpBuffer = ffi.new("LPVOID[%d]" % nBufferSize)
    lpBytesRead = ffi.new("LPDWORD")
    lpTotalBytesAvail = ffi.new("LPDWORD")
    lpBytesLeftThisMessage = ffi.new("LPDWORD")

    code = library.PeekNamedPipe(
        hNamedPipe,
        lpBuffer,
        nBufferSize,
        lpBytesRead,
        lpTotalBytesAvail,
        lpBytesLeftThisMessage
    )
    error_check("PeekNamedPipe", code=code, expected=Enums.NON_ZERO)

    return PeekNamedPipeResult(
        lpBuffer=lpBuffer,
        lpBytesRead=lpBytesRead[0],
        lpTotalBytesAvail=lpTotalBytesAvail[0],
        lpBytesLeftThisMessage=lpBytesLeftThisMessage[0]
    )

def WriteFile(hFile, lpBuffer, lpOverlapped=None):
    """
    Writes data to ``hFile`` which may be an I/O device for file.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365747

    :param handle hFile:
        The handle to write to

    :type lpBuffer: bytes, string or unicode.
    :param lpBuffer:
        The data to be written to the file or device. We should be able
        to convert this value to unicode.

    :type lpOverlapped: None or OVERLAPPED
    :param lpOverlapped:
        None or a pointer to a ``OVERLAPPED`` structure.  See Microsoft's
        documentation for intended usage and below for an example of this
        struct.

        >>> from pywincffi.core.ffi import Library
        >>> ffi, library = Library.load()
        >>> hFile = None # normally, this would be a handle
        >>> lpOverlapped = ffi.new(
        ...     "OVERLAPPED[1]", [{
        ...         "hEvent": hFile
        ...     }]
        ... )
        >>> bytes_written = WriteFile(
        ...     hFile, "Hello world", lpOverlapped=lpOverlapped)

    :returns:
        Returns the number of bytes written
    """
    ffi, library = Library.load()

    if lpOverlapped is None:
        lpOverlapped = ffi.NULL

    input_check("hFile", hFile, Enums.HANDLE)
    input_check("lpBuffer", lpBuffer, Enums.UTF8)
    input_check("lpOverlapped", lpOverlapped, Enums.OVERLAPPED)

    # Prepare string and outputs
    nNumberOfBytesToWrite = len(lpBuffer)
    lpBuffer = ffi.new("wchar_t[%d]" % nNumberOfBytesToWrite, lpBuffer)
    bytes_written = ffi.new("LPDWORD")

    code = library.WriteFile(
        hFile, lpBuffer, ffi.sizeof(lpBuffer), bytes_written, lpOverlapped)
    error_check("WriteFile", code=code, expected=Enums.NON_ZERO)

    return bytes_written[0]

def ReadFile(hFile, nNumberOfBytesToRead, lpOverlapped=None):
    """
    .. seealso::

        https://msdn.microsoft.com/en-us/library/aa365467

    :param handle hFile:
        The handle to read from

    :param int nNumberOfBytesToRead:
        The number of bytes to read from ``hFile``

    :type lpOverlapped: None or OVERLAPPED
    :param lpOverlapped:
        None or a pointer to a ``OVERLAPPED`` structure.  See Microsoft's
        documentation for intended usage and below for an example of this
        struct.

        >>> from pywincffi.core.ffi import Library
        >>> ffi, library = Library.load()
        >>> reader = None # normally, this would be a handle
        >>> struct = ffi.new(
        ...     "OVERLAPPED[1]", [{
        ...         "hEvent": reader
        ...     }]
        ... )

    :returns:
        Returns the data read from ``hFile``
    """
    ffi, library = Library.load()

    if lpOverlapped is None:
        lpOverlapped = ffi.NULL

    input_check("hFile", hFile, Enums.HANDLE)
    input_check("nNumberOfBytesToRead", nNumberOfBytesToRead, integer_types)
    input_check("lpOverlapped", lpOverlapped, Enums.OVERLAPPED)

    lpBuffer = ffi.new("wchar_t[%d]" % nNumberOfBytesToRead)
    bytes_read = ffi.new("LPDWORD")
    code = library.ReadFile(
        hFile, lpBuffer, ffi.sizeof(lpBuffer), bytes_read, lpOverlapped
    )
    error_check("ReadFile", code=code, expected=Enums.NON_ZERO)
    return ffi.string(lpBuffer)

def CloseHandle(hObject):
    """
    Closes an open object handle.

    .. seealso::

        https://msdn.microsoft.com/en-us/library/ms724211

    :param handle hObject:
        The handle object to close.
    """
    input_check("hObject", hObject, Enums.HANDLE)
    ffi, library = Library.load()

    code = library.CloseHandle(hObject)
    error_check("CloseHandle", code=code, expected=Enums.NON_ZERO)

def GetStdHandle(nStdHandle):
    """
    Retrieves a handle to the specified standard
    device (standard input, standard output, or standard error).

    .. seealso::

        https://msdn.microsoft.com/en-us/library/ms683231

    :param int nStdHandle:
        The standard device to retrieve

    :rtype: handle
    :return:
        Returns a handle to the standard device retrieved.
    """
    ffi, library = Library.load()
    input_check("nStdHandle", nStdHandle,
                allowed_values=(library.STD_INPUT_HANDLE,
                                library.STD_OUTPUT_HANDLE,
                                library.STD_ERROR_HANDLE))

    handle = library.GetStdHandle(nStdHandle)

    if handle == library.INVALID_HANDLE_VALUE:  # pragma: no cover
        raise WindowsAPIError(
            "GetStdHandle", "Invalid Handle", library.INVALID_HANDLE_VALUE,
            "not %s" % library.INVALID_HANDLE_VALUE)

    return handle
<|endoftext|>


<commit_before>r"""

Iconic Font
===========

A lightweight module handling iconic fonts.

It is designed to provide a simple way for creating QIcons from glyphs.

From a user's viewpoint, the main entry point is the ``IconicFont`` class which
contains methods for loading new iconic fonts with their character map and
methods returning instances of ``QIcon``.

"""

# Standard library imports
from __future__ import print_function
import json
import os
import hashlib

# Third party imports
from qtpy.QtCore import QObject, QPoint, QRect, qRound, Qt
from qtpy.QtGui import (QColor, QFont, QFontDatabase, QIcon, QIconEngine,
                        QPainter, QPixmap)
from six import unichr

_default_options = {
    'color': QColor(50, 50, 50),
    'color_disabled': QColor(150, 150, 150),
    'opacity': 1.0,
    'scale_factor': 1.0,
}

def set_global_defaults(**kwargs):
    """Set global defaults for the options passed to the icon painter."""

    valid_options = [
        'active', 'selected', 'disabled', 'on', 'off',
        'on_active', 'on_selected', 'on_disabled',
        'off_active', 'off_selected', 'off_disabled',
        'color', 'color_on', 'color_off',
        'color_active', 'color_selected', 'color_disabled',
        'color_on_selected', 'color_on_active', 'color_on_disabled',
        'color_off_selected', 'color_off_active', 'color_off_disabled',
        'animation', 'offset', 'scale_factor',
        ]

    for kw in kwargs:
        if kw in valid_options:
            _default_options[kw] = kwargs[kw]
        else:
            error = "Invalid option '{0}'".format(kw)
            raise KeyError(error)

class CharIconPainter:

    """Char icon painter."""

    def paint(self, iconic, painter, rect, mode, state, options):
        """Main paint method."""
        for opt in options:
            self._paint_icon(iconic, painter, rect, mode, state, opt)

    def _paint_icon(self, iconic, painter, rect, mode, state, options):
        """Paint a single icon."""
        painter.save()
        color = options['color']
        char = options['char']

        color_options = {
            QIcon.On: {
                QIcon.Normal: (options['color_on'], options['on']),
                QIcon.Disabled: (options['color_on_disabled'],
                                 options['on_disabled']),
                QIcon.Active: (options['color_on_active'],
                               options['on_active']),
                QIcon.Selected: (options['color_on_selected'],
                                 options['on_selected'])
            },

            QIcon.Off: {
                QIcon.Normal: (options['color_off'], options['off']),
                QIcon.Disabled: (options['color_off_disabled'],
                                 options['off_disabled']),
                QIcon.Active: (options['color_off_active'],
                               options['off_active']),
                QIcon.Selected: (options['color_off_selected'],
                                 options['off_selected'])
            }
        }

        color, char = color_options[state][mode]

        painter.setPen(QColor(color))

        # A 16 pixel-high icon yields a font size of 14, which is pixel perfect
        # for font-awesome. 16 * 0.875 = 14
        # The reason why the glyph size is smaller than the icon size is to
        # account for font bearing.

        draw_size = 0.875 * qRound(rect.height() * options['scale_factor'])
        prefix = options['prefix']

        # Animation setup hook
        animation = options.get('animation')
        if animation is not None:
            animation.setup(self, painter, rect)

        painter.setFont(iconic.font(prefix, draw_size))
        if 'offset' in options:
            rect = QRect(rect)
            rect.translate(options['offset'][0] * rect.width(),
                           options['offset'][1] * rect.height())

        painter.setOpacity(options.get('opacity', 1.0))

        painter.drawText(rect, Qt.AlignCenter | Qt.AlignVCenter, char)
        painter.restore()

class FontError(Exception):
    """Exception for font errors."""

class CharIconEngine(QIconEngine):

    """Specialization of QIconEngine used to draw font-based icons."""

    def __init__(self, iconic, painter, options):
        super(CharIconEngine, self).__init__()
        self.iconic = iconic
        self.painter = painter
        self.options = options

    def paint(self, painter, rect, mode, state):
        self.painter.paint(
            self.iconic, painter, rect, mode, state, self.options)

    def pixmap(self, size, mode, state):
        pm = QPixmap(size)
        pm.fill(Qt.transparent)
        self.paint(QPainter(pm), QRect(QPoint(0, 0), size), mode, state)
        return pm

class IconicFont(QObject):

    """Main class for managing iconic fonts."""

    def __init__(self, *args):
        """IconicFont Constructor.

        Parameters
        ----------
        ``*args``: tuples
            Each positional argument is a tuple of 3 or 4 values:
            - The prefix string to be used when accessing a given font set,
            - The ttf font filename,
            - The json charmap filename,
            - Optionally, the directory containing these files. When not
              provided, the files will be looked for in ``./fonts/``.
        """
        super(IconicFont, self).__init__()
        self.painter = CharIconPainter()
        self.painters = {}
        self.fontname = {}
        self.charmap = {}
        for fargs in args:
            self.load_font(*fargs)

    def load_font(self, prefix, ttf_filename, charmap_filename, directory=None):
        """Loads a font file and the associated charmap.

        If ``directory`` is None, the files will be looked for in ``./fonts/``.

        Parameters
        ----------
        prefix: str
            Prefix string to be used when accessing a given font set
        ttf_filename: str
            Ttf font filename
        charmap_filename: str
            Charmap filename
        directory: str or None, optional
            Directory for font and charmap files
        """

        def hook(obj):
            result = {}
            for key in obj:
                result[key] = unichr(int(obj[key], 16))
            return result

        if directory is None:
            directory = os.path.join(
                os.path.dirname(os.path.realpath(__file__)), 'fonts')

        # Load font
        id_ = QFontDatabase.addApplicationFont(os.path.join(directory,
                                                            ttf_filename))
        loadedFontFamilies = QFontDatabase.applicationFontFamilies(id_)
        if(loadedFontFamilies):
            self.fontname[prefix] = loadedFontFamilies[0]
        else:
            raise FontError(u"Font at '{0}' appears to be empty. "
                            "If you are on Windows 10, please read "
                            "https://support.microsoft.com/en-us/kb/3053676 "
                            "to know how to prevent Windows from blocking "
                            "the fonts that come with QtAwesome.".format(
                            os.path.join(directory, ttf_filename)))

        # Verify that font is not corrupt
        with open(os.path.join(directory, charmap_filename), 'r') as codes:
            self.charmap[prefix] = json.load(codes, object_hook=hook)

        md5_hashes = {'fontawesome-webfont.ttf':
                      'a3de2170e4e9df77161ea5d3f31b2668',
                      'elusiveicons-webfont.ttf':
                      '207966b04c032d5b873fd595a211582e'}
        ttf_hash = md5_hashes.get(ttf_filename, None)
        if ttf_hash is not None:
            hasher = hashlib.md5()
            with open(os.path.join(directory, ttf_filename), 'rb') as tff_font:
                buffer = tff_font.read()
                hasher.update(buffer)
            ttf_calculated_hash_code = hasher.hexdigest()
            if ttf_calculated_hash_code != ttf_hash:
                raise FontError(u"Font is corrupt at: '{0}'".format(
                                os.path.join(directory, ttf_filename)))

    def icon(self, *names, **kwargs):
        """Return a QIcon object corresponding to the provided icon name."""
        options_list = kwargs.pop('options', [{}] * len(names))
        general_options = kwargs

        if len(options_list) != len(names):
            error = '"options" must be a list of size {0}'.format(len(names))
            raise Exception(error)

        parsed_options = []
        for i in range(len(options_list)):
            specific_options = options_list[i]
            parsed_options.append(self._parse_options(specific_options,
                                                      general_options,
                                                      names[i]))

        # Process high level API
        api_options = parsed_options

        return self._icon_by_painter(self.painter, api_options)

    def _parse_options(self, specific_options, general_options, name):
        options = dict(_default_options, **general_options)
        options.update(specific_options)

        # Handle icons for modes (Active, Disabled, Selected, Normal)
        # and states (On, Off)
        icon_kw = ['char', 'on', 'off', 'active', 'selected', 'disabled',
                   'on_active', 'on_selected', 'on_disabled', 'off_active',
                   'off_selected', 'off_disabled']
        char = options.get('char', name)
        on = options.get('on', char)
        off = options.get('off', char)
        active = options.get('active', on)
        selected = options.get('selected', active)
        disabled = options.get('disabled', char)
        on_active = options.get('on_active', active)
        on_selected = options.get('on_selected', selected)
        on_disabled = options.get('on_disabled', disabled)
        off_active = options.get('off_active', active)
        off_selected = options.get('off_selected', selected)
        off_disabled = options.get('off_disabled', disabled)

        icon_dict = {'char': char,
                     'on': on,
                     'off': off,
                     'active': active,
                     'selected': selected,
                     'disabled': disabled,
                     'on_active': on_active,
                     'on_selected': on_selected,
                     'on_disabled': on_disabled,
                     'off_active': off_active,
                     'off_selected': off_selected,
                     'off_disabled': off_disabled,
                     }
        names = [icon_dict.get(kw, name) for kw in icon_kw]
        prefix, chars = self._get_prefix_chars(names)
        options.update(dict(zip(*(icon_kw, chars))))
        options.update({'prefix': prefix})

        # Handle colors for modes (Active, Disabled, Selected, Normal)
        # and states (On, Off)
        color = options.get('color')
        options.setdefault('color_on', color)
        options.setdefault('color_active', options['color_on'])
        options.setdefault('color_selected', options['color_active'])
        options.setdefault('color_on_active', options['color_active'])
        options.setdefault('color_on_selected', options['color_selected'])
        options.setdefault('color_on_disabled', options['color_disabled'])
        options.setdefault('color_off', color)
        options.setdefault('color_off_active', options['color_active'])
        options.setdefault('color_off_selected', options['color_selected'])
        options.setdefault('color_off_disabled', options['color_disabled'])

        return options

    def _get_prefix_chars(self, names):
        chars = []
        for name in names:
            if '.' in name:
                prefix, n = name.split('.')
                if prefix in self.charmap:
                    if n in self.charmap[prefix]:
                        chars.append(self.charmap[prefix][n])
                    else:
                        error = 'Invalid icon name "{0}" in font "{1}"'.format(
                            n, prefix)
                        raise Exception(error)
                else:
                    error = 'Invalid font prefix "{0}"'.format(prefix)
                    raise Exception(error)
            else:
                raise Exception('Invalid icon name')

        return prefix, chars

    def font(self, prefix, size):
        """Return a QFont corresponding to the given prefix and size."""
        font = QFont(self.fontname[prefix])
        font.setPixelSize(size)
        return font

    def set_custom_icon(self, name, painter):
        """Associate a user-provided CharIconPainter to an icon name.

        The custom icon can later be addressed by calling
        icon('custom.NAME') where NAME is the provided name for that icon.

        Parameters
        ----------
        name: str
            name of the custom icon
        painter: CharIconPainter
            The icon painter, implementing
            ``paint(self, iconic, painter, rect, mode, state, options)``
        """
        self.painters[name] = painter

    def _custom_icon(self, name, **kwargs):
        """Return the custom icon corresponding to the given name."""
        options = dict(_default_options, **kwargs)
        if name in self.painters:
            painter = self.painters[name]
            return self._icon_by_painter(painter, options)
        else:
            return QIcon()

    def _icon_by_painter(self, painter, options):
        """Return the icon corresponding to the given painter."""
        engine = CharIconEngine(self, painter, options)
        return QIcon(engine)
<commit_msg>Add a way to not verify hash of vendorized fonts<commit_after>r"""

Iconic Font
===========

A lightweight module handling iconic fonts.

It is designed to provide a simple way for creating QIcons from glyphs.

From a user's viewpoint, the main entry point is the ``IconicFont`` class which
contains methods for loading new iconic fonts with their character map and
methods returning instances of ``QIcon``.

"""

# Standard library imports
from __future__ import print_function
import json
import os
import hashlib

# Third party imports
from qtpy.QtCore import QObject, QPoint, QRect, qRound, Qt
from qtpy.QtGui import (QColor, QFont, QFontDatabase, QIcon, QIconEngine,
                        QPainter, QPixmap)
from six import unichr

# Linux packagers, please set this to True if you want to make qtawesome
# use system fonts
SYSTEM_FONTS = False

_default_options = {
    'color': QColor(50, 50, 50),
    'color_disabled': QColor(150, 150, 150),
    'opacity': 1.0,
    'scale_factor': 1.0,
}

def set_global_defaults(**kwargs):
    """Set global defaults for the options passed to the icon painter."""

    valid_options = [
        'active', 'selected', 'disabled', 'on', 'off',
        'on_active', 'on_selected', 'on_disabled',
        'off_active', 'off_selected', 'off_disabled',
        'color', 'color_on', 'color_off',
        'color_active', 'color_selected', 'color_disabled',
        'color_on_selected', 'color_on_active', 'color_on_disabled',
        'color_off_selected', 'color_off_active', 'color_off_disabled',
        'animation', 'offset', 'scale_factor',
        ]

    for kw in kwargs:
        if kw in valid_options:
            _default_options[kw] = kwargs[kw]
        else:
            error = "Invalid option '{0}'".format(kw)
            raise KeyError(error)

class CharIconPainter:

    """Char icon painter."""

    def paint(self, iconic, painter, rect, mode, state, options):
        """Main paint method."""
        for opt in options:
            self._paint_icon(iconic, painter, rect, mode, state, opt)

    def _paint_icon(self, iconic, painter, rect, mode, state, options):
        """Paint a single icon."""
        painter.save()
        color = options['color']
        char = options['char']

        color_options = {
            QIcon.On: {
                QIcon.Normal: (options['color_on'], options['on']),
                QIcon.Disabled: (options['color_on_disabled'],
                                 options['on_disabled']),
                QIcon.Active: (options['color_on_active'],
                               options['on_active']),
                QIcon.Selected: (options['color_on_selected'],
                                 options['on_selected'])
            },

            QIcon.Off: {
                QIcon.Normal: (options['color_off'], options['off']),
                QIcon.Disabled: (options['color_off_disabled'],
                                 options['off_disabled']),
                QIcon.Active: (options['color_off_active'],
                               options['off_active']),
                QIcon.Selected: (options['color_off_selected'],
                                 options['off_selected'])
            }
        }

        color, char = color_options[state][mode]

        painter.setPen(QColor(color))

        # A 16 pixel-high icon yields a font size of 14, which is pixel perfect
        # for font-awesome. 16 * 0.875 = 14
        # The reason why the glyph size is smaller than the icon size is to
        # account for font bearing.

        draw_size = 0.875 * qRound(rect.height() * options['scale_factor'])
        prefix = options['prefix']

        # Animation setup hook
        animation = options.get('animation')
        if animation is not None:
            animation.setup(self, painter, rect)

        painter.setFont(iconic.font(prefix, draw_size))
        if 'offset' in options:
            rect = QRect(rect)
            rect.translate(options['offset'][0] * rect.width(),
                           options['offset'][1] * rect.height())

        painter.setOpacity(options.get('opacity', 1.0))

        painter.drawText(rect, Qt.AlignCenter | Qt.AlignVCenter, char)
        painter.restore()

class FontError(Exception):
    """Exception for font errors."""

class CharIconEngine(QIconEngine):

    """Specialization of QIconEngine used to draw font-based icons."""

    def __init__(self, iconic, painter, options):
        super(CharIconEngine, self).__init__()
        self.iconic = iconic
        self.painter = painter
        self.options = options

    def paint(self, painter, rect, mode, state):
        self.painter.paint(
            self.iconic, painter, rect, mode, state, self.options)

    def pixmap(self, size, mode, state):
        pm = QPixmap(size)
        pm.fill(Qt.transparent)
        self.paint(QPainter(pm), QRect(QPoint(0, 0), size), mode, state)
        return pm

class IconicFont(QObject):

    """Main class for managing iconic fonts."""

    def __init__(self, *args):
        """IconicFont Constructor.

        Parameters
        ----------
        ``*args``: tuples
            Each positional argument is a tuple of 3 or 4 values:
            - The prefix string to be used when accessing a given font set,
            - The ttf font filename,
            - The json charmap filename,
            - Optionally, the directory containing these files. When not
              provided, the files will be looked for in ``./fonts/``.
        """
        super(IconicFont, self).__init__()
        self.painter = CharIconPainter()
        self.painters = {}
        self.fontname = {}
        self.charmap = {}
        for fargs in args:
            self.load_font(*fargs)

    def load_font(self, prefix, ttf_filename, charmap_filename, directory=None):
        """Loads a font file and the associated charmap.

        If ``directory`` is None, the files will be looked for in ``./fonts/``.

        Parameters
        ----------
        prefix: str
            Prefix string to be used when accessing a given font set
        ttf_filename: str
            Ttf font filename
        charmap_filename: str
            Charmap filename
        directory: str or None, optional
            Directory for font and charmap files
        """

        def hook(obj):
            result = {}
            for key in obj:
                result[key] = unichr(int(obj[key], 16))
            return result

        if directory is None:
            directory = os.path.join(
                os.path.dirname(os.path.realpath(__file__)), 'fonts')

        # Load font
        id_ = QFontDatabase.addApplicationFont(os.path.join(directory,
                                                            ttf_filename))
        loadedFontFamilies = QFontDatabase.applicationFontFamilies(id_)
        if(loadedFontFamilies):
            self.fontname[prefix] = loadedFontFamilies[0]
        else:
            raise FontError(u"Font at '{0}' appears to be empty. "
                            "If you are on Windows 10, please read "
                            "https://support.microsoft.com/en-us/kb/3053676 "
                            "to know how to prevent Windows from blocking "
                            "the fonts that come with QtAwesome.".format(
                            os.path.join(directory, ttf_filename)))

        with open(os.path.join(directory, charmap_filename), 'r') as codes:
            self.charmap[prefix] = json.load(codes, object_hook=hook)

        # Verify that vendorized fonts are not corrupt
        if not SYSTEM_FONTS:
            md5_hashes = {'fontawesome-webfont.ttf':
                          'a3de2170e4e9df77161ea5d3f31b2668',
                          'elusiveicons-webfont.ttf':
                          '207966b04c032d5b873fd595a211582e'}
            ttf_hash = md5_hashes.get(ttf_filename, None)
            if ttf_hash is not None:
                hasher = hashlib.md5()
                with open(os.path.join(directory, ttf_filename), 'rb') as f:
                    content = f.read()
                    hasher.update(content)
                ttf_calculated_hash_code = hasher.hexdigest()
                if ttf_calculated_hash_code != ttf_hash:
                    raise FontError(u"Font is corrupt at: '{0}'".format(
                                    os.path.join(directory, ttf_filename)))

    def icon(self, *names, **kwargs):
        """Return a QIcon object corresponding to the provided icon name."""
        options_list = kwargs.pop('options', [{}] * len(names))
        general_options = kwargs

        if len(options_list) != len(names):
            error = '"options" must be a list of size {0}'.format(len(names))
            raise Exception(error)

        parsed_options = []
        for i in range(len(options_list)):
            specific_options = options_list[i]
            parsed_options.append(self._parse_options(specific_options,
                                                      general_options,
                                                      names[i]))

        # Process high level API
        api_options = parsed_options

        return self._icon_by_painter(self.painter, api_options)

    def _parse_options(self, specific_options, general_options, name):
        options = dict(_default_options, **general_options)
        options.update(specific_options)

        # Handle icons for modes (Active, Disabled, Selected, Normal)
        # and states (On, Off)
        icon_kw = ['char', 'on', 'off', 'active', 'selected', 'disabled',
                   'on_active', 'on_selected', 'on_disabled', 'off_active',
                   'off_selected', 'off_disabled']
        char = options.get('char', name)
        on = options.get('on', char)
        off = options.get('off', char)
        active = options.get('active', on)
        selected = options.get('selected', active)
        disabled = options.get('disabled', char)
        on_active = options.get('on_active', active)
        on_selected = options.get('on_selected', selected)
        on_disabled = options.get('on_disabled', disabled)
        off_active = options.get('off_active', active)
        off_selected = options.get('off_selected', selected)
        off_disabled = options.get('off_disabled', disabled)

        icon_dict = {'char': char,
                     'on': on,
                     'off': off,
                     'active': active,
                     'selected': selected,
                     'disabled': disabled,
                     'on_active': on_active,
                     'on_selected': on_selected,
                     'on_disabled': on_disabled,
                     'off_active': off_active,
                     'off_selected': off_selected,
                     'off_disabled': off_disabled,
                     }
        names = [icon_dict.get(kw, name) for kw in icon_kw]
        prefix, chars = self._get_prefix_chars(names)
        options.update(dict(zip(*(icon_kw, chars))))
        options.update({'prefix': prefix})

        # Handle colors for modes (Active, Disabled, Selected, Normal)
        # and states (On, Off)
        color = options.get('color')
        options.setdefault('color_on', color)
        options.setdefault('color_active', options['color_on'])
        options.setdefault('color_selected', options['color_active'])
        options.setdefault('color_on_active', options['color_active'])
        options.setdefault('color_on_selected', options['color_selected'])
        options.setdefault('color_on_disabled', options['color_disabled'])
        options.setdefault('color_off', color)
        options.setdefault('color_off_active', options['color_active'])
        options.setdefault('color_off_selected', options['color_selected'])
        options.setdefault('color_off_disabled', options['color_disabled'])

        return options

    def _get_prefix_chars(self, names):
        chars = []
        for name in names:
            if '.' in name:
                prefix, n = name.split('.')
                if prefix in self.charmap:
                    if n in self.charmap[prefix]:
                        chars.append(self.charmap[prefix][n])
                    else:
                        error = 'Invalid icon name "{0}" in font "{1}"'.format(
                            n, prefix)
                        raise Exception(error)
                else:
                    error = 'Invalid font prefix "{0}"'.format(prefix)
                    raise Exception(error)
            else:
                raise Exception('Invalid icon name')

        return prefix, chars

    def font(self, prefix, size):
        """Return a QFont corresponding to the given prefix and size."""
        font = QFont(self.fontname[prefix])
        font.setPixelSize(size)
        return font

    def set_custom_icon(self, name, painter):
        """Associate a user-provided CharIconPainter to an icon name.

        The custom icon can later be addressed by calling
        icon('custom.NAME') where NAME is the provided name for that icon.

        Parameters
        ----------
        name: str
            name of the custom icon
        painter: CharIconPainter
            The icon painter, implementing
            ``paint(self, iconic, painter, rect, mode, state, options)``
        """
        self.painters[name] = painter

    def _custom_icon(self, name, **kwargs):
        """Return the custom icon corresponding to the given name."""
        options = dict(_default_options, **kwargs)
        if name in self.painters:
            painter = self.painters[name]
            return self._icon_by_painter(painter, options)
        else:
            return QIcon()

    def _icon_by_painter(self, painter, options):
        """Return the icon corresponding to the given painter."""
        engine = CharIconEngine(self, painter, options)
        return QIcon(engine)
<|endoftext|>