Пример #1
0
class GradientEditor(tk.Toplevel):
    """The gradient editor window, i.e. the thing that contains the gradient
    display, the function controls and the buttons."""
    def __init__(self, master, vtk_table, on_change_color_table = None):
        """Initialize the gradient editor window.
        
        Parameters
        ----------
        master
            Owning widget, for example a tk root object.
        VtkTable 
            Instance of vtkLookupTable, designating the table which is
            to be edited.
        OnChangeColorTable
            Callback function taking no arguments. Called
            when the color table was changed and rendering is requested."""
        
        # Inner dimensions of the color control gui-elements in pixels.
        gradient_preview_width = 300
        gradient_preview_height = 50
        channel_function_width = gradient_preview_width
        channel_function_height = 80
    
        tk.Toplevel.__init__(self, master) 
        self.title("Color Gradient Editor")
        self.minsize( gradient_preview_width+4, gradient_preview_height + 5 * \
                      channel_function_height + 50 )

        self.gradient_table = GradientTable(gradient_preview_width)
        self.vtk_color_table = vtk_table

        # create controls.
        self.gradient_control = GradientControl(self, self.gradient_table,
                                                gradient_preview_width,
                                                gradient_preview_height )
        self.gradient_control.grid(row=0,column=1,sticky="we")

        def on_gradient_table_changed( final_update ):
            # update all function controls.
            self.function_control_rgb.update()
            for control in self.function_controls_hsv:
                control.update()
            # repaint the gradient display or the external windows only
            # when the instant*** options are set or when the update was final.
            if final_update or ( 1 == self.show_instant_gradients.get() ):
                self.gradient_control.update()

            if final_update or ( 1 == self.show_instant_feedback.get() ):
                self.gradient_table.store_to_vtk_lookup_table( self.vtk_color_table )
                on_change_color_table()
        self.on_gradient_table_changed = on_gradient_table_changed
            
        self.function_control_rgb = FunctionControl(self, self.gradient_table,
                "rgb", channel_function_width, channel_function_height,
                on_gradient_table_changed)
        label = tk.Label( self, text = "rgb" )
        label.grid(row=1, column=0)
        self.function_control_rgb.grid(row=1,column=1,sticky="we")
        self.function_controls_hsv = []
        for it in [("hue",2), ("sat",3), ("val",4), ("alp", 5) ]:
            control = FunctionControl(self, self.gradient_table,
                it[0][0], channel_function_width, channel_function_height,
                on_gradient_table_changed )
            control.grid(row=it[1],column=1,sticky="we")
            self.function_controls_hsv.append(control)

            label = tk.Label( self, text = it[0] )
            label.grid(row=it[1], column=0)

        # buttons and the instruction label get into an own subframe for
        # easier control.
        button_frame = tk.Frame(self)
        button_frame.grid(row=6,column=0,columnspan=2)

        ok_button = tk.Button(button_frame, text="ok", command=self.ok)
        ok_button.grid(row=0,column=1)
        #CancelButton = tk.Button(ButtonFrame, text="cancel", command=self.Cancel)
        #CancelButton.grid(row=0,column=2)
        spacer = tk.Frame(button_frame, width=10 )
        spacer.grid(row=0,column=3)
        save_button = tk.Button(button_frame, text="save", command=self.save_gradient)
        save_button.grid(row=0,column=4)
        load_button = tk.Button(button_frame, text="load", command=self.load_gradient)
        load_button.grid(row=0,column=5)
        spacer = tk.Frame(button_frame, width=10 )
        spacer.grid(row=0,column=6)
        label = tk.Label(button_frame,text="instant:")
        label.grid(row=0,column=7)

        # these two buttons control whether gradient and render target
        # updates are executed during movement of control points or
        # only at the end of such changes.
        self.show_instant_gradients = tk.IntVar()
        self.show_instant_gradients.set(1) # enable instant gradients by default
        self.show_instant_feedback = tk.IntVar()
        self.show_instant_feedback.set(0) # disable instant feedback by default
        instant_gradient_button = tk.Checkbutton(button_frame, text="grad")
        instant_gradient_button.grid(row=0,column=8)
        instant_gradient_button.configure(variable=self.show_instant_gradients)
        instant_feedback_button = tk.Checkbutton(button_frame, text="feed")
        instant_feedback_button.grid(row=0,column=9)
        instant_feedback_button.configure(variable=self.show_instant_feedback)

        instruction_label = tk.Label(button_frame,
                text="left button: move point; right click: toggle point")
        instruction_label.grid(column=0,columnspan=9,row=1)

        # insert a ratio button which decides whether the controls for nonlinear
        # scaling of the gradient are shown and activated.
        self.nonlinear_scaling_enabled = tk.IntVar()
        self.nonlinear_scaling_enabled.set(0)
        nonlinear_enabled_button = tk.Checkbutton(button_frame, text="nonlin")
        nonlinear_enabled_button.grid(column=9,row=1)
        nonlinear_enabled_button.configure(variable=self.nonlinear_scaling_enabled,
            command=self.nonlinear_scaling_option_changed)
        
        # the controls for the nonlinear scaling also get into an own frame.
        # this one can be shown or hidden when the "nonlin"-button is pressed
        nonlin_frame = tk.Frame(self)
        self.nonlin_frame = nonlin_frame

        label = tk.Label(nonlin_frame, text="f(x) =")
        label.grid(row=0, column=0)
        self.nonlinear_function_string = tk.StringVar()
        self.nonlinear_function_string.set( "x**(4*a)" )
        function_edit = tk.Entry(nonlin_frame, width=35, 
            textvariable=self.nonlinear_function_string)
        function_edit.bind("<Return>", self.nonlinear_function_string_changed )
        function_edit.grid(row=0, column=1)

        label = tk.Label(nonlin_frame, text="param a:")
        label.grid(row=1, column=0)
        self.parameter_scale = tk.Scale(nonlin_frame, from_=0.0, to=1.0, 
            resolution=0.001, length=250, orient="horizontal")
        self.parameter_scale.bind("<ButtonRelease>",
            lambda event: self.nonlinear_parameter_scale_changed(final_update=True))
        self.parameter_scale.bind("<Motion>",
            lambda event:self.nonlinear_parameter_scale_changed(final_update=False))
        self.parameter_scale.set(0.5)
        self.parameter_scale.grid(row=1, column=1)
        label = tk.Label(nonlin_frame, text= \
                "f(x) should map [0..1] to [0..1]. It rescales the gradient.")
        label.grid(column=0,columnspan=2,row=2)

        # finally, write the current gradient out into main program
        on_gradient_table_changed(final_update = True)

    def nonlinear_scaling_option_changed(self):
        """called when the 'nonlin'-button is pressed to toggle if nonlinear-
        scaling is activated and the corresponding controls are shown"""
        if ( 1 == self.nonlinear_scaling_enabled.get() ):
            # activate the nonlinear scaling controls
            self.nonlin_frame.grid(row=7,column=0,columnspan=2)
            self.nonlinear_parameter_scale_changed(final_update=False)
            self.nonlinear_function_string_changed(None)
        else:
            # disable the nonlinear scaling controls (and the scaling)
            self.nonlin_frame.pack(side=tk.LEFT, anchor=tk.NW)
            self.nonlin_frame.pack_forget()
            self.gradient_table.set_scaling_function("")
            self.on_gradient_table_changed(final_update=True)

    def nonlinear_parameter_scale_changed(self,final_update):
        """Event Handler for the nonlinear-parameter scaling bar. FinalUpdate
        is true on ButtonRelease and False on Motion"""
        self.gradient_table.set_scaling_function_parameter(self.parameter_scale.get())
        self.on_gradient_table_changed(final_update = final_update)

    def nonlinear_function_string_changed(self,event):
        """Invoked when Return is pressed in the nonlinear-function edit"""
        self.gradient_table.set_scaling_function(self.nonlinear_function_string.get())
        self.on_gradient_table_changed(final_update = True)
        
    def ok(self):
        self.destroy()
        
    def save_gradient(self):
        filetypes = [("Gradient Files","*.grad"),("All Files","*")]
        file_name = tkFileDialog.asksaveasfilename(defaultextension=".grad",
                                                   filetypes=filetypes)
        if file_name:
            # there is probably a way to find out which file type the user
            # actually selected. But since I don't know it and also don't really
            # know how to find it out, i rely on this error prone method...
            if ( ".lut" == file_name[len(file_name)-4:] ):
                self.gradient_table.save(file_name)
            self.gradient_table.save(file_name)
        
    def load_gradient(self):
        filetypes = [("Gradient Files","*.grad"), ("All Files","*")]
        file_name = tkFileDialog.askopenfilename(defaultextension=".grad",
                                                 filetypes=filetypes)
        if file_name:
            self.gradient_table.load(file_name)
            self.on_gradient_table_changed(final_update = True)
            if self.gradient_table.scaling_function:
                self.parameter_scale.set(self.gradient_table.scaling_function_parameter)
                self.nonlinear_function_string.set(self.gradient_table.scaling_function_string)
                self.nonlinear_scaling_enabled.set(1)
                self.nonlinear_scaling_option_changed()
            else:
                self.nonlinear_scaling_enabled.set(0)
                self.nonlinear_scaling_option_changed()
