def run(self):
    # check if current active layer is a polygon layer:
    layer =  self.iface.activeLayer()
    if layer == None:
        QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("No active layer found\n" "Please make one (multi)polygon or point layer active\n" "by choosing a layer in the legend"), QMessageBox.Ok, QMessageBox.Ok)
        return
    # don't know if this is possible / needed
    if not layer.isValid():
        QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("No VALID layer found\n" "Please make one (multi)polygon or point layer active\n" "by choosing a layer in the legend"), QMessageBox.Ok, QMessageBox.Ok)
        return
    if (layer.type()>0): # 0 = vector, 1 = raster
        QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("Wrong layer type, only vector layers can be used..\n" "Please make one vector layer active\n" "by choosing a vector layer in the legend"), QMessageBox.Ok, QMessageBox.Ok)
        return
    self.provider = layer.dataProvider()
    if not(self.provider.geometryType() == QGis.WKBPolygon or self.provider.geometryType() == QGis.WKBMultiPolygon or self.provider.geometryType() == QGis.WKBPoint):
        QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("Wrong geometrytype, only (multi)polygons and points can be used.\n" "Please make one (multi)polygon or point layer active\n" "by choosing a layer in the legend"), QMessageBox.Ok, QMessageBox.Ok)
        return

    # we need the fields of the active layer to show in the attribute combobox in the gui:
    attrFields = []

    fields = self.provider.fields()
    if hasattr(fields, 'iteritems'):
      for (i, field) in fields.iteritems():
        attrFields.append(field.name().trimmed())
    else:
        for field in self.provider.fields():
          attrFields.append(field.name().strip())

    # construct gui (using these fields)
    flags = Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMaximizeButtonHint  # QgisGui.ModalDialogFlags
    # construct gui: if available reuse this one
    if hasattr(self, 'imageMapPlugin') == False:
        self.imageMapPluginGui = ImageMapPluginGui(self.iface.mainWindow(), flags)
    self.imageMapPluginGui.setAttributeFields(attrFields)
    self.imageMapPluginGui.setMapCanvasSize(self.iface.mapCanvas().width(), self.iface.mapCanvas().height())
    self.layerAttr = attrFields
    self.selectedFeaturesOnly = False # default all features in current Extent
    # catch SIGNAL's
    QObject.connect(self.imageMapPluginGui, SIGNAL("getFilesPath(QString)"), self.setFilesPath)
    QObject.connect(self.imageMapPluginGui, SIGNAL("onHrefAttributeSet(QString)"), self.onHrefAttributeFieldSet)
    QObject.connect(self.imageMapPluginGui, SIGNAL("onClickAttributeSet(QString)"), self.onClickAttributeFieldSet)
    QObject.connect(self.imageMapPluginGui, SIGNAL("onMouseOverAttributeSet(QString)"), self.onMouseOverAttributeFieldSet)
    QObject.connect(self.imageMapPluginGui, SIGNAL("onMouseOutAttributeSet(QString)"), self.onMouseOutAttributeFieldSet)
    QObject.connect(self.imageMapPluginGui, SIGNAL("getCbkBoxSelectedOnly(bool)"), self.setSelectedOnly)
    QObject.connect(self.imageMapPluginGui, SIGNAL("go(QString)"), self.go)
    QObject.connect(self.imageMapPluginGui, SIGNAL("setMapCanvasSize(int, int)"), self.setMapCanvasSize)
    # remember old path's in this session:
    self.imageMapPluginGui.setFilesPath(self.filesPath)
    self.imageMapPluginGui.show()
