tk_interface.py 15.1 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
    Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
"""

import sys
12
if sys.version_info < (3, 0):
13
    # for Python2
14
    import Tkinter as Tk
15 16 17
    import tkMessageBox
    import tk as tk_local
    from interface import Interface, InterfaceException as IException
18
else:
19 20
    # for Python3
    import tkinter as Tk
21 22 23 24 25 26 27 28
    import tkinter.messagebox as tkMessageBox
    from .tk import tk_utils,tk_popup,tk_customframe,tk_canvas,tk_logger,tk_customdialog,tk_menu
    class TkLocal:
        def __init__(self,Utils, Menu,Image,Log,Pop, CustomGrid,SelectDialog,ChooseOneDialog,ConfigDialog):
            self.Utils=Utils
            self.Menu=Menu
            self.Image=Image
            self.Log=Log
29
            self.Popup=Pop
30 31 32 33 34 35 36 37 38
            self.CustomGrid=CustomGrid
            self.SelectDialog=SelectDialog
            self.ChooseOneDialog=ChooseOneDialog
            self.ConfigDialog=ConfigDialog
    tk_local=TkLocal
    tk_local.Utils=tk_utils.Utils
    tk_local.Menu=tk_menu.Menu
    tk_local.Image=tk_canvas.Image
    tk_local.Log=tk_logger.Log
39
    tk_local.Popup=tk_popup.Popup
40 41 42 43 44
    tk_local.CustomGrid=tk_customframe.CustomGrid
    tk_local.SelectDialog=tk_customdialog.SelectDialog
    tk_local.ChooseOneDialog=tk_customdialog.ChooseOneDialog
    tk_local.ConfigDialog=tk_customdialog.ConfigDialog

45
    from interface import Interface, InterfaceException as IException
46 47


48

49
class TkInterface(Interface):
50
    """Implements graphical interface and functionalities using Tkinter."""
51
    
52
    MAX_CLASSES = 200
53 54 55 56

    if sys.version_info < (3, 0):
        utils = tk_local.Utils
    else:
57
        utils = tk_local.Utils
58 59

    def __init__(self, title):
60 61 62 63 64 65 66
        """Constructor.

        Parameters
        ----------
        title : string
            Title of interface window.
        """
67 68 69 70
        self.title = title
        
        self._root = Tk.Tk()
        self._root.wm_title(self.title)
71
        self._root.geometry('%dx%d+%d+%d' % (800, 800, 0, 0))
72 73 74
        
        self._menus = []
        
75
        self._image = tk_local.Image(self._root)
76 77 78 79 80
        self._grid = None
        self._logger = None
        self._conf_dialog = None
        
    def set_subtitle(self, subtitle):
81 82 83 84 85 86 87
        """Set complement of window title.

        Parameters
        ----------
        subtitle : string
            Complement of window title.
        """
88 89 90 91
        self._root.wm_title(self.title + ' - ' + subtitle)
    

    def add_menu(self, label):
92 93 94 95 96 97 98
        """Add a menu.

        Parameters
        ----------
        label : string
            Menu label.
        """
99 100 101
        self._menus.append( tk_local.Menu(self._root, label) )
    
    def add_command(self, label, action, shortcut = None):
102 103 104 105 106 107 108 109 110 111 112
        """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.
        """
113 114 115
        self._menus[-1].add_command( label = label, action = lambda *_: self._apply( action ), shortcut = shortcut )

    def add_separator(self):
116 117
        """Add a separator to last menu added.
        """
118 119
        self._menus[-1].add_separator( )
        
120
    def add_check_button(self, label, action, shortcut = None, default_state = True):
121 122 123 124 125 126 127 128
        """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.
129
        shortcut : string, optional, default = None
130
            Check button shortcut number ou letter. Not case sensitive.
131 132
        default_state : boolean, optional, default = True
            Initial state of check button. If true set to on.
133
        """
134
        self._menus[-1].add_check_button(label = label, action = lambda *_: self._apply( action ), shortcut = shortcut, default_state = default_state)
135 136

    def render_menu(self):
137 138
        """Add to window GUI the last menu added.
        """
139 140 141 142 143 144 145 146 147
        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):
148 149 150 151 152 153 154 155 156 157 158
        """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.
        """
159 160 161
        if self._image is not None:
            self._image.close()
           
162
        self._image.render(image, lambda event, *_: self._apply( onclick, event ))
163 164 165 166
            
        if title is not None:
            self.set_subtitle(title)
            
167
    def toggle_image_toolbar(self):
168 169
        """Toogle the matplotlib image toolbar.
        """
170 171 172
        self._image.toggle_toolbar()
            
    def toggle_image_axes(self):
173 174
        """Toogle the axes of image.
        """
175
        self._image.toggle_axes()
176

177
    def refresh_image(self, image = None, title = None):
178 179 180 181 182 183 184 185 186
        """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.
        """
187 188 189 190
        self._image.refresh(image)
            
        if title is not None:
            self.set_subtitle(title)
191 192

    def close_image(self):
193 194
        """Close the image.
        """
195 196 197
        if tkMessageBox.askokcancel("Quit", "Do you want to close the image?"):
            return self._image.close()

198 199

    def open_log(self):
200 201
        """Add a console log to window GUI.
        """
202 203 204
        self._logger = tk_local.Log(self._root)
        
    def write_log(self, fmt, *args):
205 206 207 208 209 210 211 212 213
        """Log a new formatted message.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