Пример #2
0
class GradientEditor(tk.Toplevel):
    """The gradient editor window, i.e. the thing that contains the gradient
    display, the function controls and the buttons."""
    def __init__(self, master, vtk_table, on_change_color_table=None):
        """Initialize the gradient editor window.

        Parameters
        ----------
        master
            Owning widget, for example a tk root object.
        VtkTable
            Instance of vtkLookupTable, designating the table which is
            to be edited.
        OnChangeColorTable
            Callback function taking no arguments. Called
            when the color table was changed and rendering is requested."""

        # Inner dimensions of the color control gui-elements in pixels.
        gradient_preview_width = 300
        gradient_preview_height = 50
        channel_function_width = gradient_preview_width
        channel_function_height = 80

        tk.Toplevel.__init__(self, master)
        self.title("Color Gradient Editor")
        self.minsize( gradient_preview_width+4, gradient_preview_height + 5 * \
                      channel_function_height + 50 )

        self.gradient_table = GradientTable(gradient_preview_width)
        self.vtk_color_table = vtk_table

        # create controls.
        self.gradient_control = GradientControl(self, self.gradient_table,
                                                gradient_preview_width,
                                                gradient_preview_height)
        self.gradient_control.grid(row=0, column=1, sticky="we")

        def on_gradient_table_changed(final_update):
            # update all function controls.
            self.function_control_rgb.update()
            for control in self.function_controls_hsv:
                control.update()
            # repaint the gradient display or the external windows only
            # when the instant*** options are set or when the update was final.
            if final_update or (1 == self.show_instant_gradients.get()):
                self.gradient_control.update()

            if final_update or (1 == self.show_instant_feedback.get()):
                self.gradient_table.store_to_vtk_lookup_table(
                    self.vtk_color_table)
                on_change_color_table()

        self.on_gradient_table_changed = on_gradient_table_changed

        self.function_control_rgb = FunctionControl(self, self.gradient_table,
                                                    "rgb",
                                                    channel_function_width,
                                                    channel_function_height,
                                                    on_gradient_table_changed)
        label = tk.Label(self, text="rgb")
        label.grid(row=1, column=0)
        self.function_control_rgb.grid(row=1, column=1, sticky="we")
        self.function_controls_hsv = []
        for it in [("hue", 2), ("sat", 3), ("val", 4), ("alp", 5)]:
            control = FunctionControl(self, self.gradient_table, it[0][0],
                                      channel_function_width,
                                      channel_function_height,
                                      on_gradient_table_changed)
            control.grid(row=it[1], column=1, sticky="we")
            self.function_controls_hsv.append(control)

            label = tk.Label(self, text=it[0])
            label.grid(row=it[1], column=0)

        # buttons and the instruction label get into an own subframe for
        # easier control.
        button_frame = tk.Frame(self)
        button_frame.grid(row=6, column=0, columnspan=2)

        ok_button = tk.Button(button_frame, text="ok", command=self.ok)
        ok_button.grid(row=0, column=1)
        #CancelButton = tk.Button(ButtonFrame, text="cancel", command=self.Cancel)
        #CancelButton.grid(row=0,column=2)
        spacer = tk.Frame(button_frame, width=10)
        spacer.grid(row=0, column=3)
        save_button = tk.Button(button_frame,
                                text="save",
                                command=self.save_gradient)
        save_button.grid(row=0, column=4)
        load_button = tk.Button(button_frame,
                                text="load",
                                command=self.load_gradient)
        load_button.grid(row=0, column=5)
        spacer = tk.Frame(button_frame, width=10)
        spacer.grid(row=0, column=6)
        label = tk.Label(button_frame, text="instant:")
        label.grid(row=0, column=7)

        # these two buttons control whether gradient and render target
        # updates are executed during movement of control points or
        # only at the end of such changes.
        self.show_instant_gradients = tk.IntVar()
        self.show_instant_gradients.set(
            1)  # enable instant gradients by default
        self.show_instant_feedback = tk.IntVar()
        self.show_instant_feedback.set(
            0)  # disable instant feedback by default
        instant_gradient_button = tk.Checkbutton(button_frame, text="grad")
        instant_gradient_button.grid(row=0, column=8)
        instant_gradient_button.configure(variable=self.show_instant_gradients)
        instant_feedback_button = tk.Checkbutton(button_frame, text="feed")
        instant_feedback_button.grid(row=0, column=9)
        instant_feedback_button.configure(variable=self.show_instant_feedback)

        instruction_label = tk.Label(
            button_frame,
            text="left button: move point; right click: toggle point")
        instruction_label.grid(column=0, columnspan=9, row=1)

        # insert a ratio button which decides whether the controls for nonlinear
        # scaling of the gradient are shown and activated.
        self.nonlinear_scaling_enabled = tk.IntVar()
        self.nonlinear_scaling_enabled.set(0)
        nonlinear_enabled_button = tk.Checkbutton(button_frame, text="nonlin")
        nonlinear_enabled_button.grid(column=9, row=1)
        nonlinear_enabled_button.configure(
            variable=self.nonlinear_scaling_enabled,
            command=self.nonlinear_scaling_option_changed)

        # the controls for the nonlinear scaling also get into an own frame.
        # this one can be shown or hidden when the "nonlin"-button is pressed
        nonlin_frame = tk.Frame(self)
        self.nonlin_frame = nonlin_frame

        label = tk.Label(nonlin_frame, text="f(x) =")
        label.grid(row=0, column=0)
        self.nonlinear_function_string = tk.StringVar()
        self.nonlinear_function_string.set("x**(4*a)")
        function_edit = tk.Entry(nonlin_frame,
                                 width=35,
                                 textvariable=self.nonlinear_function_string)
        function_edit.bind("<Return>", self.nonlinear_function_string_changed)
        function_edit.grid(row=0, column=1)

        label = tk.Label(nonlin_frame, text="param a:")
        label.grid(row=1, column=0)
        self.parameter_scale = tk.Scale(nonlin_frame,
                                        from_=0.0,
                                        to=1.0,
                                        resolution=0.001,
                                        length=250,
                                        orient="horizontal")
        self.parameter_scale.bind(
            "<ButtonRelease>", lambda event: self.
            nonlinear_parameter_scale_changed(final_update=True))
        self.parameter_scale.bind(
            "<Motion>", lambda event: self.nonlinear_parameter_scale_changed(
                final_update=False))
        self.parameter_scale.set(0.5)
        self.parameter_scale.grid(row=1, column=1)
        label = tk.Label(nonlin_frame, text= \
                "f(x) should map [0..1] to [0..1]. It rescales the gradient.")
        label.grid(column=0, columnspan=2, row=2)

        # finally, write the current gradient out into main program
        on_gradient_table_changed(final_update=True)

    def nonlinear_scaling_option_changed(self):
        """called when the 'nonlin'-button is pressed to toggle if nonlinear-
        scaling is activated and the corresponding controls are shown"""
        if (1 == self.nonlinear_scaling_enabled.get()):
            # activate the nonlinear scaling controls
            self.nonlin_frame.grid(row=7, column=0, columnspan=2)
            self.nonlinear_parameter_scale_changed(final_update=False)
            self.nonlinear_function_string_changed(None)
        else:
            # disable the nonlinear scaling controls (and the scaling)
            self.nonlin_frame.pack(side=tk.LEFT, anchor=tk.NW)
            self.nonlin_frame.pack_forget()
            self.gradient_table.set_scaling_function("")
            self.on_gradient_table_changed(final_update=True)

    def nonlinear_parameter_scale_changed(self, final_update):
        """Event Handler for the nonlinear-parameter scaling bar. FinalUpdate
        is true on ButtonRelease and False on Motion"""
        self.gradient_table.set_scaling_function_parameter(
            self.parameter_scale.get())
        self.on_gradient_table_changed(final_update=final_update)

    def nonlinear_function_string_changed(self, event):
        """Invoked when Return is pressed in the nonlinear-function edit"""
        self.gradient_table.set_scaling_function(
            self.nonlinear_function_string.get())
        self.on_gradient_table_changed(final_update=True)

    def ok(self):
        self.destroy()

    def save_gradient(self):
        filetypes = [("Gradient Files", "*.grad"), ("All Files", "*")]
        file_name = tkFileDialog.asksaveasfilename(defaultextension=".grad",
                                                   filetypes=filetypes)
        if file_name:
            # there is probably a way to find out which file type the user
            # actually selected. But since I don't know it and also don't really
            # know how to find it out, i rely on this error prone method...
            if (".lut" == file_name[len(file_name) - 4:]):
                self.gradient_table.save(file_name)
            self.gradient_table.save(file_name)

    def load_gradient(self):
        filetypes = [("Gradient Files", "*.grad"), ("All Files", "*")]
        file_name = tkFileDialog.askopenfilename(defaultextension=".grad",
                                                 filetypes=filetypes)
        if file_name:
            self.gradient_table.load(file_name)
            self.on_gradient_table_changed(final_update=True)
            if self.gradient_table.scaling_function:
                self.parameter_scale.set(
                    self.gradient_table.scaling_function_parameter)
                self.nonlinear_function_string.set(
                    self.gradient_table.scaling_function_string)
                self.nonlinear_scaling_enabled.set(1)
                self.nonlinear_scaling_option_changed()
            else:
                self.nonlinear_scaling_enabled.set(0)
                self.nonlinear_scaling_option_changed()
