#Notices:
#Copyright 2017 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved.
#Disclaimers
#No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS, RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM USE OF THE SUBJECT SOFTWARE. FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS."
#Waiver and Indemnity: RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT. IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW. RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
##########################
#
#
# @Author: Grant Mercer
# @Author: Nathan Qian
##########################
from tools.vocalDataBlock import VocalDataBlock
import matplotlib
matplotlib.use('tkAgg')
from Tkconstants import RIGHT, END, DISABLED
from Tkinter import Tk, Label, Toplevel, Menu, PanedWindow, \
Frame, Button, HORIZONTAL, BOTH, VERTICAL, TOP, LEFT, \
SUNKEN, StringVar, Text, IntVar
import logging
from sys import platform as _platform
from tkColorChooser import askcolor
import tkFileDialog
import tkMessageBox
import webbrowser
from os.path import dirname
from attributesdialog import AttributesDialog
from bokeh.colors import white
from constants import Plot, PATH, ICO, CONF
import constants
from extractdialog import ExtractDialog
from importdialog import ImportDialog
from settingsdialog import SettingsDialog
from log.log import logger, error_check
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from plot.plot_depolar_ratio import render_depolarized
from plot.plot_backscattered import render_backscattered
from plot.plot_vfm import render_vfm
from plot.plot_iwp import render_iwp
from plot.plot_horiz_avg import render_horiz_avg
from plot.plot_aerosol_subtype import render_aerosol_subtype
from polygon.manager import ShapeManager
from tools.linearalgebra import distance
from tools.navigationtoolbar import NavigationToolbar2CALIPSO
from tools.optionmenu import ShapeOptionMenu
from tools.tools import Catcher, center
from toolswindow import ToolsWindow
from db import db
from PIL import ImageTk
from tools.tooltip import create_tool_tip
import matplotlib.image as mpimg
[docs]class Calipso(object):
"""
Main class of the application, handles all GUI related events as well as
creating other GUI windows such as the toolbar or import dialog
"""
############################################################
# Initialization functions
def __init__(self, r):
self.load_img = ImageTk.PhotoImage(file=PATH + '/ico/load.png')
self.save_img = ImageTk.PhotoImage(file=PATH + '/ico/save.png')
self.__root = r # Root of program
self.__file = '' # Current file in use
self.xrange = self.yrange = (0, 1000) # X and Y range for scrolling plot
self.panx = self.pany = 0 # Pan values for shifting map
self.plot = Plot.baseplot # Current selected plot
self.__label_file_dialog = None
self.new_file_flag = False
self.option_menu = None
self.shape_var = StringVar()
self.__data_block = VocalDataBlock('Empty')
self.plot_type = IntVar()
self.width = self.__root.winfo_screenwidth()
self.height = self.__root.winfo_screenheight()
logger.info('Screen resolution: ' + str(self.width) + 'x' + str(self.height))
base_pane = PanedWindow()
base_pane.pack(fill=BOTH, expand=1)
sectioned_pane = PanedWindow(orient=VERTICAL)
base_pane.add(sectioned_pane)
top_paned_window = PanedWindow(sectioned_pane, orient=HORIZONTAL)
sectioned_pane.add(top_paned_window)
# Frame to hold dialog for browsing files
self.__dialog_frame = Frame(top_paned_window)
self.__dialog_frame.pack(side=LEFT)
self.__dialog_shape_frame = Frame(top_paned_window)
self.__dialog_shape_frame.pack(side=RIGHT)
# Bottom half the screen
bottom_paned_window = PanedWindow(sectioned_pane)
sectioned_pane.add(bottom_paned_window)
# The frame on which we will set out canvas for drawing etc.
self.__drawplot_frame = Frame(bottom_paned_window,
width=constants.WIDTH,
height=constants.HEIGHT)
# Matplotlib backend objects
self.__parent_fig = Figure(figsize=(16, 11))
self.__fig = self.__parent_fig.add_subplot(1, 1, 1)
self.__parent_fig.set_tight_layout(True)
self.__drawplot_canvas = FigureCanvasTkAgg(self.__parent_fig,
master=self.__drawplot_frame)
# Create ToolsWindow class and pass itself + the root
logger.info('Creating ToolsWindow')
self.__child = ToolsWindow(self.__drawplot_canvas, self, r)
logger.info('Creating ShapeManager')
self.__shapemanager = ShapeManager(self.__fig, self.__drawplot_canvas,
self)
logger.info('Binding matplotlib backend to canvas and frame')
self.__toolbar = NavigationToolbar2CALIPSO(self,
self.__drawplot_canvas,
self.__child.coordinate_frame)
# pack and display canvas
self.__drawplot_canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
self.__drawplot_frame.pack()
self.__root.protocol('WM_DELETE_WINDOW', self.close)
[docs] def setup_window(self):
"""
Sets the title of root and places window on screen
"""
self.__root.title("CALIPSO Visualization Tool (VOCAL)")
sw = self.__root.winfo_screenwidth()
sh = self.__root.winfo_screenheight()
x = (sw - constants.WIDTH) / 2
y = (sh - constants.HEIGHT) / 2
self.__root.geometry('%dx%d+%d+%d' % (constants.WIDTH, constants.HEIGHT, x, y))
# the child is designed to appear off to the right of the parent window, so the x location
# is parentWindow.x + the length of the window + padding, and y is simply the parentWindow.y
# plus a fourth the distance of the window
if _platform == "linux" or _platform == "linux2":
logger.info("Linux system detected")
self.__child.geometry('%dx%d+%d+%d' % (
constants.CHILDWIDTH + 50, constants.CHILDHEIGHT, x + constants.WIDTH,
y + constants.HEIGHT / 4))
elif _platform == 'darwin':
logger.info('OSX system detected')
self.__child.geometry('%dx%d+%d+%d' % (
constants.CHILDWIDTH + 75, constants.CHILDHEIGHT + 50, x + constants.WIDTH,
y + constants.HEIGHT / 4))
else:
# if the main window and the tools window's width are greater than the screen width
if constants.WIDTH + constants.CHILDHEIGHT > sw:
# place the tools window 10/11 from the left of the screen
self.__child.geometry('%dx%d+%d+%d' % (
constants.CHILDWIDTH, constants.CHILDHEIGHT, 10 * sw / 11 - constants.CHILDWIDTH / 2,
y + constants.HEIGHT / 4))
else:
# place the tools window 50 units from the the right end of the main window
self.__child.geometry('%dx%d+%d+%d' % (
constants.CHILDWIDTH, constants.CHILDHEIGHT, x + constants.WIDTH + 50, y + constants.HEIGHT / 4))
logger.info("Placed toolswindow at: " + str(self.__child.geometry()))
self.__root.wm_iconbitmap(ICO)
self.__child.wm_iconbitmap(ICO)
[docs] def setup_main_screen(self):
"""
Setup the top GUI, initialize toolbar window and set the plot to a blank image
"""
logger.info('Creating upper program GUI')
# Create label , entry box and browse button
label_file = Label(self.__dialog_frame, text="File:")
self.__label_file_dialog = Label(self.__dialog_frame, width=50, justify=LEFT,
bg=white, relief=SUNKEN)
browse_button = Button(self.__dialog_frame, text='Browse', width=10,
command=self.import_file)
label_file.grid(row=1, column=0)
self.__label_file_dialog.grid(row=1, column=1, padx=10)
browse_button.grid(row=1, column=3)
# Load shapes from JSON
load_button = \
Button(self.__dialog_shape_frame, image=self.load_img, width=30, height=30, command=self.load)
load_button.pack(side=RIGHT, padx=2)
create_tool_tip(load_button, 'Load JSON')
# Save shapes as JSON
save_button = \
Button(self.__dialog_shape_frame, image=self.save_img, width=30, height=30, command=self.save_as_json)
save_button.pack(side=RIGHT, padx=2)
create_tool_tip(save_button, 'Save selected\n objects to\n JSON')
self.option_menu = ShapeOptionMenu(self.__dialog_shape_frame, self.shape_var, "",
command=self.select_shape)
self.option_menu.bind("<ButtonPress-1>", self.update_shape_optionmenu)
self.option_menu.pack(side=RIGHT, padx=10)
label_shapes = Label(self.__dialog_shape_frame, text="Select")
label_shapes.pack(side=RIGHT)
self.__child.setup_toolbar_buttons()
logger.info('Setting initial plot')
self.set_plot(Plot.baseplot, 0)
# end Initialization functions
############################################################
############################################################
# Exclusive menu bar & top gui functions
def select_shape(self, tag):
self.__shapemanager.select_from_tag(tag)
[docs] def import_file(self):
"""
Load an HDF file for use with displaying backscatter and depolarized images
"""
logger.info('Importing HDF file')
# function to import HDF file used my open and browse
file_types = [('CALIPSO Data files', '*.hdf'), ('All files', '*')]
dlg = tkFileDialog.Open(filetypes=file_types, initialdir=CONF.session_hdf.dir())
fl = dlg.show()
if fl != '':
if self.__file is not None and fl is not self.__file:
self.new_file_flag = True
self.__file = fl
self.__data_block = VocalDataBlock(fl)
segments = self.__file.rpartition('/')
self.__label_file_dialog.config(width=50, bg=white, relief=SUNKEN, justify=LEFT,
text=segments[2])
CONF.session_hdf.change(fl)
[docs] def export_db(self, only_selected=False):
"""
Notify the database that a save is taking place, the
db will then save all polygons present on the screen
"""
logger.info('Notifying database to save with select flag %s' % (str(only_selected)))
success = self.__shapemanager.save_db(only_selected)
if success:
logger.info('Success, saved to db')
tkMessageBox.showinfo('database', 'Objects saved to database')
else:
logger.error('No objects to be saved')
tkMessageBox.showerror('database', 'No objects to be saved')
@staticmethod
[docs] def create_db():
"""
Opens a file browser to create a database file for polygons
:return:
"""
options = dict()
options['defaultextension'] = '.db'
options['filetypes'] = [('CALIPSO Databases', '*.db'), ('All files', '*')]
options['initialdir'] = CONF.session_db.dir()
options['title'] = 'Select Database to Use'
options['initialfile'] = 'CALIPSOdb.db'
fl = tkFileDialog.asksaveasfilename(**options)
if fl != '':
db.set_path(fl)
CONF.session_db.change(fl)
@staticmethod
[docs] def select_db():
"""
Opens a file browser to select a database if one is not already chosen
:param iscommand: Make True if we are executing from the menu command
:return: Return 0 if no file was selected
"""
options = dict()
options['defaultextension'] = '.db'
options['filetypes'] = [('CALIPSO Databases', '*.db'), ('All files', '*')]
options['initialdir'] = CONF.session_db.dir()
options['title'] = 'Select Database to Use'
fl = tkFileDialog.Open(**options)
fl = fl.show()
print(fl)
if fl != '':
db.set_path(fl)
CONF.session_db.change(fl)
@staticmethod
[docs] def import_json_db():
"""
Import the contents of a JSON file to the database, works hand in hand
with the ``export_json_db`` class method. This will allows users to share
their database without needing to manually move their db file.
:return:
"""
options = dict()
options['defaultextension'] = '.zip'
options['filetypes'] = [('CALIPSO Data Archive', '*.zip'), ('All files', '*')]
options['initialdir'] = CONF.session_db.dir()
fl = tkFileDialog.askopenfilename(**options)
if fl != '':
log_fname = fl.rpartition('/')[2]
logger.info('Importing database from \'%s\'' % log_fname)
success = db.import_from_json(fl)
if success:
logger.info('Success, JSON file imported')
tkMessageBox.showinfo('database', 'shapes from %s imported ' % log_fname +
'(note: new tags have been assigned to these shapes!)')
else:
logger.error('Invalid JSON file')
tkMessageBox.showerror('database', 'Invalid JSON file %s' % log_fname)
@staticmethod
[docs] def export_json_db():
"""
Export the contents of the database to an archive containing JSON, which can then be
loaded into other databases and have all shapes imported
"""
if tkMessageBox.askyesno('Export database',
'Database will be exported to a specified' +
' archive (this operation is a copy, not a move)' +
' continue?'):
options = dict()
options['defaultextension'] = '.zip'
options['filetypes'] = [('ZIP Files', '*.zip'), ('All files', '*')]
options['initialdir'] = CONF.session_db.dir()
fl = tkFileDialog.asksaveasfilename(**options)
if fl != '':
log_fname = fl.rpartition('/')[2]
logger.info('Dumping database to \'%s\'' % log_fname)
success = db.dump_to_json(fl)
if success:
logger.info('Success, JSON file created')
tkMessageBox.showinfo('database', 'Database exported to \'%s\'' % log_fname)
else:
logger.error('No objects to be saved')
tkMessageBox.showerror('database', 'No objects inside database to export to JSON')
else:
logger.info('Export to database canceled')
# End menu bar functions
############################################################
def plot_baseplot(self, in_i):
self.__metadata_text = "Metadata\nMetadata\nMetadata\nMetadata\nMetadata\nMetadata\nMetadata\nMetadata"
self.__metadata_label = Label(self.__baseplot_frame, text=self.__metadata_text,
width=constants.WIDTH, height=constants.HEIGHT,
bg='LightYellow')
self.__metadata_label.pack()
def plot_not_available(self, in_i, in_type):
self.__shapemanagers[in_i].set_plot(Plot.not_available)
im = mpimg.imread(PATH + '/dat/grey.jpg')
self.__figs[in_i].get_yaxis().set_visible(False)
self.__figs[in_i].set_title("Plot currently not available")
self.__figs[in_i].get_xaxis().set_visible(False)
self.__figs[in_i].imshow(im)
[docs] def set_plot(self, plot_type, xrange_=(0, 1000), yrange=(0, 20)):
"""
Draws to the canvas according to the *plot_type* specified in the arguments. Accepts one of
the attributes below
.. py:attribute:: BASE_PLOT
.. py:attribute:: BACKSCATTERED
.. py:attribute:: DEPOLARIZED
.. py:attribute:: VFM
.. py:attribute:: IWP
.. py:attribute:: HORIZ_AVG
.. py:attribute:: AEROSOL_SUBTYPE
:param int plot_type: accepts ``BASE_PLOT, BACKSCATTERED, DEPOLARIZED, VFM, IWP, HORIZ_AVG
:param list xrange\_: accepts a range of time to plot
:param list yrange: accepts a range of altitude to plot
"""
self.xrange = xrange_
self.yrange = yrange
if plot_type == Plot.baseplot:
# Hide the axis and print an image
self.__shapemanager.set_plot(Plot.baseplot)
im = mpimg.imread(PATH + '/dat/CALIPSO.jpg')
self.__fig.get_yaxis().set_visible(False)
self.__fig.get_xaxis().set_visible(False)
self.__fig.imshow(im)
elif plot_type == Plot.backscattered:
try:
# Clear any references to the current figure, construct a new figure
# and render the backscattered plot to it
logger.info('Setting plot to backscattered xrange: ' +
str(xrange_) + ' yrange: ' + str(yrange))
self.__file = self.__data_block.get_file_name(1)
logger.info('Using file ' + self.__file)
# Reset if the file is not empty AND we are using granules from different time/place
if self.__shapemanager.get_hdf() != '' and \
self.__file[-25:-4] != self.__shapemanager.get_hdf()[-25:-4]:
self.__shapemanager.reset(all_=True)
else:
self.__shapemanager.clear_refs()
self.__shapemanager.set_hdf(self.__file)
self.__parent_fig.clear()
self.__fig = self.__parent_fig.add_subplot(1, 1, 1)
self.__fig = render_backscattered(self.__file, xrange_, yrange, self.__fig, self.__parent_fig)
self.__shapemanager.set_current(Plot.backscattered, self.__fig)
self.__drawplot_canvas.show()
self.__toolbar.update()
self.plot = Plot.backscattered
except IOError:
logger.error('IOError, no file exists')
tkMessageBox.showerror('File Not Found', 'No File Exists')
except IndexError:
tkMessageBox.showerror('Backscattered Plot', 'Index out of bounds')
elif plot_type == Plot.depolarized:
try:
# Clear any references to the current figure, construct a new figure
# and render the depolarized plot to it
logger.info('Setting plot to depolarized xrange: ' +
str(xrange_) + ' yrange: ' + str(yrange))
self.__file = self.__data_block.get_file_name(1)
logger.info('Using file ' + self.__file)
# Reset if the file is not empty AND we are using granules from different time/place
if self.__shapemanager.get_hdf() != '' and \
self.__file[-25:-4] != self.__shapemanager.get_hdf()[-25:-4]:
self.__shapemanager.reset(all_=True)
else:
self.__shapemanager.clear_refs()
self.__shapemanager.set_hdf(self.__file)
self.__parent_fig.clear()
self.__fig = self.__parent_fig.add_subplot(1, 1, 1)
self.__fig = render_depolarized(self.__file, xrange_, yrange, self.__fig, self.__parent_fig)
self.__shapemanager.set_current(Plot.depolarized, self.__fig)
self.__drawplot_canvas.show()
self.__toolbar.update()
self.plot = Plot.depolarized
except IOError:
logger.error('IOError, no file exists')
tkMessageBox.showerror('File Not Found', "No File Exists")
elif plot_type == Plot.vfm:
try:
# Clear any references to the current figure, construct a new figure
# and render the depolarized plot to it
logger.info('Setting plot to vfm xrange: ' +
str(xrange_) + ' yrange: ' + str(yrange))
self.__file = self.__data_block.get_file_name(2)
logger.info('Using file ' + self.__file)
# Reset if the file is not empty AND we are using granules from different time/place
if self.__shapemanager.get_hdf() != '' and \
self.__file[-25:-4] != self.__shapemanager.get_hdf()[-25:-4]:
self.__shapemanager.reset(all_=True)
else:
self.__shapemanager.clear_refs()
self.__shapemanager.set_hdf(self.__file)
self.__parent_fig.clear()
self.__fig = self.__parent_fig.add_subplot(1, 1, 1)
self.__fig = render_vfm(self.__file, xrange_, yrange, self.__fig, self.__parent_fig)
self.__shapemanager.set_current(Plot.vfm, self.__fig)
self.__drawplot_canvas.show()
self.__toolbar.update()
self.plot = Plot.vfm
except IOError:
logger.error('IOError, no file exists')
tkMessageBox.showerror('File Not Found', "No File Exists")
elif plot_type == Plot.iwp:
try:
# Clear any references to the current figure, construct a new figure
# and render the depolarized plot to it
logger.info('Setting plot to iwp xrange: ' +
str(xrange_) + ' yrange: ' + str(yrange))
self.__file = self.__data_block.get_file_name(2)
logger.info('Using file ' + self.__file)
# Reset if the file is not empty AND we are using granules from different time/place
if self.__shapemanager.get_hdf() != '' and \
self.__file[-25:-4] != self.__shapemanager.get_hdf()[-25:-4]:
self.__shapemanager.reset(all_=True)
else:
self.__shapemanager.clear_refs()
self.__shapemanager.set_hdf(self.__file)
self.__parent_fig.clear()
self.__fig = self.__parent_fig.add_subplot(1, 1, 1)
self.__fig = render_iwp(self.__file, xrange_, yrange, self.__fig, self.__parent_fig)
self.__shapemanager.set_current(Plot.iwp, self.__fig)
self.__drawplot_canvas.show()
self.__toolbar.update()
self.plot = Plot.iwp
except IOError:
logger.error('IOError, no file exists')
tkMessageBox.showerror('File Not Found', "No File Exists")
elif plot_type == Plot.horiz_avg:
try:
# Clear any references to the current figure, construct a new figure
# and render the depolarized plot to it
logger.info('Setting plot to horiz_avg xrange: ' +
str(xrange_) + ' yrange: ' + str(yrange))
self.__file = self.__data_block.get_file_name(2)
logger.info('Using file ' + self.__file)
# Reset if the file is not empty AND we are using granules from different time/place
if self.__shapemanager.get_hdf() != '' and \
self.__file[-25:-4] != self.__shapemanager.get_hdf()[-25:-4]:
self.__shapemanager.reset(all_=True)
else:
self.__shapemanager.clear_refs()
self.__shapemanager.set_hdf(self.__file)
self.__parent_fig.clear()
self.__fig = self.__parent_fig.add_subplot(1, 1, 1)
self.__fig = render_horiz_avg(self.__file, xrange_, yrange, self.__fig, self.__parent_fig)
self.__shapemanager.set_current(Plot.horiz_avg, self.__fig)
self.__drawplot_canvas.show()
self.__toolbar.update()
self.plot = Plot.horiz_avg
except IOError:
logger.error('IOError, no file exists')
tkMessageBox.showerror('File Not Found', "No File Exists")
elif plot_type == Plot.aerosol_subtype:
try:
# Clear any references to the current figure, construct a new figure
# and render the depolarized plot to it
logger.info('Setting plot to aerosol_subtype xrange: ' +
str(xrange_) + ' yrange: ' + str(yrange))
self.__file = self.__data_block.get_file_name(2)
logger.info('Using file ' + self.__file)
# Reset if the file is not empty AND we are using granules from different time/place
if self.__shapemanager.get_hdf() != '' and \
self.__file[-25:-4] != self.__shapemanager.get_hdf()[-25:-4]:
self.__shapemanager.reset(all_=True)
else:
self.__shapemanager.clear_refs()
self.__shapemanager.set_hdf(self.__file)
self.__parent_fig.clear()
self.__fig = self.__parent_fig.add_subplot(1, 1, 1)
self.__fig = render_aerosol_subtype(self.__file, xrange_, yrange, self.__fig, self.__parent_fig)
self.__shapemanager.set_current(Plot.aerosol_subtype, self.__fig)
self.__drawplot_canvas.show()
self.__toolbar.update()
self.plot = Plot.aerosol_subtype
except IOError:
logger.error('IOError, no file exists')
tkMessageBox.showerror('File Not Found', "No File Exists")
else:
logger.warning('Plot Type not yet supported')
[docs] def pan(self, event):
"""
Saves initial coordinates of mouse press when the user begins to pan
:param event: Tkinter passed event object
"""
logger.info("Pan point 1")
self.panx = event.x
self.pany = event.y
[docs] def render_pan(self, event):
"""
Saves ending coordinates of mouse press and proceeds to find the distance
between the two points, scrolls the map accordingly
:param event: Tkinter passed event object
"""
logger.info('Pan point 2, finding distance and panning...')
# Find distance and add an amplifier of 1.5
dst = int(distance(self.panx, self.pany, event.x, event.y) * 1.5)
# If the user is scrolling backwards
if self.panx < event.x:
# Already at beginning
if self.xrange[0] == 0:
logger.warning(
'Attempting to pan backwards, already at beginning nothing to be done')
return
# The end position would be negative
if self.xrange[0] - dst < 0:
logger.warning('Attempting to pan past beginning, setting to beginning')
# Set both xrange and dst to zero and simply reload beginning range
self.xrange = (0, self.xrange[1])
dst = 0
self.set_plot(self.plot, (self.xrange[0] - dst, self.xrange[1] - dst))
logger.info('Panning backwards')
else:
logger.info('Panning forwards')
self.set_plot(self.plot, xrange_=(self.xrange[0] + dst, self.xrange[1] + dst),
yrange=(int(self.__child.begin_alt_range_entry.get()),
int(self.__child.end_alt_range_entry.get())))
self.__child.begin_range_entry.delete(0, END)
self.__child.end_range_entry.delete(0, END)
self.__child.begin_range_entry.insert(END, str(self.xrange[0]))
self.__child.end_range_entry.insert(END, str(self.xrange[1]))
[docs] def save_json(self):
"""
**DEPRECATED**
Save all shapes on the map inside a JSON object given a previously
saved file. If no file exists prompt for file
"""
logger.info('Notifying JSON to save')
# Save to last saved file, if no file exists prompt to a new file
if self.__shapemanager.get_count() > 0:
saved = True
if self.__shapemanager.get_filename() == '':
saved = self.save_as_json() # Still prompt for a file name if none currently exists
else:
self.__shapemanager.save_json() # Else do a normal save with internal file
if saved:
tkMessageBox.showinfo('save', 'Shapes saved successfully')
else:
return False
else:
tkMessageBox.showerror('save as JSON', 'No objects to be saved')
[docs] def save_as_json(self, save_all=False):
"""
Save all selected objects on the plot, asking for a filename first
if ``save_all`` is specified and set to ``True``, the function will save **all**
shapes across **all** plots in the program.
"""
logger.info('Notifying JSON to save as')
# Save to a file entered by user, saveAll saves ALL objects across canvas
# and cannot be called as a normal save(must always be save as)
if self.__shapemanager.get_selected_count() > 0:
options = dict()
options['defaultextension'] = '.json'
options['filetypes'] = [('CALIPSO Data files', '*.json'), ('All files', '*')]
f = tkFileDialog.asksaveasfilename(**options)
if f == '':
logger.info("cancelling save as json")
return False
if save_all:
self.__shapemanager.save_all_json(f)
else:
self.__shapemanager.save_json(f)
else:
logger.error('No selected objects found, canceling save')
tkMessageBox.showerror('save as JSON', 'No objects to be saved')
[docs] def load(self):
"""
load JSON objects from file by calling :py:meth:`polygonlist.readPlot(f)`
"""
logger.info('Loading JSON')
# loads JSON object by calling the polygonList internal readPlot method
options = dict()
options['defaultextension'] = '.json'
options['filetypes'] = [('CALIPSO Data files', '*.json'), ('All files', '*')]
f = tkFileDialog.askopenfilename(**options)
if f is '':
return
self.__shapemanager.read_plot(f)
[docs] def about(self):
"""
Simple TopLevel window displaying the authors
"""
logger.info('Opening about window')
file_window = Toplevel(self.__root)
file_window.geometry("300x300")
file_window.title('About')
text = Text(file_window)
text.insert(END, constants.ABOUT)
text.config(state=DISABLED)
text.pack(expand=True, fill=BOTH)
button_close = Button(file_window, text='Close', command=file_window.destroy)
button_close.pack()
center(file_window, (300, 300))
[docs] def paint(self, event):
"""
Opens the paint window for specifying the shape's color
:param event: A Tkinter passed event object
"""
shape = self.__shapemanager.find_shape(event)
color = askcolor()
if color[0] is not None:
red = format(color[0][0], '02x')
green = format(color[0][1], '02x')
blue = format(color[0][2], '02x')
color = '#' + red + green + blue
logger.info('Painting %s -> %s' % (shape.get_tag(), color))
shape.get_itemhandler().set_facecolor(color)
self.__drawplot_canvas.show()
[docs] def reset(self):
"""
Reset all objects on the screen, move pan to original
"""
logger.info("Resetting plot")
self.__shapemanager.reset() # reset all shapes
self.__toolbar.home() # proc toolbar function to reset plot to home
############################################################
# The following functions open dialogs which are defined
# in separate files. Dialog should always be treated as
# singletons
[docs] def attribute_dialog(self, event):
"""
Open attribute window for specifying attributes on objects
:param event: A Tkinter passed event object
"""
logger.info('Grabbing shape object')
shape = self.__shapemanager.find_shape(event)
logger.info('Opening attributes dialog')
AttributesDialog(self.__root, shape). \
wm_iconbitmap(ICO)
[docs] def import_dialog(self):
"""
Open the database import window allowing the user to import and
delete entries.
"""
logger.info('Opening database import window')
if (not ImportDialog.singleton):
ImportDialog(self.__root, self). \
wm_iconbitmap(ICO)
else:
logger.warning('Found existing import window, canceling')
[docs] def settings_dialog(self):
"""
Opens the settings window allowing the user to manually change the settings in the config
file
"""
logger.info('Opening settings window')
if (not SettingsDialog.singleton):
SettingsDialog(self.__root, self). \
wm_iconbitmap(ICO)
else:
logger.warning('Found existing settings window, canceling')
# end dialog functions
############################################################
[docs] def get_root(self):
"""
Return the root of the application
:rtype: A Tkinter root
"""
return self.__root
[docs] def get_shapemanager(self):
"""
Returns the internal :py:class:`polygonList` object
:rtype: :py:class:`polygon.manager.ShapeManager`
"""
return self.__shapemanager # get functions for private variables
[docs] def get_fig(self):
"""
Returns the figure that is plotted to the canvas
:rtype: :py:class:`SubplotAxes`
"""
if self.__fig:
return self.__fig
logger.error('Fig does not exist')
[docs] def get_file(self):
"""
Return the current HDF file being displayed
:type: :py:class:`str`
"""
return self.__file
[docs] def close(self):
"""
Checks if the all the shapes are saved. If a shape is unsaved, the
program will ask the user whether save or not, and then close the
program. Also saves the session settings to the config.json file
"""
logger.info('Writing session settings')
CONF.opened.change(True)
CONF.write_config()
if not self.__shapemanager.is_all_saved():
logger.warning('Unsaved shapes found')
answer = tkMessageBox. \
askyesnocancel('Close Without Saving',
'There are unsaved shapes on the plot. Save these shapes?')
if answer is True:
logger.info('Saving shapes')
saved = self.save_json()
if saved:
error_check()
self.__root.destroy()
else:
return
elif answer is False:
logger.info('Dumping unsaved shapes')
error_check()
self.__root.destroy()
elif answer is None:
return
else:
error_check()
self.__root.destroy()
############################################################
# New functions
############################################################
def goToMain(self):
self.__drawplot_notebook.select(self.__backscattered532_frame)
def main():
logger.info("Debug Level = %s" % str(constants.debug_switch))
# Create Tkinter root and initialize Calipso
logging.info('Starting CALIPSO program')
Tk.CallWrapper = Catcher
rt = Tk()
logging.info('Instantiate CALIPSO program')
program = Calipso(rt)
# Setup Calipso window
logger.info('Setting up window')
program.setup_window()
logger.info('Setting up menu')
program.setup_menu()
logger.info('Setting up main screen')
program.setup_main_screen()
# Begin program
rt.mainloop()
logging.info('Terminated CALIPSO program')
if __name__ == '__main__':
main()