class ImageMapPlugin:

  MSG_BOX_TITLE = "QGis Html Image Map Plugin "

  def __init__(self, iface):
    # save reference to the QGIS interface
    self.iface = iface
    self.filesPath = "/tmp/foo"


  def initGui(self):
    # create action that will start plugin configuration
    self.action = QAction(QIcon(":/imagemapicon.xpm"), "Image Map", self.iface.mainWindow())
    self.action.setWhatsThis("Configuration for Image Map plugin")
    QObject.connect(self.action, SIGNAL("triggered()"), self.run)
    # add toolbar button and menu item
    self.iface.addToolBarIcon(self.action)
    if hasattr ( self.iface, "addPluginToWebMenu" ):
        self.iface.addPluginToWebMenu("&Html Image Map Plugin", self.action)
    else:
        self.iface.addPluginToMenu("&Html Image Map Plugin", self.action)

    #self.iface.pluginMenu().insertAction(self.action)
    # connect to signal renderComplete which is emitted when canvas rendering is done
    QObject.connect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"), self.renderTest)

  def unload(self):
    # remove the plugin menu item and icon
    if hasattr ( self.iface, "addPluginToWebMenu" ):
        self.iface.removePluginWebMenu("&Html Image Map Plugin",self.action)
    else:
        self.iface.removePluginMenu("&Html Image Map Plugin",self.action)
    self.iface.removeToolBarIcon(self.action)
    # disconnect form signal of the canvas
    QObject.disconnect(self.iface.mapCanvas(), SIGNAL("renderComplete(QPainter *)"), self.renderTest)

  def run(self):
    # check if current active layer is a polygon layer:
    layer =  self.iface.activeLayer()
    if layer == None:
        QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("No active layer found\n" "Please make one (multi)polygon or point layer active\n" "by choosing a layer in the legend"), QMessageBox.Ok, QMessageBox.Ok)
        return
    # don't know if this is possible / needed
    if not layer.isValid():
        QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("No VALID layer found\n" "Please make one (multi)polygon or point layer active\n" "by choosing a layer in the legend"), QMessageBox.Ok, QMessageBox.Ok)
        return
    if (layer.type()>0): # 0 = vector, 1 = raster
        QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("Wrong layer type, only vector layers can be used..\n" "Please make one vector layer active\n" "by choosing a vector layer in the legend"), QMessageBox.Ok, QMessageBox.Ok)
        return
    self.provider = layer.dataProvider()
    if not(self.provider.geometryType() == QGis.WKBPolygon or self.provider.geometryType() == QGis.WKBMultiPolygon or self.provider.geometryType() == QGis.WKBPoint):
        QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("Wrong geometrytype, only (multi)polygons and points can be used.\n" "Please make one (multi)polygon or point layer active\n" "by choosing a layer in the legend"), QMessageBox.Ok, QMessageBox.Ok)
        return

    # we need the fields of the active layer to show in the attribute combobox in the gui:
    attrFields = []

    fields = self.provider.fields()
    if hasattr(fields, 'iteritems'):
      for (i, field) in fields.iteritems():
        attrFields.append(field.name().trimmed())
    else:
        for field in self.provider.fields():
          attrFields.append(field.name().strip())

    # construct gui (using these fields)
    flags = Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowMaximizeButtonHint  # QgisGui.ModalDialogFlags
    # construct gui: if available reuse this one
    if hasattr(self, 'imageMapPlugin') == False:
        self.imageMapPluginGui = ImageMapPluginGui(self.iface.mainWindow(), flags)
    self.imageMapPluginGui.setAttributeFields(attrFields)
    self.imageMapPluginGui.setMapCanvasSize(self.iface.mapCanvas().width(), self.iface.mapCanvas().height())
    self.layerAttr = attrFields
    self.selectedFeaturesOnly = False # default all features in current Extent
    # catch SIGNAL's
    QObject.connect(self.imageMapPluginGui, SIGNAL("getFilesPath(QString)"), self.setFilesPath)
    QObject.connect(self.imageMapPluginGui, SIGNAL("onHrefAttributeSet(QString)"), self.onHrefAttributeFieldSet)
    QObject.connect(self.imageMapPluginGui, SIGNAL("onClickAttributeSet(QString)"), self.onClickAttributeFieldSet)
    QObject.connect(self.imageMapPluginGui, SIGNAL("onMouseOverAttributeSet(QString)"), self.onMouseOverAttributeFieldSet)
    QObject.connect(self.imageMapPluginGui, SIGNAL("onMouseOutAttributeSet(QString)"), self.onMouseOutAttributeFieldSet)
    QObject.connect(self.imageMapPluginGui, SIGNAL("getCbkBoxSelectedOnly(bool)"), self.setSelectedOnly)
    QObject.connect(self.imageMapPluginGui, SIGNAL("go(QString)"), self.go)
    QObject.connect(self.imageMapPluginGui, SIGNAL("setMapCanvasSize(int, int)"), self.setMapCanvasSize)
    # remember old path's in this session:
    self.imageMapPluginGui.setFilesPath(self.filesPath)
    self.imageMapPluginGui.show()


  def writeHtml(self):
    # create a holder for retrieving features from the provider
    feature = QgsFeature();
    temp = unicode(self.filesPath+".png")
    imgfilename = os.path.basename(temp)
    html = [u'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html>']
    # some rudimentary javascript to show off the mouse click and mouse over
    html.append(u'<head><title>QGIS</title><script type="text/javascript">\n')
    html.append(u'function mapOnMouseOver(str){document.getElementById("mousemovemessage").innerHTML=str; }\n')
    html.append(u'function mapOnMouseOut(str){document.getElementById("mousemovemessage").innerHTML="out of "+str; }\n')
    html.append(u'function mapOnClick(str){alert(str);}\n')
    html.append(u'</script> </head> <body>')
    html.append(u'<div id="mousemovemessage"></div><br>')
    html.append(u'<img src="' + imgfilename + '" border="0" ismap="ismap" usemap="#mapmap" alt="html imagemap created with QGIS" >\n')
    html.append(u'<map name="mapmap">\n')

    mapCanvasExtent = self.iface.mapCanvas().extent()
    doCrsTransform = False

    # in case of 'on the fly projection' 
    # AND 
    # different srs's for mapCanvas/project and layer we have to reproject stuff
    if hasattr(self.iface.mapCanvas().mapRenderer(), 'destinationSrs'):
      # QGIS < 2.0
      destinationCrs = self.iface.mapCanvas().mapRenderer().destinationSrs()
      layerCrs = self.iface.activeLayer().srs()
    else:
      destinationCrs = self.iface.mapCanvas().mapRenderer().destinationCrs()
      layerCrs = self.iface.activeLayer().crs()
    #print 'destination crs: %s:' % destinationCrs.toProj4()
    #print 'layer crs:       %s:' % layerCrs.toProj4()
    if not destinationCrs == layerCrs:
      # we have to transform the mapCanvasExtent to the data/layer Crs to be able
      # to retrieve the features from the data provider
      # but ONLY if we are working with on the fly projection
      # (because in that case we just 'fly' to the raw coordinates from data)
      if self.iface.mapCanvas().hasCrsTransformEnabled():
        self.crsTransform = QgsCoordinateTransform(destinationCrs, layerCrs)
        mapCanvasExtent = self.crsTransform.transformBoundingBox(mapCanvasExtent)
        # we have to have a transformer to do the transformation of the geometries
        # to the mapcanvas srs ourselves:
        self.crsTransform = QgsCoordinateTransform(layerCrs, destinationCrs)
        doCrsTransform = True
    # now iterate through each feature
    # select features within current extent,
    # set max progress bar to number of features (not very accurate with a lot of huge multipolygons)
    #self.imageMapPluginGui.setProgressBarMax(self.iface.activeLayer().featureCount())
    # or run over all features in current selection, just to determine the number of... (should be simpler ...)
    count = 0
    #   with  ALL attributes, WITHIN extent, WITH geom, AND using Intersect instead of bbox
    if hasattr(self.provider, 'select'):
        self.provider.select(self.provider.attributeIndexes(), mapCanvasExtent, True, True)
        while self.provider.nextFeature(feature):
            count = count + 1
    else:
        request = QgsFeatureRequest().setFilterRect(mapCanvasExtent)
        for feature in self.iface.activeLayer().getFeatures(request):
            count = count + 1
    self.imageMapPluginGui.setProgressBarMax(count)
    progressValue = 0
    # in case of points / lines we need to buffer geometries, calculate bufferdistance here
    bufferDistance = self.iface.mapCanvas().mapUnitsPerPixel()*10 #(plusminus 20pixel areas)

    # get a list of all selected features ids
    selectedFeaturesIds = self.iface.activeLayer().selectedFeaturesIds()
    # it seems that a postgres provider is on the end of file now
    # we do the select again to set the pointer/cursor to 0 again ?
    if hasattr(self.provider, 'select'):
        self.provider.select(self.provider.attributeIndexes(), mapCanvasExtent, True, True)
        while self.provider.nextFeature(feature):
            html.extend( self.handleGeom(feature, selectedFeaturesIds, doCrsTransform, bufferDistance) )
            progressValue = progressValue+1
            self.imageMapPluginGui.setProgressBarValue(progressValue)
    else:   # QGIS >= 2.0
        for feature in self.iface.activeLayer().getFeatures(request):
            html.extend( self.handleGeom(feature, selectedFeaturesIds, doCrsTransform, bufferDistance) )
            progressValue = progressValue+1
            self.imageMapPluginGui.setProgressBarValue(progressValue)
    html.append(u'</map></body></html>')
    return html

  def handleGeom(self, feature, selectedFeaturesIds, doCrsTransform, bufferDistance):
    html = []
    # if checkbox 'selectedFeaturesOnly' is checked: check if this feature is selected
    if self.selectedFeaturesOnly and feature.id() not in selectedFeaturesIds:
        # print "skipping %s " % feature.id()
        None
    else:
        geom = feature.geometry()
        if hasattr(self.iface.activeLayer(), "srs"):
            # QGIS < 2.0
            layerCrs = self.iface.activeLayer().srs()
        else:
            layerCrs = self.iface.activeLayer().crs()
        if doCrsTransform:
            if hasattr(geom, "transform"):
                geom.transform(self.crsTransform)
            else:
                QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("Cannot crs-transform geometry in your QGIS version ...\n" "Only QGIS version 1.5 and above can transform geometries on the fly\n" "As a workaround, you can try to save the layer in the destination crs (eg as shapefile) and reload that layer...\n"), QMessageBox.Ok, QMessageBox.Ok)
                #break
                raise Exception("Cannot crs-transform geometry in your QGIS version ...\n" "Only QGIS version 1.5 and above can transform geometries on the fly\n" "As a workaround, you can try to save the layer in the destination crs (eg as shapefile) and reload that layer...\n")
        projectExtent = self.iface.mapCanvas().extent()
        projectExtentAsPolygon = QgsGeometry()
        projectExtentAsPolygon = QgsGeometry.fromRect(projectExtent)
        #print "GeomType: %s" % geom.wkbType()
        if geom.wkbType() == QGis.WKBPoint: # 1 = WKBPoint
            # we make a copy of the geom, because apparently buffering the orignal will
            # only buffer the source-coordinates 
            geomCopy = QgsGeometry.fromPoint(geom.asPoint())
            polygon = geomCopy.buffer(bufferDistance, 0).asPolygon()
            #print "BufferedPoint: %s" % polygon
            for ring in polygon:
                h = self.ring2area(feature, ring, projectExtent, projectExtentAsPolygon)
                html.append(h)
        if geom.wkbType() == QGis.WKBPolygon: # 3 = WKBTYPE.WKBPolygon:
            polygon = geom.asPolygon()  # returns a list
            for ring in polygon:
                h = self.ring2area(feature, ring, projectExtent, projectExtentAsPolygon)
                html.append(h)
        if geom.wkbType() == QGis.WKBMultiPolygon: # 6 = WKBTYPE.WKBMultiPolygon:
            multipolygon = geom.asMultiPolygon() # returns a list
            for polygon in multipolygon:
                for ring in polygon:
                    h = self.ring2area(feature, ring, projectExtent, projectExtentAsPolygon)
                    html.append(h)
    return html

  def renderTest(self, painter):
    # Get canvas dimensions
    self.canvaswidth = painter.device().width()
    self.canvasheight = painter.device().height()

  def setFilesPath(self, filesPathQString):
    self.filesPath = filesPathQString

  def onHrefAttributeFieldSet(self, attributeFieldQstring):
    self.hrefAttributeField = attributeFieldQstring
    self.hrefAttributeIndex = self.provider.fieldNameIndex(attributeFieldQstring)

  def onClickAttributeFieldSet(self, attributeFieldQstring):
    self.onClickAttributeField = attributeFieldQstring
    self.onClickAttributeIndex = self.provider.fieldNameIndex(attributeFieldQstring)

  def onMouseOverAttributeFieldSet(self, attributeFieldQstring):
    self.onMouseOverAttributeField = attributeFieldQstring
    self.onMouseOverAttributeIndex = self.provider.fieldNameIndex(attributeFieldQstring)

  def onMouseOutAttributeFieldSet(self, attributeFieldQstring):
    self.onMouseOutAttributeField = attributeFieldQstring
    self.onMouseOutAttributeIndex = self.provider.fieldNameIndex(attributeFieldQstring)

  def setSelectedOnly(self, selectedOnlyBool):
    #print "selectedFeaturesOnly: %s" % selectedOnlyBool
    self.selectedFeaturesOnly = selectedOnlyBool

  def setMapCanvasSize(self, newWidth, newHeight):
    mapCanvas=self.iface.mapCanvas()
    parent=mapCanvas.parentWidget()
    # QGIS 2.4 places another widget between mapcanvas and qmainwindow, so:
    if not parent.parentWidget() == None:
        parent = parent.parentWidget()
    # some QT magic for me, coming from maximized force a minimal layout change first
    if(parent.isMaximized()):
      QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("Maximized QGIS window..\n" "QGIS window is maximized, plugin will try to de-maximize the window.\n" "If image size is still not exact what you asked for,\ntry starting plugin with non maximized window."), QMessageBox.Ok, QMessageBox.Ok)
      parent.showNormal()
    # on diffent OS's there seems to be different offsets to be taken into account
    magic=0
    if platform.system() == "Linux":
      magic=0 # mmm, not magic anymore?
    elif platform.system() == "Windows":
      magic=0 # mmm, not magic anymore?
    newWidth=newWidth+magic
    newHeight=newHeight+magic
    diffWidth=mapCanvas.size().width()-newWidth
    diffHeight=mapCanvas.size().height()-newHeight
    mapCanvas.resize(newWidth, newHeight)
    parent.resize(parent.size().width()-diffWidth, parent.size().height()-diffHeight)
    # HACK: there are cases where after maximising and here demaximizing the size of the parent is not
    # in sync with the actual size, giving a small error in the size setting
    # we do the resizing again, this fixes this small error then ....
    if newWidth <> mapCanvas.size().width() or newHeight <> mapCanvas.size().height():
      diffWidth=mapCanvas.size().width()-newWidth
      diffHeight=mapCanvas.size().height()-newHeight
      mapCanvas.resize(newWidth, newHeight)
      parent.resize(parent.size().width()-diffWidth, parent.size().height()-diffHeight)

  def go(self, foo):
    htmlfilename = unicode(self.filesPath + ".html")
    imgfilename = unicode(self.filesPath + ".png")
    # check if path is writable: ?? TODO
    #if not os.access(htmlfilename, os._OK):
    #  QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("Unable to write file with this name.\n" "Please choose a valid filename and a writable directory."))
    #  return
    # check if file(s) excist:
    if os.path.isfile(htmlfilename) or os.path.isfile(imgfilename):
        if QMessageBox.question(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("There is already a filename with this name.\n" "Continue?"), QMessageBox.Cancel, QMessageBox.Ok) <> QMessageBox.Ok:
            return
    # else: everthing ok: start writing img and html
    try:
        if len(self.filesPath)==0:
            raise IOError
        file = open(htmlfilename, "w")
        html = self.writeHtml()
        for line in html:
          file.write(line.encode('utf-8'))
        file.close()
        self.iface.mapCanvas().saveAsImage(imgfilename)
        msg = "Files successfully saved to:\n" + self.filesPath
        QMessageBox.information(self.iface.mainWindow(), self.MSG_BOX_TITLE, ( msg ), QMessageBox.Ok)
        self.imageMapPluginGui.hide()
    except IOError:
        QMessageBox.warning(self.iface.mainWindow(), self.MSG_BOX_TITLE, ("No valid path or filename.\n" "Please give or browse a valid filename."), QMessageBox.Ok, QMessageBox.Ok)


  # NOT WORKING ????
  # pixpoint = m2p.transform(point.x(), point.y())
  # print m2p.transform(point.x(), point.y())
  # so for now: a custom 'world2pixel' method
  def w2p(self, x, y, mupp, minx, maxy):
    pixX = (x - minx)/mupp
    pixY = (y - maxy)/mupp
    return [int(pixX), int(-pixY)]

  # for given ring in feature, IF al least on point on ring is in mapCanvasExtent
  # generate a string like:
  # <area shape=polygon href='xxx' onClick="mapOnClick('yyy')" onMouseOver="mapOnMouseOver('zzz')  coords=519,-52,519,..,-52,519,-52>
  def ring2area(self, feature, ring, extent, extentAsPoly):
    param = u''
    htm = u'<area shape="poly" '
    if hasattr(feature, 'attributeMap'):
        attrs = feature.attributeMap()
    else:
        # QGIS > 2.0
        attrs = feature
    # escape ' and " because they will collapse as javascript parameter
    if self.imageMapPluginGui.isHrefChecked():
        htm = htm + 'href="' + unicode(attrs[self.hrefAttributeIndex]) + '" '
    if self.imageMapPluginGui.isOnClickChecked():
        param = unicode(attrs[self.onClickAttributeIndex])
        htm = htm + 'onClick="mapOnClick(\'' + self.jsEscapeString(param) + '\')" '
    if self.imageMapPluginGui.isOnMouseOverChecked():
        param = unicode(attrs[self.onMouseOverAttributeIndex])
        htm = htm + 'onMouseOver="mapOnMouseOver(\'' + self.jsEscapeString(param) + '\')" '
    if self.imageMapPluginGui.isOnMouseOutChecked():
        param = unicode(attrs[self.onMouseOutAttributeIndex])
        htm = htm + 'onMouseOut="mapOnMouseOut(\'' + self.jsEscapeString(param) + '\')" '
    htm = htm + ' coords="'
    lastPixel=[0,0]
    insideExtent = False
    coordCount = 0
    extentAsPoly = QgsGeometry()
    extentAsPoly = QgsGeometry.fromRect(extent)
    for point in ring:
        if extentAsPoly.contains(point):
            insideExtent = True
        pixpoint =  self.w2p(point.x(), point.y(), 
                self.iface.mapCanvas().mapUnitsPerPixel(),
                extent.xMinimum(), extent.yMaximum())
        if lastPixel<>pixpoint:
            coordCount = coordCount +1
            htm += (str(pixpoint[0]) + ',' + str(pixpoint[1]) + ',')
            lastPixel = pixpoint
    htm = htm[0:-1]
    # check if there are more then 2 coords: very small polygons on current map can have coordinates
    # which if rounded to pixels all come to the same pixel, resulting in just ONE x,y coordinate
    # we skip these
    if coordCount < 2:
        #print "Ring contains just one pixel coordinate pair: skipping"
        return ''
    # if at least ONE pixel of this ring is in current view extent, return the area-string, otherwise return an empty string
    if not insideExtent:
        #print "RING FULLY OUTSIDE EXTENT: %s " % ring
        return ''
    else:
        # using last param as alt parameter (to be W3 compliant we need one)
        htm += '" alt="' + param + '">\n'
        return unicode(htm)


  # escape ' and " so string can be safely used as string javascript argument
  def jsEscapeString(self, str):
    return unicode(str.replace("'", "\\'").replace('"', '\"'))