Change-Id: If95cb839ad81ca2a842ff7f6926847ac3928d8f2 Former-commit-id: 77e1a4d8f5237e5fae930c1e00589c752f8b3738
400 lines
14 KiB
Python
400 lines
14 KiB
Python
##
|
|
# This software was developed and / or modified by Raytheon Company,
|
|
# pursuant to Contract DG133W-05-CQ-1067 with the US Government.
|
|
#
|
|
# U.S. EXPORT CONTROLLED TECHNICAL DATA
|
|
# This software product contains export-restricted data whose
|
|
# export/transfer/disclosure is restricted by U.S. law. Dissemination
|
|
# to non-U.S. persons whether in the United States or abroad requires
|
|
# an export license or other authorization.
|
|
#
|
|
# Contractor Name: Raytheon Company
|
|
# Contractor Address: 6825 Pine Street, Suite 340
|
|
# Mail Stop B8
|
|
# Omaha, NE 68106
|
|
# 402.291.0100
|
|
#
|
|
# See the AWIPS II Master Rights File ("Master Rights File.pdf") for
|
|
# further licensing information.
|
|
##
|
|
#
|
|
# Name:
|
|
# EditorTools.py
|
|
# GFS1-NHD:A6834.0000-SCRIPT;19
|
|
#
|
|
# Status:
|
|
# DELIVERED
|
|
#
|
|
# History:
|
|
# Revision 19 (DELIVERED)
|
|
# Created: 18-DEC-2008 17:31:05 OBERFIEL
|
|
# Changed label on default button in the QC dialog.
|
|
#
|
|
# Revision 18 (DELIVERED)
|
|
# Created: 01-OCT-2008 22:07:36 OBERFIEL
|
|
# Simplified layout now that customization can be found in
|
|
# the info.cfg files.
|
|
#
|
|
# Revision 17 (DELIVERED)
|
|
# Created: 23-SEP-2008 08:44:54 GILMOREDM
|
|
# Changed so that all TAFs are QC'd regardless of a user's
|
|
# selection in the QC dialog
|
|
#
|
|
# Revision 16 (REVIEW)
|
|
# Created: 19-SEP-2008 12:41:17 GILMOREDM
|
|
# minor change to TafQC
|
|
#
|
|
# Revision 15 (INITIALIZE)
|
|
# Created: 19-SEP-2008 12:20:46 GILMOREDM
|
|
# Slight fix for TafQC
|
|
#
|
|
# Revision 14 (REVIEW)
|
|
# Created: 16-SEP-2008 11:34:37 GILMOREDM
|
|
# Refreshed changes where needed for TafQC
|
|
#
|
|
# Revision 13 (DELIVERED)
|
|
# Created: 18-AUG-2008 12:49:28 OBERFIEL
|
|
# Remove obsolete CM info
|
|
#
|
|
# Revision 12 (REVIEW)
|
|
# Created: 15-MAY-2008 15:11:29 GILMOREDM
|
|
# Configured QC dialog to handle individual site QC
|
|
# configurations
|
|
#
|
|
# Revision 11 (DELIVERED)
|
|
# Created: 27-FEB-2008 10:26:09 GILMOREDM
|
|
# Added functionality to overcome a bug with the
|
|
# Pmw.TimeCounter object
|
|
#
|
|
# Revision 10 (DELIVERED)
|
|
# Created: 23-JAN-2007 12:36:15 OBERFIEL
|
|
# Further dereference of the fcstr information was needed to
|
|
# return forecaster ID.
|
|
#
|
|
# Revision 9 (DELIVERED)
|
|
# Created: 09-SEP-2005 13:53:20 TROJAN
|
|
# spr 7011
|
|
#
|
|
# Revision 8 (DELIVERED)
|
|
# Created: 07-JUL-2005 12:27:35 TROJAN
|
|
# spr 6911
|
|
#
|
|
# Revision 7 (DELIVERED)
|
|
# Created: 07-MAY-2005 11:32:43 OBERFIEL
|
|
# Added Item Header Block
|
|
#
|
|
# Revision 6 (DELIVERED)
|
|
# Created: 04-APR-2005 15:51:05 TROJAN
|
|
# spr 6775
|
|
#
|
|
# Revision 5 (DELIVERED)
|
|
# Created: 24-JAN-2005 15:51:13 TROJAN
|
|
# spr 6259
|
|
#
|
|
# Revision 4 (APPROVED)
|
|
# Created: 01-OCT-2004 13:42:40 TROJAN
|
|
# spr 6400
|
|
#
|
|
# Revision 3 (APPROVED)
|
|
# Created: 09-JUL-2004 19:10:54 OBERFIEL
|
|
# Replaced busy dialogs
|
|
#
|
|
# Revision 2 (APPROVED)
|
|
# Created: 01-JUL-2004 14:59:21 OBERFIEL
|
|
# Update
|
|
#
|
|
# Revision 1 (DELIVERED)
|
|
# Created: 08-JAN-2004 21:29:52 PCMS
|
|
# Initial version
|
|
#
|
|
# Change Document History:
|
|
# 1:
|
|
# Change Document: GFS1-NHD_SPR_7407
|
|
# Action Date: 03-JAN-2009 09:26:51
|
|
# Relationship Type: In Response to
|
|
# Status: TEST
|
|
# Title: AvnFPS: Allow WFOs to update HDF5 climate files
|
|
#
|
|
#
|
|
import logging, time
|
|
from Tkinter import *
|
|
import Pmw
|
|
import Avn, AvnLib, AvnParser, Busy, Globals
|
|
|
|
Help = {
|
|
'title': 'Editor Help',
|
|
'content': """
|
|
The text window key bindings are essentially the same as documented in Tcl/Tk
|
|
text widget manual. See also Brent B. Welch, Practical Programming in Tcl and
|
|
Tk, second edition, pp 385-387). Note, however, that some of the key
|
|
combination may be intercepted by the window manager.
|
|
|
|
Any-Key Insert normal printing characters.
|
|
Control-u Undo changes
|
|
Control-r Redo changes
|
|
Button-1 Sets the insert point, clear the selection, set focus.
|
|
Control-Button-1 Set the insert point without affecting the selection.
|
|
B1-Motion Sweep out a selection from the insert point.
|
|
Double-Button-1 Select the word under the mouse.
|
|
Triple-Button-1 Select the line under the mouse.
|
|
Shift-Button-1 Adjust the end of selection closest to the mouse.
|
|
Shift-B1-Motion Continue to adjust the selection.
|
|
Button-2 Paste the selection, or set the scrolling anchor.
|
|
B2-Motion Scroll the window.
|
|
Key-Left or Control-b Move the cursor left one character. Clear selection.
|
|
Shift-Left Move the cursor and extend the selection.
|
|
Control-Left Move the cursor by words. Clear the selection.
|
|
Control-Shift-Left Move the cursor by words. Extend the selection.
|
|
Key-Right or Control-f Right bindings are analogous to Left bindings.
|
|
Alt-b or Alt-f Same as Control-Left, Control-Right.
|
|
Key-Up or Control-p Move the cursor up one line. Clear the selection.
|
|
Control-Up Move the cursor by paragraph which are group of lines
|
|
separated by a blank line.
|
|
Control-Shift-Up Move the cursor by paragraph. Extend selection.
|
|
Key-Down or Control-n All Down bindings are analogous to Up bindings.
|
|
PageUp, PageDown Move the cursor by one screen. Clear the selection.
|
|
Shift-PageUp, PageDown Move the cursor by one screen. Extend the selection.
|
|
Home or Control-a Move the cursor to line start. Clear the selection.
|
|
Shift-Home Move the cursor to line start. Extend the selection.
|
|
End or Control-e Move the cursor to line end. Clear the selection.
|
|
Shift-End Move the cursor to line end. Extend the selection.
|
|
Control-Home Move the cursor to the beginning of text. Clear the
|
|
selection.
|
|
Control-End Move the cursor to the beginning of text. Extend the
|
|
selection.
|
|
Control-slash Select everything in the text widget.
|
|
Control-backslash Clear the selection.
|
|
Delete Delete the selection, if any. Otherwise delete the
|
|
character to the right of the cursor.
|
|
BackSpace or Control-h Delete the selection, if any. Otherwise delete the
|
|
character to the left of the cursor.
|
|
Control-d Delete character to the right of the cursor.
|
|
Alt-d Delete word to the right of the cursor.
|
|
Control-k Delete from cursor to the end of the line. If you are
|
|
at the end of the line, delete the newline character.
|
|
Control-o Insert a newline but do not advance the cursor.
|
|
Alt-Delete Delete the word to the left of the cursor.
|
|
Alt-Backspace
|
|
Control-t Transpose the characters on either side of the cursor.
|
|
"""
|
|
}
|
|
_Logger = logging.getLogger(__name__)
|
|
|
|
#############################################################################
|
|
class SendDialog(Pmw.Dialog):
|
|
def __init__(self, parent, **kw):
|
|
Pmw.Dialog.__init__(self, parent.interior())
|
|
self.title(Avn.Name + ' Send')
|
|
self.withdraw()
|
|
self.configure(buttons=('OK', 'Cancel'),
|
|
defaultbutton='OK',
|
|
command=self.__execute)
|
|
self._time = Pmw.TimeCounter(self.interior(),
|
|
labelpos='w',
|
|
label_text='Transmit at:\nHH:MM:SS',
|
|
min='00:00:00',
|
|
max='23:59:59',
|
|
value='00:00:00',
|
|
)
|
|
self._time.pack(padx=10, pady=5)
|
|
self._slist = Pmw.ScrolledListBox(self.interior(),
|
|
labelpos='n',
|
|
label_text='Responsible for this forecast',
|
|
)
|
|
self._slist.pack()
|
|
self.__load()
|
|
self._position = 'first+%d+%d' % (parent.winfo_rootx()+30, \
|
|
parent.winfo_rooty()+30)
|
|
|
|
def __load(self):
|
|
try:
|
|
self._fcstdict = AvnParser.getForecasters()
|
|
if not self._fcstdict:
|
|
raise ValueError, 'Empty file'
|
|
fcstnames = self._fcstdict.keys()
|
|
fcstnames.sort()
|
|
self._slist.setlist(fcstnames)
|
|
except IOError:
|
|
msg = 'Cannot open forecaster file'
|
|
_Logger.exception(msg)
|
|
Busy.showerror(msg, self.interior())
|
|
raise SystemExit
|
|
except (ValueError, IndexError):
|
|
msg = 'Invalid entry in forecaster file'
|
|
_Logger.exception(msg)
|
|
Busy.showerror(msg, self.interior())
|
|
raise SystemExit
|
|
|
|
def __execute(self, result=None):
|
|
if result == 'OK':
|
|
now = time.time()
|
|
self.time = self._getTime() + (now//86400.0)*86400.0
|
|
if self.time < now - 43200:
|
|
self.time += 86400
|
|
if not (now - 600 < self.time < now + 10800):
|
|
if not Busy.askokcancel('Are you sure?', self.interior()):
|
|
return
|
|
try:
|
|
fcstname = self._slist.getcurselection()[0]
|
|
self.id = self._fcstdict[fcstname].get('id',0)
|
|
except IndexError:
|
|
Busy.showerror('Select name', self.interior())
|
|
else:
|
|
self.time, self.id = None, 0
|
|
self.deactivate()
|
|
|
|
def _getTime(self):
|
|
# this solves a bug in the Pmw.TimeCounter object not returning correct
|
|
# time if values are updated in the entryfield only (no arrows are clicked)
|
|
if self.thetime != self._time.getint():
|
|
inttime = self._time.getint()
|
|
else:
|
|
thetime = [int(self._time.component(comp).get()) for comp in ['hourentry','minuteentry','secondentry']]
|
|
inttime = thetime[0]*3600 + thetime[1]*60 + thetime[2]
|
|
return inttime
|
|
|
|
def activate(self, kind, headertime, bbb):
|
|
if bbb and bbb[0] != ' ':
|
|
t = time.time()
|
|
else:
|
|
t = AvnLib.getXmitTime(kind, headertime)
|
|
self._time.setvalue(time.strftime('%H:%M:00', time.gmtime(t)))
|
|
self.thetime = self._time.getint() # used in self._getTime() method
|
|
ix = list(self._slist.get()).index(Globals.Forecaster)
|
|
self._slist.selection_set(ix)
|
|
self._slist.see(ix)
|
|
Busy.Manager.busy(None, self.component('hull'))
|
|
Pmw.Dialog.activate(self, geometry=self._position)
|
|
Busy.Manager.notbusy()
|
|
return self.time, self.id
|
|
|
|
#############################################################################
|
|
class QCDialog(Pmw.Dialog):
|
|
def __init__(self, parent, **kw):
|
|
Pmw.Dialog.__init__(self, parent)
|
|
self.withdraw()
|
|
self.title(Avn.Name + ' QC')
|
|
|
|
self.configure(buttons=('Apply', 'Close'),
|
|
defaultbutton='Apply',
|
|
command=self.__execute,
|
|
)
|
|
|
|
self.parent = parent
|
|
self._tkProduct = StringVar()
|
|
|
|
self.radiobuttons = Pmw.RadioSelect(self.interior(),
|
|
buttontype='checkbutton',
|
|
orient='vertical',
|
|
labelpos='n',
|
|
label_text='QC Options ',
|
|
hull_borderwidth=0,
|
|
padx=5,
|
|
pady=1,
|
|
hull_relief='ridge',
|
|
)
|
|
|
|
self.KeysLabels = [('currentwx', 'Current Wx'), ('climate', 'Climate'), ('impact', 'Impact')]
|
|
self.radiobuttons.pack(side='left', expand='no', padx=5)
|
|
for item, label in self.KeysLabels:
|
|
self.radiobuttons.add(item, text=label)
|
|
self.radiobuttons.invoke(item)
|
|
|
|
self.resizable(width='false', height='true')
|
|
self._position = 'first+%d+%d' % (parent.winfo_rootx()+30, \
|
|
parent.winfo_rooty()+30)
|
|
|
|
def __execute(self, result):
|
|
self.items = dict([(a[0],0) for a in self.KeysLabels])
|
|
if result == 'Apply':
|
|
for item in self.radiobuttons.getcurselection():
|
|
self.items[item] = 1
|
|
|
|
self.deactivate()
|
|
|
|
def activate(self):
|
|
Busy.Manager.busy(None, self.component('hull'))
|
|
Pmw.Dialog.activate(self, geometry=self._position)
|
|
Busy.Manager.notbusy()
|
|
return self.items
|
|
|
|
###############################################################################
|
|
class TkIndex:
|
|
# Used to convert offsets to Tk text indices
|
|
def __init__(self, text):
|
|
self.newlines = []
|
|
self.length = len(text)
|
|
i = 0
|
|
while 1:
|
|
try:
|
|
k = text.index('\n', i)
|
|
self.newlines.append((i, k))
|
|
i = k+1
|
|
except ValueError:
|
|
break
|
|
|
|
def ix(self, offset, length):
|
|
if offset < 0 or length <= 0 or offset > self.length:
|
|
raise ValueError, 'Invalid arguments %d %d' % (offset, length)
|
|
start, end = '1.0', None
|
|
n = 1
|
|
for i, k in self.newlines:
|
|
if i <= offset <= k:
|
|
start = '%d.%d' % (n, offset-i)
|
|
break
|
|
n += 1
|
|
offset += length
|
|
for i, k in self.newlines[n-1:]:
|
|
if i <= offset <= k:
|
|
end = '%d.%d' % (n, offset-i)
|
|
break
|
|
n += 1
|
|
if end is None:
|
|
end = 'end'
|
|
return start, end
|
|
|
|
#############################################################################
|
|
def validateBBB(text):
|
|
# validator used by Pmw.EntryField
|
|
if not text:
|
|
return Pmw.OK
|
|
if len(text) < 3:
|
|
return Pmw.PARTIAL
|
|
elif len(text) > 3:
|
|
return Pmw.ERROR
|
|
try:
|
|
bb = text[:2].upper()
|
|
if not bb in ('AA', 'CC', 'RR'):
|
|
return Pmw.ERROR
|
|
if not text[2].isalpha():
|
|
return Pmw.ERROR
|
|
return Pmw.OK
|
|
except:
|
|
return Pmw.ERROR
|
|
|
|
#############################################################################
|
|
def validateHeaderTime(text):
|
|
# validator used by Pmw.EntryField
|
|
if len(text) < 6:
|
|
return Pmw.PARTIAL
|
|
try:
|
|
day = int(text[:2])
|
|
hour = int(text[2:4])
|
|
minute = int(text[4:])
|
|
if day < 1 or day > 31:
|
|
return Pmw.ERROR
|
|
if hour < 0 or hour > 23:
|
|
return Pmw.ERROR
|
|
if minute < 0 or minute > 59:
|
|
return Pmw.ERROR
|
|
return Pmw.OK
|
|
except:
|
|
return Pmw.ERROR
|
|
|
|
def xmitServerOK():
|
|
# find server name
|
|
for key in Globals.ServerStatus:
|
|
if key.startswith('XMIT'):
|
|
return time.time() - Globals.ServerStatus[key] < 60.0
|
|
else:
|
|
return False
|