d.frame.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. #!/usr/bin/env python
  2. ############################################################################
  3. #
  4. # MODULE: d.frame
  5. # AUTHOR(S): Martin Landa <landa.martin gmail.com>
  6. # Based on d.frame from GRASS 6
  7. # PURPOSE: Manages display frames on the user's graphics monitor
  8. # COPYRIGHT: (C) 2014-2015 by Martin Landa, and the GRASS Development Team
  9. #
  10. # This program is free software; you can redistribute it and/or modify
  11. # it under the terms of the GNU General Public License as published by
  12. # the Free Software Foundation; either version 2 of the License, or
  13. # (at your option) any later version.
  14. #
  15. # This program is distributed in the hope that it will be useful,
  16. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  18. # GNU General Public License for more details.
  19. #
  20. ############################################################################
  21. #%module
  22. #% description: Manages display frames on the user's graphics monitor.
  23. #% keyword: display
  24. #% keyword: graphics
  25. #% keyword: monitors
  26. #% keyword: frame
  27. #% overwrite: yes
  28. #%end
  29. #%flag
  30. #% key: c
  31. #% description: Create a new frame if doesn't exist and select
  32. #%end
  33. #%flag
  34. #% key: e
  35. #% description: Remove all frames, erase the screen and exit
  36. #% suppress_required: yes
  37. #%end
  38. #%flag
  39. #% key: p
  40. #% description: Print name of current frame and exit
  41. #% suppress_required: yes
  42. #%end
  43. #%flag
  44. #% key: a
  45. #% description: Print names of all frames including 'at' position and exit
  46. #% suppress_required: yes
  47. #%end
  48. #%option
  49. #% key: frame
  50. #% type: string
  51. #% required: yes
  52. #% multiple: no
  53. #% key_desc: name
  54. #% description: Frame to be selected or created (if -c flag is given)
  55. #%end
  56. #%option
  57. #% key: at
  58. #% type: double
  59. #% required: no
  60. #% multiple: no
  61. #% key_desc: bottom,top,left,right
  62. #% label: Screen coordinates in percent where to place the frame (0,0 is lower-left)
  63. #% options: 0-100
  64. #% description: Implies only when -c or --overwrite flag is given
  65. #%end
  66. import os
  67. import sys
  68. from grass.script.core import parser, read_command, fatal, debug, run_command, gisenv, warning, parse_command
  69. # check if monitor is running
  70. def check_monitor():
  71. return read_command('d.mon', flags='p', quiet=True).strip()
  72. # read monitor file and return list of lines
  73. # TODO: replace by d.info (see #2577)
  74. def read_monitor_file(monitor, ftype='env'):
  75. mfile = check_monitor_file(monitor, ftype)
  76. try:
  77. fd = open(mfile, 'r')
  78. except IOError as e:
  79. fatal(_("Unable to get monitor info. %s"), e)
  80. lines = []
  81. for line in fd.readlines():
  82. lines.append(line)
  83. fd.close()
  84. return lines
  85. # check if monitor file exists
  86. def check_monitor_file(monitor, ftype='env'):
  87. mfile = parse_command('d.mon', flags='g').get(ftype, None)
  88. if mfile is None or not os.path.isfile(mfile):
  89. fatal(_("Unable to get monitor info (no %s found)") % var)
  90. return mfile
  91. # write new monitor file
  92. def write_monitor_file(monitor, lines, ftype='env'):
  93. mfile = check_monitor_file(monitor, ftype)
  94. try:
  95. fd = open(mfile, 'w')
  96. except IOError as e:
  97. fatal(_("Unable to get monitor info. %s"), e)
  98. fd.writelines(lines)
  99. fd.close()
  100. # remove all frames and erase screen
  101. def erase(monitor):
  102. # remove frames
  103. lines = []
  104. for line in read_monitor_file(monitor):
  105. if 'FRAME' not in line:
  106. lines.append(line)
  107. write_monitor_file(monitor, lines)
  108. # erase screen
  109. run_command('d.erase')
  110. # find frame for given monitor
  111. def find_frame(monitor, frame):
  112. for line in read_monitor_file(monitor):
  113. if 'FRAME' in line:
  114. if get_frame_name(line) == frame:
  115. return True
  116. return False
  117. # print frames name(s) to stdout
  118. def print_frames(monitor, current_only=False, full=False):
  119. for line in read_monitor_file(monitor):
  120. if 'FRAME' not in line:
  121. continue
  122. if current_only and line.startswith('#'):
  123. continue
  124. sys.stdout.write(get_frame_name(line))
  125. if full:
  126. sys.stdout.write(':' + line.split('=', 1)[1].rsplit('#', 1)[0])
  127. sys.stdout.write('\n')
  128. # get frame name from line
  129. def get_frame_name(line):
  130. return line.rstrip('\n').rsplit('#', 1)[1].strip(' ')
  131. def calculate_frame(frame, at, width, height):
  132. """Calculate absolute position of the frame from percentages
  133. at is from bottom left in percents (bottom,top,left,right)
  134. output is in pixels from top left (top,bottom,left,right)
  135. This function does also the necessary formating.
  136. >>> calculate_frame('apple', "0,49.8,0,50.2", 500, 500)
  137. 'GRASS_RENDER_FRAME=251,500,0,251 # apple\\n'
  138. >>> calculate_frame('orange', "50.2,0,49.8,100", 500, 500)
  139. 'GRASS_RENDER_FRAME=500,249,249,500 # orange\\n'
  140. >>> calculate_frame('odd_number', "0,49.8,0,50.2", 367, 367)
  141. 'GRASS_RENDER_FRAME=184,367,0,184 # odd_number\\n'
  142. The following would give 182,367,0,184 if we would be truncating
  143. to integers instead of rounding, but that would be wrong because
  144. height of the frame would be 367 minus 182 which is 185 while
  145. 0.502 times 367 is 184.234 which fits with the (correct) width of
  146. the frame which is 184.
  147. >>> calculate_frame('test_truncating_bug', "0,50.2,0,50.2", 367, 367)
  148. 'GRASS_RENDER_FRAME=183,367,0,184 # test_truncating_bug\\n'
  149. """
  150. try:
  151. b, t, l, r = list(map(float, at.split(',')))
  152. except:
  153. fatal(_("Invalid frame position: %s") % at)
  154. top = round(height - (t / 100. * height))
  155. bottom = round(height - (b / 100. * height))
  156. left = round(l / 100. * width)
  157. right = round(r / 100. * width)
  158. # %d for floats works like int() - truncates
  159. return 'GRASS_RENDER_FRAME=%d,%d,%d,%d # %s%s' % \
  160. (top, bottom, left, right, frame, '\n')
  161. # create new frame
  162. def create_frame(monitor, frame, at, overwrite=False):
  163. lines = read_monitor_file(monitor)
  164. # get width and height of the monitor
  165. width = height = -1
  166. for line in lines:
  167. try:
  168. if 'WIDTH' in line:
  169. width = int(line.split('=', 1)[1].rsplit(' ', 1)[0])
  170. elif 'HEIGHT' in line:
  171. height = int(line.split('=', 1)[1].rsplit(' ', 1)[0])
  172. except:
  173. pass
  174. if width < 0 or height < 0:
  175. fatal(_("Invalid monitor size: %dx%d") % (width, height))
  176. if not overwrite:
  177. lines.append(calculate_frame(frame, at, width, height))
  178. else:
  179. for idx in range(len(lines)):
  180. line = lines[idx]
  181. if 'FRAME' not in line:
  182. continue
  183. if get_frame_name(line) == frame:
  184. lines[idx] = calculate_frame(frame, at, width, height)
  185. write_monitor_file(monitor, lines)
  186. # select existing frame
  187. def select_frame(monitor, frame):
  188. lines = read_monitor_file(monitor)
  189. for idx in range(len(lines)):
  190. line = lines[idx]
  191. if 'FRAME' not in line:
  192. continue
  193. if get_frame_name(line) == frame:
  194. if line.startswith('#'):
  195. lines[idx] = line.lstrip('# ') # un-comment line
  196. elif not line.startswith('#'):
  197. lines[idx] = '# ' + line # comment-line
  198. write_monitor_file(monitor, lines)
  199. def main():
  200. # get currently selected monitor
  201. monitor = check_monitor()
  202. if not monitor:
  203. fatal(_("No graphics device selected. Use d.mon to select graphics device."))
  204. if flags['e']:
  205. # remove frames and erase monitor and exit
  206. erase(monitor)
  207. return
  208. if flags['p']:
  209. # print current frame and exit
  210. print_frames(monitor, current_only=True)
  211. return
  212. if flags['a']:
  213. # print all frames including their position and exit
  214. print_frames(monitor, current_only=False, full=True)
  215. return
  216. found = find_frame(monitor, options['frame'])
  217. if not found:
  218. if not flags['c']:
  219. fatal(_("Frame <%s> doesn't exist, exiting. "
  220. "To create a new frame use '-c' flag.") % options['frame'])
  221. else:
  222. if not options['at']:
  223. fatal(_("Required parameter <%s> not set") % "at")
  224. # create new frame if not exists
  225. create_frame(monitor, options['frame'], options['at'])
  226. else:
  227. if os.getenv('GRASS_OVERWRITE', '0') == '1':
  228. warning(_("Frame <%s> already exists and will be overwritten") % options['frame'])
  229. create_frame(monitor, options['frame'], options['at'], overwrite=True)
  230. else:
  231. if options['at']:
  232. warning(
  233. _("Frame <%s> already found. An existing frame can be overwritten by '%s' flag.") %
  234. (options['frame'], "--overwrite"))
  235. # select givenframe
  236. select_frame(monitor, options['frame'])
  237. if __name__ == "__main__":
  238. if len(sys.argv) == 2 and sys.argv[1] == '--doctest':
  239. import doctest
  240. sys.exit(doctest.testmod().failed)
  241. options, flags = parser()
  242. sys.exit(main())