Пример #3
0
class wxGradientEditorWidget(wx.Panel, AbstractGradEditor):
    """A Gradient Editor widget that can be used anywhere.
    """
    def __init__(self, master, vtk_table, on_change_color_table=None,
                 colors=None):
        """

        Parameters:
        -----------
        
        vtk_table : the `tvtk.LookupTable` or `tvtk.VolumeProperty` object
                    to set.
        
        on_change_color_table : A callback called when the color table
                                changes.
        
        colors : list of 'rgb', 'hsv', 'h', 's', 'v', 'a'
                 (Default : ['rgb', 'hsv', 'a'])

                 'rgb' creates one panel to edit Red, Green and Blue
                 colors.

                 'hsv' creates one panel to edit Hue, Saturation and
                 Value.

                 'h', 's', 'v', 'r', 'g', 'b', 'a' separately
                 specified creates different panels for each.
        """
        wx.Panel.__init__(self, master)

        if colors is None:
            colors = ['rgb', 'hsv', 'a']
        
        gradient_preview_width = 300
        gradient_preview_height = 50
        channel_function_width = gradient_preview_width
        channel_function_height = 80
        self.gradient_table = GradientTable(gradient_preview_width)
        self.vtk_color_table = vtk_table
        if isinstance(vtk_table, tvtk.LookupTable):
            self.vtk_table_is_lut = True
        else:
            # This is a tvtk.VolumeProperty
            self.vtk_table_is_lut = False
            # Initialize the editor with the volume property. 
            self.gradient_table.load_from_vtk_volume_prop(vtk_table)

        self.on_change_color_table = on_change_color_table

        # set up all the panels in a gridbagsizer (i.e. a big grid)
        # 6x2 size:  6 rows, 2 columns...
        sizer = wx.GridBagSizer(2, 2)

        # "Gradient Viewer" panel, in position (0,1) for sizer
        self.gradient_control = wxGradientControl(self,
                                                  self.gradient_table,
                                                  gradient_preview_width,
                                                  gradient_preview_height)
        tt = wx.ToolTip('Right click for menu')
        self.gradient_control.Bind(wx.EVT_CONTEXT_MENU, self.on_gradient_menu)
        self.gradient_control.SetToolTip(tt)
        sizer.Add(self.gradient_control, pos=(0,1))

        
        # Add the function controls:
        function_controls = []
        self.function_controls = function_controls

        tooltip_text = 'Left click: move control points\n'\
                       'Right click: add/remove control points'
        editor_data = {'rgb': ('', 'RGB'),
                       'hsv': ('Hue: Red; Saturation: Green; '\
                               'Value: Blue\n',                   
                               'HSV'
                               ),
                       'h': ('', 'HUE'),
                       's': ('', 'SAT'),
                       'v': ('', 'VAL'),
                       'r': ('', 'RED'),
                       'g': ('', 'GREEN'),
                       'b': ('', 'BLUE'),
                       'a': ('', 'ALPHA'),
                       }
        row = 1
        for color in colors:
            data = editor_data[color]
            control = wxFunctionControl(self, self.gradient_table, color,
                                        channel_function_width,
                                        channel_function_height)
            txt = data[0] + tooltip_text
            control.SetToolTip(wx.ToolTip(txt))
            # Add name of editor (to left side of editor)
            sizer.Add(wx.StaticText(self, -1, data[1]), pos=(row, 0),
                      flag=wx.ALIGN_CENTER|wx.ALL)
            # Add the "RGB" control point editor
            sizer.Add(control, pos=(row, 1))
            function_controls.append(control)
            row += 1

        # The status text.
        self.text = wx.StaticText(self, -1, 'status')
        sizer.Add(self.text, (row,0), (row,2))
        row += 1

        # set the appropriate sizer.
        sizer.SetSizeHints(self)
        self.SetSizerAndFit(sizer)

    ######################################################################
    # `wxGradientEditorWidget` interface.
    ######################################################################
    def set_status_text(self, msg):
        t = self.text
        t.SetLabel(msg)
        t.Refresh()
        t.Update()

    def on_gradient_table_changed(self, final_update ):
        """ Update the gradient table and vtk lookuptable..."""
        # update all function controls.
        for control in self.function_controls:
            control.update()
        # repaint the gradient display or the external windows only
        # when the instant*** options are set or when the update was final.
        #if final_update or ( 1 == self.show_instant_gradients.get() ):
        if True:
            self.gradient_control.update()

        #if final_update or ( 1 == self.show_instant_feedback.get() ):
        if final_update:
            vtk_table = self.vtk_color_table
            if self.vtk_table_is_lut:
                self.gradient_table.store_to_vtk_lookup_table(vtk_table)
            else:
                rng = self.get_table_range()
                self.gradient_table.store_to_vtk_volume_prop(vtk_table, rng)
                
            cb = self.on_change_color_table
            if cb is not None:
                cb()

    def get_table_range(self):
        vtk_table = self.vtk_color_table
        if self.vtk_table_is_lut:
            return vtk_table.table_range
        else:
            return vtk_table.get_scalar_opacity().range

    def load(self, file_name):
        """Set the state of the color table using the given file.
        """
        if len(file_name) == 0:
            return
        self.gradient_table.load(file_name)
        self.on_gradient_table_changed(final_update = True)

    def save(self, file_name):
        """Store the color table to the given file.  This actually
        generates 3 files, a '.grad', a '.lut' file and a '.jpg' file.
        The .lut file can be used to setup a lookup table.  The .grad
        file is used to set the state of the gradient table and the
        JPG file is an image of the how the lut will look.
        """
        if len(file_name) == 0:
            return
        self.gradient_table.save(file_name)          

    ######################################################################
    # wxPython event methods.
    ######################################################################
    def on_gradient_menu(self, event):
        if not hasattr(self, 'save_menuid'):
            # Do this only the first time.
            self.save_menuid = wx.NewId()
            self.load_menuid = wx.NewId()
            self.Bind(wx.EVT_MENU, self.on_save, id=self.save_menuid)
            self.Bind(wx.EVT_MENU, self.on_load, id=self.load_menuid)
        
        menu = wx.Menu()
        menu.Append(self.save_menuid, "Save as")
        menu.Append(self.load_menuid, "Load")
        self.PopupMenu(menu)
        menu.Destroy()

    def on_save(self, event):
        """
        Open "Save" dialog, write lookuptable to 3 files: ``*.lut`` (lookuptable) ``*.grad``
        (gradient table for use with this program), and ``*.jpg`` (image of the
        gradient)
        """
        dlg = wx.FileDialog(self, "Save LUT to...", style=wx.SAVE)
        wildcard = "Gradient Files (.grad)|*.grad|"   \
                   "All files (*.*)|*.*"
        dlg.SetWildcard(wildcard)
        if (dlg.ShowModal() == wx.ID_OK):
            file_name = dlg.GetPath()
            if file_name:
                self.save(file_name)

    def on_load(self, event):
        """
        Load a ``*.grad`` lookuptable file using wxpython dialog
        """
        style = wx.OPEN | wx.HIDE_READONLY
        dlg = wx.FileDialog(self, "Open a file", style=style)
        wildcard = "Gradient Files (.grad)|*.grad|"   \
                   "All files (*.*)|*.*"
        dlg.SetWildcard(wildcard)
        if (dlg.ShowModal() == wx.ID_OK):
            file_name = dlg.GetPath()        
            if file_name:
                self.load(file_name)