tk_interface.py 14 KB
Newer Older
1 2 3 4 5 6
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
"""
    Implements graphical interface using Tkinter package modules.
    
7
    Name: tk_interface.py
8 9 10 11 12 13 14 15 16
    Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
"""

import sys
if sys.version_info[0] < 3:
    import Tkinter as Tk
else:
    import tkinter as Tk
import tkMessageBox
17
#import tkinter.messagebox
18 19

import tk as tk_local
20
#import tkinter as tk_local
21 22 23 24

from interface import Interface, InterfaceException as IException

class TkInterface(Interface):
25
    """Implements graphical interface and functionalities using Tkinter."""
26
    
27
    MAX_CLASSES = 200
28 29 30 31
    
    utils = tk_local.Utils

    def __init__(self, title):
32 33 34 35 36 37 38
        """Constructor.

        Parameters
        ----------
        title : string
            Title of interface window.
        """
39 40 41 42 43 44 45 46
        self.title = title
        
        self._root = Tk.Tk()
        self._root.wm_title(self.title)
        self._root.geometry('%dx%d+%d+%d' % (800, 600, 0, 0))
        
        self._menus = []
        
47
        self._image = tk_local.Image(self._root)
48 49 50 51 52
        self._grid = None
        self._logger = None
        self._conf_dialog = None
        
    def set_subtitle(self, subtitle):
53 54 55 56 57 58 59
        """Set complement of window title.

        Parameters
        ----------
        subtitle : string
            Complement of window title.
        """
60 61 62 63
        self._root.wm_title(self.title + ' - ' + subtitle)
    

    def add_menu(self, label):
64 65 66 67 68 69 70
        """Add a menu.

        Parameters
        ----------
        label : string
            Menu label.
        """
71 72 73
        self._menus.append( tk_local.Menu(self._root, label) )
    
    def add_command(self, label, action, shortcut = None):
74 75 76 77 78 79 80 81 82 83 84
        """Add a menu option to last menu added.

        Parameters
        ----------
        label : string
            Menu option label.
        action : function
            Callback to be executed on menu option click.
        shortcut : string
            Menu option shortcut number ou letter. Not case sensitive.
        """
85 86 87
        self._menus[-1].add_command( label = label, action = lambda *_: self._apply( action ), shortcut = shortcut )

    def add_separator(self):
88 89
        """Add a separator to last menu added.
        """
90 91
        self._menus[-1].add_separator( )
        
92
    def add_check_button(self, label, action, shortcut = None, default_state = True):
93 94 95 96 97 98 99 100
        """Add a check button to last menu added.

        Parameters
        ----------
        label : string
            Check button option label.
        action : function
            Callback to be executed on check button click.
101
        shortcut : string, optional, default = None
102
            Check button shortcut number ou letter. Not case sensitive.
103 104
        default_state : boolean, optional, default = True
            Initial state of check button. If true set to on.
105
        """
106
        self._menus[-1].add_check_button(label = label, action = lambda *_: self._apply( action ), shortcut = shortcut, default_state = default_state)
107 108

    def render_menu(self):
109 110
        """Add to window GUI the last menu added.
        """
111 112 113 114 115 116 117 118 119
        menubar = Tk.Menu(self._root)

        for menu in self._menus:
            menu.render(menubar)
          
        self._root.config(menu=menubar)
        
    
    def add_image(self, image, title = None, onclick = None):
120 121 122 123 124 125 126 127 128 129 130
        """Add a image to window GUI.

        Parameters
        ----------
        image : opencv 3-channel color image
            OpenCV Image.
        title : string
            Title of image. It will be concatenate to window title.
        onclick : function
            Callback to be executed on image click.
        """
131 132 133
        if self._image is not None:
            self._image.close()
           
134
        self._image.render(image, lambda event, *_: self._apply( onclick, event ))
135 136 137 138
            
        if title is not None:
            self.set_subtitle(title)
            
139
    def toggle_image_toolbar(self):
140 141
        """Toogle the matplotlib image toolbar.
        """
142 143 144
        self._image.toggle_toolbar()
            
    def toggle_image_axes(self):
145 146
        """Toogle the axes of image.
        """
147
        self._image.toggle_axes()
148

149
    def refresh_image(self, image = None, title = None):
150 151 152 153 154 155 156 157 158
        """Refresh the image content.

        Parameters
        ----------
        image : opencv 3-channel color image
            OpenCV Image.
        title : string
            Title of image. It will be concatenate to window title.
        """