214 215
        if self._logger:
            self._logger.write_logger(fmt, *args)
216
            self.refresh_image()
217 218
        
    def append_log(self, fmt, *args):
219 220 221 222 223 224 225 226 227
        """Append a formatted message to log.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
228 229
        if self._logger:
            self._logger.append_logger(fmt, *args)
230
            self.refresh_image()
231 232
        
    def clear_log(self):
233 234
        """Clear log content.
        """
235 236 237
        if self._logger:
            self._logger.clear_logger()
            
238
    def toggle_log(self):
239 240
        """Toogle console log.
        """
241 242 243 244 245 246
        if self._logger:
            self.close_log()
        else:
            self.open_log()
        
    def close_log(self):
247 248
        """Close console log.
        """
249 250 251 252 253 254
        if self._logger:
            self._logger.destroy_logger()
            self._logger = None

        
    def add_panel(self, left = True):
255 256 257 258 259 260 261
        """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.
        """
262 263 264 265 266 267
        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):
268 269 270 271 272 273 274 275 276
        """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.
        """
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
        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):
297 298 299 300 301 302 303 304 305
        """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.
        """
306 307 308 309
        self.clear_panel_classes()
        self.add_panel_classes(classes, selected)
        
    def clear_panel_classes(self):
310 311
        """Remove all classes from panel.
        """
312 313 314 315 316
        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):
317 318
        """Remove the panel from window GUI.
        """
319 320 321 322 323
        if self._grid is not None:
            self._grid.pack_forget();
            self._grid.destroy();
        
        
324
    def popup(self, message):
325 326 327 328 329 330 331
        """Open a non-blocking popup.
        
        Parameters
        ----------
        message : string
            Message to be shown in popup.
        """
332
        tk_local.Popup(self._root).show(message)
333 334 335

        
    def dialogue_config(self, title, configs, callback):
336 337 338 339 340 341 342 343 344 345 346
        """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.
        """
347 348 349
        self._conf_dialog = tk_local.ConfigDialog(self._root, title, configs, command_ok = lambda *_: self._apply( callback ))
        
    def dialogue_choose_one(self, title, configs, callback):
350 351 352 353 354 355 356 357 358 359 360
        """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.
        """
361 362
        self._conf_dialog = tk_local.ChooseOneDialog(self._root, title, configs, command_ok = lambda *_: self._apply( callback ))
        
363
    def dialogue_select(self, title, configs, callback):
364 365 366 367 368 369 370 371 372 373 374
        """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.
        """
375 376
        self._conf_dialog = tk_local.SelectDialog(self._root, title, configs, command_ok = lambda *_: self._apply( callback ))
        
377
    def get_config_and_destroy(self):
378 379
        """Execute last created dialogue, get the results and erase it.
        """
380 381 382 383 384 385 386 387 388 389 390 391 392 393
        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()
394
            raise IException("Illegal values, please try again")
395 396 397


    def show_error(self, fmt, *args):
398 399 400 401 402 403 404 405 406
        """Show error window message.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
407 408 409
        tkMessageBox.showerror("Error", fmt % args)
        
    def show_info(self, fmt, *args):
410 411 412 413 414 415 416 417 418
        """Show info window message.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
419 420 421
        tkMessageBox.showinfo("Info", fmt % args)
        
    def show_warning(self, fmt, *args):
422 423 424 425 426 427 428 429 430
        """Show warning window message.
        
        Parameters
        ----------
        fmt : string
            Message with format variables.
        *args : arguments
            List of arguments of message.
        """
431
        tkMessageBox.showwarning("Warning", fmt % args)
432 433 434
        
    
    def ask_ok_cancel(self, title, question):
435 436 437 438 439 440 441 442 443
        """Show ok or cancel window dialogue.
        
        Parameters
        ----------
        title : string
            Window dialogue title.
        question : string
            Question to be answered with ok or cancel.
        """
444
        return tkMessageBox.askokcancel(title, question)
445 446 447

        
    def show(self):
448 449
        """Open a rendered GUI.
        """
450 451 452 453
        self._root.protocol("WM_DELETE_WINDOW", self.quit)
        self._root.mainloop()
        
    def quit(self):
454 455
        """Ask if user want to quit. If yes, close the GUI.
        """
456
        if tkMessageBox.askokcancel("Quit", "Do you want to quit?"):
457 458 459 460 461
            try:
                import weka.core.jvm as jvm
                jvm.stop()
            except:
                pass
462 463 464 465
            self._root.quit()
            self._root.destroy()

    def debug(self, event):
466 467
        """Just a debug test.
        """
468 469 470 471
        print("DEBUG")
        

    def _apply(self, f, *args):
472 473 474 475 476 477 478 479 480
        """Apply a method catching and handling any exception.
        
        Parameters
        ----------
        f : function
            Method to be executed.
        *args : arguments
            List of method arguments.
        """
481 482 483 484 485 486 487 488
        try:
            f(*args)
        except IException as exc:
            self._log_exception( str(exc), True )
        except:
            self._log_exception( IException.format_exception() )
                
    
489
    def _log_exception(self, message, dialog = False):
490 491 492 493 494 495 496 497 498
        """Log a catched exception.
        
        Parameters
        ----------
        message : string
            Exception message.
        dialog : boolean, optional, default = False
            If True show a error dialogue message.
        """
499 500
        if dialog == True:
            self.show_error( message )
501 502 503 504
        elif IException.DEBUG == True:
            self.append_log( message )
        else:
            self.show_error( message )