d.frame.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  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. # i18N
  70. import gettext
  71. gettext.install('grassmods', os.path.join(os.getenv("GISBASE"), 'locale'))
  72. # check if monitor is running
  73. def check_monitor():
  74. return read_command('d.mon', flags='p', quiet=True).strip()
  75. # read monitor file and return list of lines
  76. # TODO: replace by d.info (see #2577)
  77. def read_monitor_file(monitor, ftype='env'):
  78. mfile = check_monitor_file(monitor, ftype)
  79. try:
  80. fd = open(mfile, 'r')
  81. except IOError as e:
  82. fatal(_("Unable to get monitor info. %s"), e)
  83. lines = []
  84. for line in fd.readlines():
  85. lines.append(line)
  86. fd.close()
  87. return lines
  88. # check if monitor file exists
  89. def check_monitor_file(monitor, ftype='env'):
  90. mfile = parse_command('d.mon', flags='g').get(ftype, None)
  91. if mfile is None or not os.path.isfile(mfile):
  92. fatal(_("Unable to get monitor info (no %s found)") % var)
  93. return mfile
  94. # write new monitor file
  95. def write_monitor_file(monitor, lines, ftype='env'):
  96. mfile = check_monitor_file(monitor, ftype)
  97. try:
  98. fd = open(mfile, 'w')
  99. except IOError as e:
  100. fatal(_("Unable to get monitor info. %s"), e)
  101. fd.writelines(lines)
  102. fd.close()
  103. # remove all frames and erase screen
  104. def erase(monitor):
  105. # remove frames
  106. lines = []
  107. for line in read_monitor_file(monitor):
  108. if 'FRAME' not in line:
  109. lines.append(line)
  110. write_monitor_file(monitor, lines)
  111. # erase screen
  112. run_command('d.erase')
  113. # find frame for given monitor
  114. def find_frame(monitor, frame):
  115. for line in read_monitor_file(monitor):
  116. if 'FRAME' in line:
  117. if get_frame_name(line) == frame:
  118. return True
  119. return False
  120. # print frames name(s) to stdout
  121. def print_frames(monitor, current_only=False, full=False):
  122. for line in read_monitor_file(monitor):
  123. if 'FRAME' not in line:
  124. continue
  125. if current_only and line.startswith('#'):
  126. continue
  127. sys.stdout.write(get_frame_name(line))
  128. if full:
  129. sys.stdout.write(':' + line.split('=', 1)[1].rsplit('#', 1)[0])
  130. sys.stdout.write('\n')
  131. # get frame name from line
  132. def get_frame_name(line):
  133. return line.rstrip('\n').rsplit('#', 1)[1].strip(' ')
  134. def calculate_frame(frame, at, width, height):
  135. """Calculate absolute position of the frame from percentages
  136. at is from bottom left in percents (bottom,top,left,right)
  137. output is in pixels from top left (top,bottom,left,right)
  138. This function does also the necessary formating.
  139. >>> calculate_frame('apple', "0,49.8,0,50.2", 500, 500)
  140. 'GRASS_RENDER_FRAME=251,500,0,251 # apple\\n'
  141. >>> calculate_frame('orange', "50.2,0,49.8,100", 500, 500)
  142. 'GRASS_RENDER_FRAME=500,249,249,500 # orange\\n'
  143. >>> calculate_frame('odd_number', "0,49.8,0,50.2", 367, 367)
  144. 'GRASS_RENDER_FRAME=184,367,0,184 # odd_number\\n'
  145. The following would give 182,367,0,184 if we would be truncating
  146. to integers instead of rounding, but that would be wrong because
  147. height of the frame would be 367 minus 182 which is 185 while
  148. 0.502 times 367 is 184.234 which fits with the (correct) width of
  149. the frame which is 184.
  150. >>> calculate_frame('test_truncating_bug', "0,50.2,0,50.2", 367, 367)
  151. 'GRASS_RENDER_FRAME=183,367,0,184 # test_truncating_bug\\n'
  152. """
  153. try:
  154. b, t, l, r = list(map(float, at.split(',')))
  155. except:
  156. fatal(_("Invalid frame position: %s") % at)
  157. top = round(height - (t / 100. * height))
  158. bottom = round(height - (b / 100. * height))
  159. left = round(l / 100. * width)
  160. right = round(r / 100. * width)
  161. # %d for floats works like int() - truncates
  162. return 'GRASS_RENDER_FRAME=%d,%d,%d,%d # %s%s' % \
  163. (top, bottom, left, right, frame, '\n')
  164. # create new frame
  165. def create_frame(monitor, frame, at, overwrite=False):
  166. lines = read_monitor_file(monitor)
  167. # get width and height of the monitor
  168. width = height = -1
  169. for line in lines:
  170. try:
  171. if 'WIDTH' in line:
  172. width = int(line.split('=', 1)[1].rsplit(' ', 1)[0])
  173. elif 'HEIGHT' in line:
  174. height = int(line.split('=', 1)[1].rsplit(' ', 1)[0])
  175. except:
  176. pass
  177. if width < 0 or height < 0:
  178. fatal(_("Invalid monitor size: %dx%d") % (width, height))
  179. if not overwrite:
  180. lines.append(calculate_frame(frame, at, width, height))
  181. else:
  182. for idx in range(len(lines)):
  183. line = lines[idx]
  184. if 'FRAME' not in line:
  185. continue
  186. if get_frame_name(line) == frame:
  187. lines[idx] = calculate_frame(frame, at, width, height)
  188. write_monitor_file(monitor, lines)
  189. # select existing frame
  190. def select_frame(monitor, frame):
  191. lines = read_monitor_file(monitor)
  192. for idx in range(len(lines)):
  193. line = lines[idx]
  194. if 'FRAME' not in line:
  195. continue
  196. if get_frame_name(line) == frame:
  197. if line.startswith('#'):
  198. lines[idx] = line.lstrip('# ') # un-comment line
  199. elif not line.startswith('#'):
  200. lines[idx] = '# ' + line # comment-line
  201. write_monitor_file(monitor, lines)
  202. def main():
  203. # get currently selected monitor
  204. monitor = check_monitor()
  205. if not monitor:
  206. fatal(_("No graphics device selected. Use d.mon to select graphics device."))
  207. if flags['e']:
  208. # remove frames and erase monitor and exit
  209. erase(monitor)
  210. return
  211. if flags['p']:
  212. # print current frame and exit
  213. print_frames(monitor, current_only=True)
  214. return
  215. if flags['a']:
  216. # print all frames including their position and exit
  217. print_frames(monitor, current_only=False, full=True)
  218. return
  219. found = find_frame(monitor, options['frame'])
  220. if not found:
  221. if not flags['c']:
  222. fatal(_("Frame <%s> doesn't exist, exiting. "
  223. "To create a new frame use '-c' flag.") % options['frame'])
  224. else:
  225. if not options['at']:
  226. fatal(_("Required parameter <%s> not set") % "at")
  227. # create new frame if not exists
  228. create_frame(monitor, options['frame'], options['at'])
  229. else:
  230. if os.getenv('GRASS_OVERWRITE', '0') == '1':
  231. warning(_("Frame <%s> already exists and will be overwritten") % options['frame'])
  232. create_frame(monitor, options['frame'], options['at'], overwrite=True)
  233. else:
  234. if options['at']:
  235. warning(
  236. _("Frame <%s> already found. An existing frame can be overwritten by '%s' flag.") %
  237. (options['frame'], "--overwrite"))
  238. # select givenframe
  239. select_frame(monitor, options['frame'])
  240. if __name__ == "__main__":
  241. if len(sys.argv) == 2 and sys.argv[1] == '--doctest':
  242. import doctest
  243. _ = str # doctest gettext workaround
  244. sys.exit(doctest.testmod().failed)
  245. options, flags = parser()
  246. sys.exit(main())