159 160 161 162
        self._image.refresh(image)
            
        if title is not None:
            self.set_subtitle(title)
163 164

    def close_image(self):
165 166
        """Close the image.
        """
167 168 169
        if tkMessageBox.askokcancel("Quit", "Do you want to close the image?"):
            return self._image.close()

170 171

    def open_log(self):
172 173
        """Add a console log to window GUI.
        """
174 175 176
        self._logger = tk_local.Log(self._root)
        
    def write_log(self, fmt, *args):
177 178 179 180 181 182 183 184 185
        """Log a new formatted message.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
186 187
        if self._logger:
            self._logger.write_logger(fmt, *args)
188
            self.refresh_image()
189 190
        
    def append_log(self, fmt, *args):
191 192 193 194 195 196 197 198 199
        """Append a formatted message to log.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
200 201
        if self._logger:
            self._logger.append_logger(fmt, *args)
202
            self.refresh_image()
203 204
        
    def clear_log(self):
205 206
        """Clear log content.
        """
207 208 209
        if self._logger:
            self._logger.clear_logger()
            
210
    def toggle_log(self):
211 212
        """Toogle console log.
        """
213 214 215 216 217 218
        if self._logger:
            self.close_log()
        else:
            self.open_log()
        
    def close_log(self):
219 220
        """Close console log.
        """
221 222 223 224 225 226
        if self._logger:
            self._logger.destroy_logger()
            self._logger = None

        
    def add_panel(self, left = True):
227 228 229 230 231 232 233
        """Add a vertical panel to window GUI.
        
        Parameters
        ----------
        left : boolean, optional, default True
            Side where the panel will be placed. If true left, otherwise right.
        """
234 235 236 237 238 239
        self._grid = tk_local.CustomGrid(self._root, 175)
        
        side = Tk.LEFT if left == True else Tk.RIGHT
        self._grid.pack(side=side, fill=Tk.Y, expand=False)
        
    def add_panel_classes(self, classes = None, selected = 0):
240 241 242 243 244 245 246 247 248
        """Add a vertical panel with classes to window GUI.
        
        Parameters
        ----------
        classes : list of dictionary class_config, optional, default = None
            List of panel classes.
        selected : integer, optional, default = 0
            Index in list of selected class.
        """
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
        if self._grid is None:
            self._grid = tk_local.CustomGrid(self._root)
            self.clear_panel_classes()
        
        classes = classes if classes is not None else []

        self._grid.add_cell_label("", 0, 0, 1, 4)
        self._grid.add_cell_label("Classes   ", 0, 1, 12, 4)
        self._grid.add_cell_label("", 0, 3, 2, 4)
        
        length = len(classes)
        for i in range(1, length+1):
            self._grid.add_cell_radio_button(classes[i-1]["name"].value[:12], i, i, 1, 12, selected=True if i == selected+1 else False, 
                                                command=classes[i-1]["callback"].value, command_args=classes[i-1]["args"].value )
            self._grid.add_cell_button_color("", i, 2, 2, bg=classes[i-1]["color"].value, 
                                                command=classes[i-1]["callback_color"].value, command_args=classes[i-1]["args"].value )
            
        self._grid.pack(side=Tk.LEFT, fill=Tk.Y, expand=False)
        
    def refresh_panel_classes(self, classes = None, selected = 0):
269 270 271 272 273 274 275 276 277
        """Update the panel classes.
        
        Parameters
        ----------
        classes : list of dictionary class_config, optional, default = None
            List of panel classes.
        selected : integer, optional, default = 0
            Index in list of selected class.
        """
278 279 280 281
        self.clear_panel_classes()
        self.add_panel_classes(classes, selected)
        
    def clear_panel_classes(self):
282 283
        """Remove all classes from panel.
        """
284 285 286 287 288
        for i in range(1, self.MAX_CLASSES+1):
            self._grid.add_cell_label("", i, 1, 15)
            self._grid.add_cell_label("", i, 2, 2)

    def close_panel(self):
289 290
        """Remove the panel from window GUI.
        """
291 292 293 294 295
        if self._grid is not None:
            self._grid.pack_forget();
            self._grid.destroy();
        
        
296
    def popup(self, message):
297 298 299 300 301 302 303
        """Open a non-blocking popup.
        
        Parameters
        ----------
        message : string
            Message to be shown in popup.
        """
304
        tk_local.Popup(self._root).show(message)
