text: # -*- coding: utf-8 -*-
  
  import bpy
  import bgl
  import blf
  import sys
  import os
  import datetime
  import math
  
  from . sun_calc import degToRad, format_hms
  from . properties import Display, Sun
  
  # ---------------------------------------------------------------------------
  
  
  class MapObject:
  
      class Origin:
          x = 0
          y = 0
  
      def __init__(self, t, w, h):
          self.type = t
          self.width = w
          self.height = h
          self.heightFactor = .50
          self.opacity = 1.0
          self.focused = False
          self.view3d_area = None
          self.colorStyle = 'N'
          self.origin = self.Origin()
  
      def set_dimensions(self, width):
          self.width = width
          self.height = int(width * self.heightFactor)
  
      def check_focus(self, context, event):
          self.focused = self.is_focused(context, event)
          return self.focused
  
      def is_focused(self, context, event):
          if context.area != self.view3d_area:
              return False
  
          x = event.mouse_region_x
          y = event.mouse_region_y
  
          for reg in self.view3d_area.regions:
              if reg.type == 'WINDOW':
                  if x < 0 or x > reg.width:
                      return False
                  else:
                      break
  
          if x < self.origin.x or x > (self.origin.x + self.width) or \
                  y < self.origin.y or y > (self.origin.y + self.height) or \
                  y < 0 or y > reg.height:
              return False
          return True
  
      def near_border(self, context, event):
          if context.area != self.view3d_area:
              return False
  
          x = event.mouse_region_x
          y = event.mouse_region_y
  
          for reg in self.view3d_area.regions:
              if reg.type == 'WINDOW':
                  if x < 20 or x > (reg.width - 20) or \
                          y < 20 or y > (reg.height - 20):
                      return True
                  else:
                      break
          return False
  
  # ---------------------------------------------------------------------------
  
  
  class MapClass:
  
      class mouse:
          pass
  
      class grab:
  
          class spot:
              pass
  
          class offset:
              pass
  
      class zoom:
          pass
  
      class image:
          pass
  
      def __init__(self):
          self.handler1 = None
          self.handler2 = None
          self.view3d_area = None
          self.draw_region = None
          self.glImage = None
          self.mapLocation = 'VIEWPORT'
          self.init_zoom_preference = True
          self.reset(self.mapLocation)
  
      def init(self, location):
          self.object = [MapObject('MAP', 0, 0), MapObject('TEXT', 100, 160)]
          self.object[0].set_dimensions(200)
          self.object[1].origin.x = 10
          self.object[1].origin.y = 80
          self.mapLocation = location
  
      def zoom_preferences(self, invert_zoom_wheel, invert_mouse_zoom):
          self.init_zoom_preference = False
          if invert_zoom_wheel:
              self.zoom.wheel_up = 'OUT'
              self.zoom.wheel_down = 'IN'
          else:
              self.zoom.wheel_up = 'IN'
              self.zoom.wheel_down = 'OUT'
          if invert_mouse_zoom:
              self.zoom.mouse_up = 'IN'
              self.zoom.mouse_down = 'OUT'
          else:
              self.zoom.mouse_up = 'OUT'
              self.zoom.mouse_down = 'IN'
  
      def reset(self, location):
          self.init(location)
          self.action = None
          self.isActive = False
          self.start = False
          self.stop = False
          self.lockCrosshair = True
          self.showInfo = False
          self.latitude = 0.0
          self.longitude = 0.0
          self.ctrlPress = False
          self.altPress = False
          self.lineWidth = 1.0
          self.textureless = False
          self.mouse.x = 0
          self.mouse.y = 0
          self.grab.spot.x = 0
          self.grab.spot.y = 0
          self.grab.offset.x = 0
          self.grab.offset.y = 0
          self.zoom.width = 0
          self.zoom.x = 0
          self.zoom.y = 0
          self.image.name = None
          self.image.bindcode = 0
          self.image.loaded = False
          self.image.free_it = False
  
      def clear_callbacks(self):
          if self.mapLocation == 'PANEL':
              pl = bpy.types.SpaceProperties
          else:
              pl = bpy.types.SpaceView3D
          if self.handler2 is not None:
              pl.draw_handler_remove(self.handler2, 'WINDOW')
              self.handler2 = None
          if self.handler1 is not None:
              pl.draw_handler_remove(self.handler1, 'WINDOW')
              self.handler1 = None
  
      def set_view3d_area(self, area):
          for obj in self.object:
              obj.view3d_area = area
  
      def activate(self, context):
          if context.area.type == 'PROPERTIES':
              self.reset(self.mapLocation)
  
              def fw(self, context):
                  if self.mapLocation == 'PANEL':
                      self.draw_region = context.region
                  areas = bpy.context.screen.areas
                  for area in areas:
                      if area.type == 'VIEW_3D':
                          self.view3d_area = context.area
                          if self.mapLocation == 'PANEL':
                              return True
                          for reg in area.regions:
                              if reg.type == 'WINDOW':
                                  self.draw_region = reg
                                  Display.refresh()
                                  return True
                  return False
              if not fw(self, context):
                  self.draw_region = context.region
  
              self.set_view3d_area(self.view3d_area)
              self.start = True
              if self.mapLocation == 'PANEL':
                  self.handler1 = bpy.types.SpaceProperties.draw_handler_add(
                      Map_load_callback,
                      (self, context), 'WINDOW', 'POST_PIXEL')
              else:
                  self.handler1 = bpy.types.SpaceView3D.draw_handler_add(
                      Map_load_callback,
                      (self, context), 'WINDOW', 'POST_PIXEL')
              self.isActive = True
              return True
          else:
              return False
  
      def activateBGLcallback(self, context):
          if self.mapLocation == 'PANEL':
              self.handler2 = bpy.types.SpaceProperties.draw_handler_add(
                  Draw_map_callback,
                  (self, context), 'WINDOW', 'POST_PIXEL')
          else:
              self.handler2 = bpy.types.SpaceView3D.draw_handler_add(
                  Draw_map_callback,
                  (self, context), 'WINDOW', 'POST_PIXEL')
          self.view3d_area = context.area
          self.set_view3d_area(self.view3d_area)
          bpy.ops.sunpos.map('INVOKE_DEFAULT')
  
      def deactivate(self):
          self.clear_callbacks()
          self.stop = False
          self.action = None
          self.image.loaded = False
          if self.glImage is not None:
              try:
                  if self.glImage.bindcode == self.image.bindcode:
                      self.glImage.gl_free()
                      bpy.data.images.remove(self.glImage)
              except:
                  pass
          self.image.free_it = False
          self.glImage = None
          self.image.bindcode = 0
          self.isActive = False
          if Sun.SP:
              Sun.SP.ShowMap = False
  
      def load_blender_image(self, file_name):
          if file_name == "None":
              self.textureless = True
              self.object[0].heightFactor = .5
              return True
          else:
              self.textureless = False
  
          # S.L. fix to use any relative path
          dir_path = os.path.dirname(os.path.realpath(__file__))
          self.image.name = dir_path + os.path.sep + file_name
          if os.path.exists(self.image.name):
              try:
                  self.glImage = bpy.data.images.load(self.image.name)
                  if self.glImage is not None:
                      self.image.loaded = True
                      self.glImage.user_clear()
                      self.object[0].heightFactor = \
                          self.glImage.size[1] / self.glImage.size[0]
                      return True
                  else:
                      return False
              except:
                  pass
          return False
  
      def load_gl_image(self):
          for i in range(1, 6):  # Make up to 6 tries to load image
              self.glImage.gl_load(bgl.GL_NEAREST, bgl.GL_NEAREST)
              if self.glImage.bindcode != 0:
                  self.image.bindcode = self.glImage.bindcode
                  return True
          return False
  
      def show_text_in_viewport(self, text):
  
          def text_line(fx, fy, reduce, c, str):
              bgl.glColor4f(c[0], c[1], c[2], c[3])
              blf.position(0, fx, fy, 0)
              blf.draw(0, str)
              if reduce:
                  fy -= 20
              return fy
  
          if text.colorStyle == 'N':
              tColor = (0.8, 0.8, 0.8, 1.0)
              vColor = (1.0, 1.0, 1.0, 1.0)
          else:
              tColor = (0.0, 0.0, 0.0, 1.0)
              vColor = (0.23, 0.09, 0.18, 1.0)
  
          blf.size(0, 14, 72)
          fx = text.origin.x
          fy = text.origin.y + 140
          fy = text_line(fx + 10, fy, True, tColor, str(Sun.SP.Month) +
                         " / " + str(Sun.SP.Day) +
                         " / " + str(Sun.SP.Year))
  
          fy = text_line(fx, fy, False, tColor, "  Day: ")
          fy = text_line(fx + 40, fy, True, vColor, str(Sun.SP.Day_of_year))
          fy = text_line(fx, fy, False, tColor, "Time: ")
          fy = text_line(fx + 40, fy, True, vColor, format_hms(Sun.SP.Time))
  
          if Sun.ShowRiseSet:
              fy -= 10
              fy = text_line(fx, fy, True, tColor, "Solar Noon:")
              fy = text_line(fx + 14, fy, True, vColor,
                             format_hms(Sun.SolarNoon.time))
              fy -= 10
              fy = text_line(fx, fy, False, tColor, "Rise: ")
              if Sun.RiseSetOK:
                  fy = text_line(fx + 40, fy, True, vColor,
                                 format_hms(Sun.Sunrise.time))
              else:
                  fy = text_line(fx + 40, fy, True, vColor, "--------")
              fy = text_line(fx, fy, False, tColor, " Set: ")
              if Sun.RiseSetOK:
                  fy = text_line(fx + 40, fy, True, vColor,
                                 format_hms(Sun.Sunset.time))
              else:
                  fy = text_line(fx + 40, fy, True, vColor, "--------")
  
      def locked_crosshair_event(self, action, event):
          self.mouse.x = event.mouse_region_x
          self.mouse.y = event.mouse_region_y
          self.grab.offset.x = event.mouse_region_x
          self.grab.offset.y = event.mouse_region_y
          self.lockCrosshair = True
          Display.refresh()
          return Map_function[action](event)
  
      # -----------------------------------------------------------------------
  
      def event_controller(self, context, event):
  
          if not Sun.SP.ShowMap or event.type == 'TIMER':
              return {'PASS_THROUGH'}
  
          mapInFocus = self.object[0].check_focus(context, event)
  
          if event.type == 'MOUSEMOVE':
              if self.action == None:
                  if mapInFocus:
                      if self.object[0].near_border(context, event):
                          return {'PASS_THROUGH'}
                      else:
                          return {'RUNNING_MODAL'}
                  else:
                      return {'PASS_THROUGH'}
              if self.action in ('PAN', 'ZOOM', 'TIME', 'DAY', 'G'):
                  return self.locked_crosshair_event(self.action, event)
  
          if event.type in ('LEFT_CTRL', 'LEFT_ALT', 'RIGHT_CTRL', 'RIGHT_ALT'):
              Key_function[event.type](event)
              return {'RUNNING_MODAL'}
  
          self.object[1].check_focus(context, event)
  
          if event.type in ('MIDDLEMOUSE', 'LEFTMOUSE', 'G'):
              val = Key_function[event.type](context, event)
              if val:
                  return val
          elif event.type in ('RIGHTMOUSE', 'ESC',
                              'T', 'D', 'X', 'Y', 'R', 'S', 'E', 'A', 'O', 'C', 'H', 'F1'):
              Display.refresh()
              return Key_function[event.type](event)
  
          if self.action in ('PAN', 'ZOOM', 'TIME', 'DAY', 'G', 'O'):
              return self.locked_crosshair_event(self.action, event)
  
          if not mapInFocus:
              return {'PASS_THROUGH'}
  
          if self.action in ('X', 'Y'):
              return Map_function[self.action](event)
          else:
              self.mouse.x = event.mouse_region_x
              self.mouse.y = event.mouse_region_y
  
          if event.type == 'WHEELUPMOUSE':
              wheel_zoom(self.zoom.wheel_up)
          elif event.type == 'WHEELDOWNMOUSE':
              wheel_zoom(self.zoom.wheel_down)
          elif self.action == 'CROSS':
              self.lockCrosshair = False
              Display.refresh()
  
          return {'RUNNING_MODAL'}
  
  # ---------------------------------------------------------------------------
  
  
  Map = MapClass()
  
  
  # ---------------------------------------------------------------------------
  
  
  def key_Ctrl(event):
      if event.value == 'PRESS':
          Map.ctrlPress = True
      elif event.value == 'RELEASE':
          Map.ctrlPress = False
  
  
  def key_Alt(event):
      if event.value == 'PRESS':
          Map.altPress = True
      elif event.value == 'RELEASE':
          Map.altPress = False
  
  
  def key_Esc(event):
      if Map.object[1].focused and Map.showInfo:
          if event.value == 'RELEASE':
              Map.action = None
              Map.showInfo = False
          return {'RUNNING_MODAL'}
      if Map.object[0].focused:
          if Map.action is None:
              Map.stop = True
              return {'FINISHED'}
      if Map.action is not None:
          if event.value == 'RELEASE':
              Map.action = None
          return {'RUNNING_MODAL'}
      return {'PASS_THROUGH'}
  
  
  def key_LeftMouse(context, event):
      if event.value == 'PRESS':
          if Map.action is not None:
              Map.action = None
              Display.refresh()
              return {'RUNNING_MODAL'}
          elif Map.object[0].focused:
              if Map.object[0].near_border(context, event):
                  Map.action = 'BORDER'
                  Map.lockCrosshair = True
                  return {'PASS_THROUGH'}
              Map.action = 'CROSS'
              Map.lockCrosshair = False
              Display.refresh()
          else:
              return {'PASS_THROUGH'}
      elif event.value == 'RELEASE':
          Map.lockCrosshair = True
          Map.action = None
          Display.refresh()
          return {'PASS_THROUGH'}
  
      return False
  
  
  def key_MiddleMouse(context, event):
      if event.value == 'PRESS':
          if Map.object[1].focused and Map.showInfo:
              Map.action = 'G' if Map.action != 'G' else None
              Map.grab.spot.y = event.mouse_region_y
              Map.grab.spot.x = event.mouse_region_x
          elif Map.object[0].focused:
              if Map.ctrlPress:
                  map = Map.object[0]
                  Map.action = 'ZOOM'
                  Map.zoom.width = map.width
                  Map.zoom.x = map.origin.x
                  Map.zoom.y = map.origin.y
              else:
                  Map.action = 'PAN'
              Map.grab.spot.x = event.mouse_region_x
              Map.grab.spot.y = event.mouse_region_y
          else:
              return {'PASS_THROUGH'}
      elif event.value == 'RELEASE':
          Map.action = None
          Map.object[0].focused = False
          Map.object[1].focused = False
          Display.refresh()
      return {'RUNNING_MODAL'}
  
  # ---------------------------------------------------------------------------
  
  
  def key_A(event):
      if event.value == 'PRESS':
          if Map.object[0].focused:
              Sun.SP.ObjectGroup = 'ANALEMMA'
          else:
              return {'PASS_THROUGH'}
      return {'RUNNING_MODAL'}
  
  
  def key_C(event):
      if event.value == 'PRESS':
          if Map.object[0].focused or Map.object[1].focused:
              if Map.object[1].colorStyle == 'N':
                  Map.object[1].colorStyle = 'R'
              else:
                  Map.object[1].colorStyle = 'N'
          else:
              return {'PASS_THROUGH'}
      return {'RUNNING_MODAL'}
  
  
  def key_E(event):
      if event.value == 'PRESS':
          if Map.object[0].focused:
              Sun.SP.ObjectGroup = 'ECLIPTIC'
          else:
              return {'PASS_THROUGH'}
      return {'RUNNING_MODAL'}
  
  
  def key_G(context, event):
      if event.value == 'PRESS':
          if Map.object[1].focused and Map.showInfo:
              Map.action = 'G' if Map.action != 'G' else None
              Map.grab.spot.y = event.mouse_region_y
              Map.grab.spot.x = event.mouse_region_x
              Display.refresh()
          elif Map.object[0].focused:
              Map.action = 'PAN' if Map.action != 'PAN' else None
              Map.grab.spot.x = event.mouse_region_x
              Map.grab.spot.y = event.mouse_region_y
              return False
          else:
              return {'PASS_THROUGH'}
      return {'RUNNING_MODAL'}
  
  
  def key_H(event):
      if event.value == 'PRESS':
          if Map.object[0].focused:
              Map.action = None
              bpy.ops.object.help_operator('INVOKE_DEFAULT')
      return {'RUNNING_MODAL'}
  
  
  def key_R(event):
      if event.value == 'PRESS':
          if Map.object[0].focused:
              Map.lineWidth += 1.0
              if Map.lineWidth == 4.0:
                  Map.lineWidth = 0.0
          else:
              Map.action = None
              return {'PASS_THROUGH'}
      return {'RUNNING_MODAL'}
  
  
  def key_S(event):
      if event.value == 'PRESS':
          if Map.object[0].focused:
              Map.showInfo = True if not Map.showInfo else False
              Sun.PP.ShowRiseSet = True
              Sun.ShowRiseSet = True
          else:
              return {'PASS_THROUGH'}
      return {'RUNNING_MODAL'}
  
  
  def key_TDOXY(event):
      if event.value == 'PRESS':
          if Map.object[0].focused:
              Map.grab.spot.x = event.mouse_region_x
              Map.grab.spot.y = event.mouse_region_y
              if event.type == 'T':
                  if Map.action == 'TIME':
                      Map.action = None
                      Map.showInfo = False
                  else:
                      Map.action = 'TIME'
                      Map.showInfo = True
              elif event.type == 'D':
                  if Map.action == 'DAY':
                      Map.action = None
                      Map.showInfo = False
                  else:
                      Map.action = 'DAY'
                      Map.showInfo = True
              else:
                  Map.action = event.type
          else:
              Map.action = None
              return {'PASS_THROUGH'}
      return {'RUNNING_MODAL'}
  
  
  # ---------------------------------------------------------------------------
  
  def map_Pan(event):
      return {'RUNNING_MODAL'}
  
  
  def map_Zoom(event):
      mouse_zoom()
      return {'RUNNING_MODAL'}
  
  
  def map_Time(event):
      if event.type == 'WHEELUPMOUSE':
          time_change_wheel(Map.zoom.wheel_up)
      elif event.type == 'WHEELDOWNMOUSE':
          time_change_wheel(Map.zoom.wheel_down)
      else:
          time_change()
      return {'RUNNING_MODAL'}
  
  
  def map_Day(event):
      if event.type == 'WHEELUPMOUSE':
          day_change_wheel(Map.zoom.wheel_up)
      elif event.type == 'WHEELDOWNMOUSE':
          day_change_wheel(Map.zoom.wheel_down)
      else:
          day_change()
      return {'RUNNING_MODAL'}
  
  
  def map_G(event):
      if Map.grab.offset.x < Map.grab.spot.x:
          off = Map.grab.spot.x - Map.grab.offset.x
          Map.object[1].origin.x -= off
      else:
          off = Map.grab.offset.x - Map.grab.spot.x
          Map.object[1].origin.x += off
      if Map.grab.offset.y < Map.grab.spot.y:
          off = Map.grab.spot.y - Map.grab.offset.y
          Map.object[1].origin.y -= off
      else:
          off = Map.grab.offset.y - Map.grab.spot.y
          Map.object[1].origin.y += off
  
      Map.grab.spot.x = Map.mouse.x
      Map.grab.spot.y = Map.mouse.y
  
      return {'RUNNING_MODAL'}
  
  
  def map_O(event):
      if event.type == 'WHEELUPMOUSE':
          opacity_change_wheel(Map.zoom.wheel_up)
      elif event.type == 'WHEELDOWNMOUSE':
          opacity_change_wheel(Map.zoom.wheel_down)
      else:
          opacity_change()
      Display.refresh()
      return {'RUNNING_MODAL'}
  
  
  def map_X(event):
      if event.type == 'WHEELUPMOUSE':
          X_change_wheel(Map.zoom.wheel_up)
      elif event.type == 'WHEELDOWNMOUSE':
          X_change_wheel(Map.zoom.wheel_down)
      else:
          Map.mouse.y = Map.grab.spot.y
          Map.mouse.x = event.mouse_region_x
          Map.lockCrosshair = False
      Display.refresh()
      return {'RUNNING_MODAL'}
  
  
  def map_Y(event):
      if event.type == 'WHEELUPMOUSE':
          Y_change_wheel(Map.zoom.wheel_up)
      elif event.type == 'WHEELDOWNMOUSE':
          Y_change_wheel(Map.zoom.wheel_down)
      else:
          Map.mouse.x = Map.grab.spot.x
          Map.mouse.y = event.mouse_region_y
          Map.lockCrosshair = False
      Display.refresh()
      return {'RUNNING_MODAL'}
  
  ############################################################################
  
  Key_function = dict([('LEFT_CTRL', key_Ctrl), ('LEFT_ALT', key_Alt),
                       ('RIGHT_CTRL', key_Ctrl), ('RIGHT_ALT', key_Alt),
                       ('MIDDLEMOUSE', key_MiddleMouse),
                       ('LEFTMOUSE', key_LeftMouse),
                       ('RIGHTMOUSE', key_Esc), ('ESC', key_Esc),
                       ('A', key_A), ('E', key_E), ('G', key_G), ('C', key_C),
                       ('H', key_H), ('F1', key_H),
                       ('R', key_R), ('S', key_S),
                       ('T', key_TDOXY), ('D', key_TDOXY), ('O', key_TDOXY),
                       ('X', key_TDOXY), ('Y', key_TDOXY)])
  
  # ---------------------------------------------------------------------------
  
  Map_function = dict([('PAN', map_Pan), ('ZOOM', map_Zoom),
                       ('TIME', map_Time), ('DAY', map_Day),
                       ('G', map_G), ('O', map_O),
                       ('X', map_X), ('Y', map_Y)])
  
  ############################################################################
  
  
  def wheel_zoom(action):
      mf = 0.2 if Map.ctrlPress else 0.0
      af = 0.07 if Map.altPress else 0.0
      if action == 'IN':
          scale = 1.10 + mf - af
      else:
          scale = .90 - mf + af
      if Map.object[0].width * scale < 50:
          return
      else:
          Map.object[0].set_dimensions(int(int(Map.object[0].width * scale)))
      x = Map.mouse.x - Map.object[0].origin.x
      y = Map.mouse.y - Map.object[0].origin.y
      Map.object[0].origin.x += x - int(x * scale)
      Map.object[0].origin.y += y - int(y * scale)
      Map.lockCrosshair = True
      Display.refresh()
  
  
  def mouse_zoom():
      if Map.mouse.y > Map.grab.spot.y:
          s = Map.mouse.y - Map.grab.spot.y
          action = Map.zoom.mouse_up
      elif Map.mouse.y < Map.grab.spot.y:
          s = Map.grab.spot.y - Map.mouse.y
          action = Map.zoom.mouse_down
      else:
          s = 0
          action = Map.zoom.mouse_down
  
      if action == 'IN':
          scale = 1 + s * .01
      else:
          scale = 1 - s * .006
  
      w = int(Map.zoom.width * scale)
      if w < 50:
          return
      Map.object[0].set_dimensions(w)
      x = Map.grab.spot.x - Map.zoom.x
      y = Map.grab.spot.y - Map.zoom.y
      Map.object[0].origin.x = x - int(x * scale) + Map.zoom.x
      Map.object[0].origin.y = y - int(y * scale) + Map.zoom.y
  
  
  # ---------------------------------------------------------------------------
  
  
  def check_time_boundary():
      if Sun.SP.Time > 24.0:
          Sun.SP.Time += -24.0
      elif Sun.SP.Time < 0.0:
          Sun.SP.Time += 24.0
      Display.refresh()
  
  
  def time_change_wheel(action):
      if action == 'IN':
          val = 1.0 if not Map.altPress else 0.0166
      else:
          val = -1.0 if not Map.altPress else -0.0166
      mf = 1.5 if Map.ctrlPress else 1.0
      Sun.SP.Time += mf * val
      check_time_boundary()
  
  
  def time_change():
      if Map.mouse.x == Map.grab.spot.x:
          return
      mf = 50 if Map.ctrlPress else 1000
      if Map.altPress:
          sx = 0.0001 if Map.mouse.x > Map.grab.spot.x else -0.0001
      else:
          sx = (Map.mouse.x - Map.grab.spot.x) / mf
      Sun.SP.Time += sx
      Map.grab.spot.x = Map.mouse.x
      check_time_boundary()
  
  # ---------------------------------------------------------------------------
  
  
  def day_change_wheel(action):
      wf = wheel_factor(action)
      day_change_bounds(wf)
  
  
  def day_change():
      if Map.mouse.x == Map.grab.spot.x:
          return
      cf = 1 if Map.ctrlPress else 3
      if Map.altPress:
          mf = 1 if Map.mouse.x > Map.grab.spot.x else -1
      else:
          mf = (Map.mouse.x - Map.grab.spot.x) / cf
      day_change_bounds(mf)
      Map.grab.spot.x = Map.mouse.x
  
  
  def day_change_bounds(wf):
      if Sun.SP.Day_of_year + wf > 366:
          Sun.SP.Day_of_year = 1
          Sun.SP.Year += 1
      elif Sun.SP.Day_of_year + wf < 1:
          Sun.SP.Day_of_year = 366
          Sun.SP.Year -= 1
      else:
          Sun.SP.Day_of_year += wf
      dt = (datetime.date(Sun.SP.Year, 1, 1) +
            datetime.timedelta(Sun.SP.Day_of_year - 1))
      Sun.SP.Day = dt.day
      Sun.SP.Month = dt.month
      Display.refresh()
  
  # ---------------------------------------------------------------------------
  
  
  def opacity_change_wheel(action):
      if action == 'IN':
          val = 0.05 if not Map.altPress else 0.01
      else:
          val = -0.05 if not Map.altPress else -0.01
      mf = 1.5 if Map.ctrlPress else 1.0
      obj = Map.object[0]
      obj.opacity += mf * val
      opacity_change_bounds(obj)
  
  
  def opacity_change():
      if Map.mouse.x > Map.grab.spot.x:
          s = Map.mouse.x - Map.grab.spot.x
          action = '+'
      elif Map.mouse.x < Map.grab.spot.x:
          s = Map.grab.spot.x - Map.mouse.x
          action = '-'
      else:
          action = '-'
          s = 0
      s /= 10
      obj = Map.object[0]
  
      if action == '+':
          scale = obj.opacity + s * .002
      else:
          scale = obj.opacity - s * .002
      obj.opacity = scale
      opacity_change_bounds(obj)
  
  
  def opacity_change_bounds(obj):
      if obj.opacity > 1.0:
          obj.opacity = 1.0
          Map.grab.spot.x = Map.mouse.x
      elif obj.opacity < 0.0:
          obj.opacity = 0.0
          Map.grab.spot.x = Map.mouse.x
  
  # ---------------------------------------------------------------------------
  
  
  def X_change_wheel(action):
      wf = wheel_factor(action)
      if Sun.SP.Longitude + wf > 180.0:
          Sun.SP.Longitude = -180.0
      elif Sun.SP.Longitude + wf < -180.0:
          Sun.SP.Longitude = 180.0
      else:
          Sun.SP.Longitude += wf
  
  
  def Y_change_wheel(action):
      wf = wheel_factor(action)
      if Sun.SP.Latitude + wf > 90.0:
          Sun.SP.Latitude = -90.0
      elif Sun.SP.Latitude + wf < -90.0:
          Sun.SP.Latitude = 90.0
      else:
          Sun.SP.Latitude += wf
  
  
  def wheel_factor(action):
      if action == 'IN':
          val = 5.0 if not Map.altPress else 1.0
      else:
          val = -5.0 if not Map.altPress else -1.0
      wf = 3 * val if Map.ctrlPress else 1.0 * val
      return wf
  
  ############################################################################
  
  
  def Map_load_callback(self, context):
  
      if Sun.SP.ShowMap and not Map.image.loaded:
          Map.glImage = None
          if not Map.load_blender_image(Sun.MapName):
              print("Could not load image file: ", Map.image.name)
              Sun.SP.ShowMap = False
  
      if Map.start:
          def set_region_data():
              Map.activateBGLcallback(context)
              bgl.glTexParameteri(bgl.GL_TEXTURE_2D,
                                  bgl.GL_TEXTURE_MAG_FILTER, bgl.GL_LINEAR)
              bgl.glTexParameteri(bgl.GL_TEXTURE_2D,
                                  bgl.GL_TEXTURE_MIN_FILTER, bgl.GL_LINEAR)
              Map.object[0].set_dimensions(0)
              Map.toolProps = None
              Map.toolPropsWidth = 0
              for reg in Map.view3d_area.regions:
                  if reg.type == 'TOOL_PROPS':
                      Map.toolProps = reg
                      Map.toolProps_width = reg.width
                  elif reg.type == 'WINDOW':
                      Map.region = reg
                      Map.saved_region_width = reg.width
                      Map.object[0].set_dimensions(int(reg.width * .8))
                      Map.object[0].origin.x = \
                          int((Map.region.width - Map.object[0].width) / 2)
                      Map.object[0].origin.y = 2
              return
  
          Map.start = False
  
          if Map.image.loaded:
              if not Map.load_gl_image():
                  print("Could not load image file: ", Map.image.name)
              elif Map.glImage.bindcode != 0:
                  Map.image.free_it = True
                  set_region_data()
                  return
              print("Could not get texture in gl_load()")
              Map.glImage = None
              Map.image.bindcode = 0
              Sun.SP.ShowMap = False
  
          elif Map.textureless:
              set_region_data()
              return
          else:
              Sun.SP.ShowMap = False
          return
  
      if Map.stop:
          Map.deactivate()
          return
  
      return
  
  
  ############################################################################
  
  
  def Draw_map_callback(self, context):
  
      if context.area != Map.view3d_area:
          return
      elif context.area.type == 'PROPERTIES' and \
              context.space_data.context != 'WORLD':
          return
  
      # Check if window area has changed for sticky zoom
      theMap = Map.object[0]
      if Map.region.width < Map.saved_region_width:
          diff = Map.saved_region_width - Map.region.width
          if theMap.origin.x + theMap.width > Map.saved_region_width:
              if theMap.origin.x > 0:
                  theMap.origin.x -= diff
              else:
                  theMap.width -= diff
          else:
              if Map.toolProps is not None:
                  if Map.toolProps.width > Map.toolProps_width:
                      theMap.origin.x -= diff
                  Map.toolProps_width = Map.toolProps.width
              if theMap.origin.x < 0:
                  theMap.origin.x += diff
      else:
          diff = Map.region.width - Map.saved_region_width
          if theMap.width > Map.saved_region_width:
              theMap.width += diff
          else:
              if Map.toolProps is not None:
                  if Map.toolProps.width < Map.toolProps_width:
                      theMap.origin.x += diff
                  Map.toolProps_width = Map.toolProps.width
      theMap.set_dimensions(theMap.width)
      Map.saved_region_width = Map.region.width
  
      # Latitude and longitude are set to an equidistant
      # cylindrical projection with lat/long 0/0 exactly
      # in the middle of the image.
  
      zLong = theMap.width / 2
      longFac = zLong / 180
      zLat = theMap.height / 2
      latFac = zLat / 90
      crossChange = True
  
      if not Map.action == 'PAN':
          x = Map.mouse.x
          y = Map.mouse.y
          if x < theMap.origin.x or x > theMap.origin.x + theMap.width:
              crossChange = False
              x = 0
          else:
              testBoundary = theMap.origin.x + theMap.width
              if testBoundary < Map.region.width:
                  rightBoundary = testBoundary
              else:
                  rightBoundary = Map.region.width
              if x > rightBoundary:
                  crossChange = False
                  x = rightBoundary
          cX = x - zLong - theMap.origin.x
  
          if longFac:
              newLongitude = cX / longFac
          else:
              newLongitude = 0.0
  
          if y < theMap.origin.y or y < 0:
              crossChange = False
              y = 0
          elif y > theMap.origin.y + theMap.height:
              crossChange = False
              y = theMap.origin.y + theMap.height
          cY = y - zLat - theMap.origin.y
  
          if latFac:
              newLatitude = cY / latFac
          else:
              newLatitude = 0.0
  
          if newLatitude == Map.latitude and newLongitude == Map.longitude:
              crossChange = False
          else:
              Map.latitude = newLatitude
              Map.longitude = newLongitude
      else:
          if Map.grab.offset.x < Map.grab.spot.x:
              off = Map.grab.spot.x - Map.grab.offset.x
              theMap.origin.x -= off
          else:
              off = Map.grab.offset.x - Map.grab.spot.x
              theMap.origin.x += off
          if Map.grab.offset.y < Map.grab.spot.y:
              off = Map.grab.spot.y - Map.grab.offset.y
              theMap.origin.y -= off
          else:
              off = Map.grab.offset.y - Map.grab.spot.y
              theMap.origin.y += off
          Map.grab.spot.x = Map.mouse.x
          Map.grab.spot.y = Map.mouse.y
  
      Lx = theMap.origin.x
      Ly = theMap.origin.y
  
      # ---------------------
      # Draw a textured quad
      # ---------------------
  
      if not Map.textureless:
          bgl.glEnable(bgl.GL_BLEND)
          if Map.glImage.bindcode == 0:
              Map.load_gl_image()
          bgl.glBindTexture(bgl.GL_TEXTURE_2D, Map.glImage.bindcode[0])
          bgl.glEnable(bgl.GL_TEXTURE_2D)
          bgl.glColor4f(1.0, 1.0, 1.0, Map.object[0].opacity)
          bgl.glBegin(bgl.GL_QUADS)
          bgl.glTexCoord2f(0.0, 0.0)
          bgl.glVertex2f(Lx, Ly)
          bgl.glTexCoord2f(1.0, 0.0)
          bgl.glVertex2f(Lx + theMap.width, Ly)
          bgl.glTexCoord2f(1.0, 1.0)
          bgl.glVertex2f(Lx + theMap.width, Ly + theMap.height)
          bgl.glTexCoord2f(0.0, 1.0)
          bgl.glVertex2f(Lx, theMap.height + Ly)
          bgl.glEnd()
          bgl.glDisable(bgl.GL_TEXTURE_2D)
  
      # -----------------------
      # Output text for stats
      # -----------------------
      if Map.action == 'TIME' or Map.action == 'DAY' or Map.showInfo:
          Map.show_text_in_viewport(Map.object[1])
  
      # ---------------------
      # draw the crosshair
      # ---------------------
      x = theMap.width / 2.0
      if crossChange and not Map.lockCrosshair:
          if Map.action != 'Y':
              Sun.SP.Longitude = newLongitude
      longitude = (Sun.SP.Longitude * x / 180.0) + x
  
      bgl.glEnable(bgl.GL_BLEND)
      bgl.glEnable(bgl.GL_LINES)
      bgl.glLineWidth(1.0)
      alpha = 1.0 if Map.action == 'Y' else 0.5
      color = (0.894, 0.741, .510, alpha)
      bgl.glColor4f(color[0], color[1], color[2], color[3])
      bgl.glBegin(bgl.GL_LINES)
      bgl.glVertex2f(Lx + longitude, Ly)
      bgl.glVertex2f(Lx + longitude, Ly + theMap.height)
      bgl.glEnd()
  
      y = theMap.height / 2.0
      if crossChange and not Map.lockCrosshair:
          if Map.action != 'X':
              Sun.SP.Latitude = newLatitude
      latitude = (Sun.SP.Latitude * y / 90.0) + y
  
      alpha = 1.0 if Map.action == 'X' else 0.5
      color = (0.894, 0.741, .510, alpha)
      bgl.glColor4f(color[0], color[1], color[2], color[3])
      bgl.glBegin(bgl.GL_LINES)
      bgl.glVertex2f(Lx, Ly + latitude)
      bgl.glVertex2f(Lx + theMap.width, Ly + latitude)
      bgl.glEnd()
  
      # ---------------------
      # draw the border
      # ---------------------
      bgl.glDisable(bgl.GL_BLEND)
      color = (0.6, 0.6, .6, 1.0)
      bgl.glColor4f(color[0], color[1], color[2], color[3])
      bgl.glBegin(bgl.GL_LINE_LOOP)
      bgl.glVertex2f(Lx, Ly)
      bgl.glVertex2f(Lx + theMap.width, Ly)
      bgl.glVertex2f(Lx + theMap.width, Ly + theMap.height)
      bgl.glVertex2f(Lx, theMap.height + Ly)
      bgl.glVertex2f(Lx, Ly)
      bgl.glEnd()
  
      if not Sun.ShowRiseSet or not Map.lineWidth:
          bgl.glDisable(bgl.GL_LINES)
          bgl.glFlush()
          return
  
      if Map.action == 'G':
          draw_text_region()
  
      # ------------------------
      # draw the sunrise, sunset
      # ------------------------
  
      def draw_angled_line(color, angle, bx, by):
          x = math.cos(angle) * radius
          y = math.sin(angle) * radius
          bgl.glColor4f(color[0], color[1], color[2], color[3])
          bgl.glBegin(bgl.GL_LINES)
          bgl.glVertex2f(bx, by)
          bgl.glVertex2f(bx + x, by + y)
          bgl.glEnd()
  
      px = Lx + longitude
      py = Ly + latitude
  
      radius = 30 + Map.lineWidth * 10
      if Sun.RiseSetOK and Map.lineWidth:
          color = (0.2, 0.6, 1.0, 0.9)
          angle = -(degToRad(Sun.Sunrise.azimuth) - math.pi / 2)
          bgl.glLineWidth(Map.lineWidth)
          draw_angled_line(color, angle, px, py)
  
          color = (0.86, 0.18, 0.18, 0.9)
          angle = -(degToRad(Sun.Sunset.azimuth) - math.pi / 2)
          draw_angled_line(color, angle, px, py)
  
      # ------------------------
      # draw current time line
      # ------------------------
  
      if Map.textureless:
          phi = degToRad(Sun.AzNorth) * -1
      else:
          phi = degToRad(Sun.Azimuth) * -1
      x = math.sin(phi) * math.sin(-Sun.Theta) * (radius + 10)
      y = math.sin(Sun.Theta) * math.cos(phi) * (radius + 10)
      night = (0.24, 0.29, 0.94, 0.9)
      day = (0.85, 0.77, 0.60, 0.9)
      if Sun.SolarNoon.elevation < 0.0:
          color = night
      elif Sun.Elevation >= Sun.Sunrise.elevation:
          if Sun.Time >= Sun.Sunset.time and \
                  Sun.Elevation <= Sun.Sunset.elevation:
              color = night
          else:
              color = day
      else:
          color = night
      bgl.glLineWidth(Map.lineWidth + 1.0)
  
      bgl.glColor4f(color[0], color[1], color[2], color[3])
      bgl.glBegin(bgl.GL_LINES)
      bgl.glVertex2f(px, py)
      bgl.glVertex2f(px + x, py + y)
      bgl.glEnd()
      bgl.glLineWidth(1.0)
      bgl.glDisable(bgl.GL_LINES)
      bgl.glFlush()
  
  # ---------------------------------------------------------------------------
  
  
  def draw_text_region():
      x = Map.object[1].origin.x
      y = Map.object[1].origin.y - 5
  
      bgl.glEnable(bgl.GL_BLEND)
      color = (0.894, 0.741, .510, 0.2)
      bgl.glColor4f(color[0], color[1], color[2], color[3])
      bgl.glBegin(bgl.GL_QUADS)
      bgl.glVertex2f(x, y)
      bgl.glVertex2f(x + Map.object[1].width, y)
      bgl.glVertex2f(x + Map.object[1].width, y + Map.object[1].height)
      bgl.glVertex2f(x, Map.object[1].height + y)
      bgl.glEnd()
      bgl.glDisable(bgl.GL_BLEND)
  
  
  # ---------------------------------------------------------------------------
  
  
  class SunPos_Help(bpy.types.Operator):
      bl_idname = "object.help_operator"
      bl_label = "Map help"
  
      def execute(self, context):
          self.report({'INFO'}, self.message)
          return {'FINISHED'}
  
      def invoke(self, context, event):
          wm = context.window_manager
          return wm.invoke_popup(self, width=400, height=200)
  
      def draw(self, context):
          self.layout.label("Available map commands:")
  
          row = self.layout.row()
          split = row.split(percentage=.27)
          colL = split.column()
          colR = split.column()
          colL.label("Esc or Right Mouse ")
          colR.label("Close map or text.")
          colL.label("Left Mouse")
          colR.label("Move crosshair.")
          colL.label("G or MiddleMouse")
          colR.label("Pan mode. Grab and move map or text.")
          colL.label("Ctrl Middlemouse")
          colR.label("Mouse zoom to point.")
          colL.label("C")
          colR.label("Invert text color.")
          colL.label("R")
          colR.label("Toggle through thickness of the radiating rise/set lines.")
          colL.label("S")
          colR.label("Show statistics data. Move with pan command.")
          self.layout.label("----- The following are changed by moving " +
                            "the mouse or using the scroll wheel.")
          self.layout.label("----- Use Ctrl for coarse increments or Alt for fine.")
          row = self.layout.row()
          split = row.split(percentage=.25)
          colL = split.column()
          colR = split.column()
          colL.label("Scroll wheel")
          colR.label("Zoom to point.")
          colL.label("T")
          colR.label("Time mode.")
          colL.label("D")
          colR.label("Day mode.")
          colL.label("O")
          colR.label("Change map opacity.")
          colL.label("X")
          colR.label("Lock latitude and change longitude.")
          colL.label("Y")
          colR.label("Lock longitude and change latitude.")
          colL.label("A")
          colR.label("Show analemma if object group is set.")
          colL.label("E")
          colR.label("Show ecliptic if object group is set.")
  