d.frame.py 9.1 KB

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