305 306 307

        
    def dialogue_config(self, title, configs, callback):
308 309 310 311 312 313 314 315 316 317 318
        """Create a configuration dialogue window.
        
        Parameters
        ----------
        title : string
            Configuration window title.
        config : dictionary
            Dictionary with the configs.
        callback: function
            Method to be excecuted after success.
        """
319 320 321
        self._conf_dialog = tk_local.ConfigDialog(self._root, title, configs, command_ok = lambda *_: self._apply( callback ))
        
    def dialogue_choose_one(self, title, configs, callback):
322 323 324 325 326 327 328 329 330 331 332
        """Create a choose one dialogue window.
        
        Parameters
        ----------
        title : string
            Configuration window title.
        config : dictionary
            Dictionary with the options.
        callback: function
            Method to be excecuted after success.
        """
333 334
        self._conf_dialog = tk_local.ChooseOneDialog(self._root, title, configs, command_ok = lambda *_: self._apply( callback ))
        
335
    def dialogue_select(self, title, configs, callback):
336 337 338 339 340 341 342 343 344 345 346
        """Create a select dialogue window.
        
        Parameters
        ----------
        title : string
            Configuration window title.
        config : dictionary
            Dictionary with the options.
        callback: function
            Method to be excecuted after success.
        """
347 348
        self._conf_dialog = tk_local.SelectDialog(self._root, title, configs, command_ok = lambda *_: self._apply( callback ))
        
349
    def get_config_and_destroy(self):
350 351
        """Execute last created dialogue, get the results and erase it.
        """
352 353 354 355 356 357 358 359 360 361 362 363 364 365
        if self._conf_dialog is None:
            return None
        
        try:
            self._conf_dialog.update_and_validate_configs()
            
            configs = self._conf_dialog.get_configs()
            
            self._conf_dialog.destroy()
            self._conf_dialog = None
            
            return configs
        except:    
            self._conf_dialog.destroy()
366
            raise IException("Illegal values, please try again")
367 368 369


    def show_error(self, fmt, *args):
370 371 372 373 374 375 376 377 378
        """Show error window message.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
379 380 381
        tkMessageBox.showerror("Error", fmt % args)
        
    def show_info(self, fmt, *args):
382 383 384 385 386 387 388 389 390
        """Show info window message.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
391 392 393
        tkMessageBox.showinfo("Info", fmt % args)
        
    def show_warning(self, fmt, *args):
394 395 396 397 398 399 400 401 402
        """Show warning window message.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
403
        tkMessageBox.showwarning("Warning", fmt % args)
404 405 406
        
    
    def ask_ok_cancel(self, title, question):
407 408 409 410 411 412 413 414 415
        """Show ok or cancel window dialogue.
        
        Parameters
        ----------
        title : string
            Window dialogue title.
        question : string
            Question to be answered with ok or cancel.
        """
416
        return tkMessageBox.askokcancel(title, question)
417 418 419

        
    def show(self):
420 421
        """Open a rendered GUI.
        """
422 423 424 425
        self._root.protocol("WM_DELETE_WINDOW", self.quit)
        self._root.mainloop()
        
    def quit(self):
426 427
        """Ask if user want to quit. If yes, close the GUI.
        """
428
        if tkMessageBox.askokcancel("Quit", "Do you want to quit?"):
429 430 431 432 433
            try:
                import weka.core.jvm as jvm
                jvm.stop()
            except:
                pass
434 435 436 437
            self._root.quit()
            self._root.destroy()

    def debug(self, event):
438 439
        """Just a debug test.
        """
440 441 442 443
        print("DEBUG")
        

    def _apply(self, f, *args):
444 445 446 447 448 449 450 451 452
        """Apply a method catching and handling any exception.
        
        Parameters
        ----------
        f : function
            Method to be executed.
        *args : arguments
            List of method arguments.
        """
453 454 455 456 457 458 459 460
        try:
            f(*args)
        except IException as exc:
            self._log_exception( str(exc), True )
        except:
            self._log_exception( IException.format_exception() )
                
    
461
    def _log_exception(self, message, dialog = False):
462 463 464 465 466 467 468 469 470
        """Log a catched exception.
        
        Parameters
        ----------
        message : string
            Exception message.
        dialog : boolean, optional, default = False
            If True show a error dialogue message.
        """
471 472
        if dialog == True:
            self.show_error( message )
473 474 475 476
        elif IException.DEBUG == True:
            self.append_log( message )
        else:
            self.show_error( message )