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