tk_interface.py 14.2 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
    Author: Alessandro dos Santos Ferreira ( santosferreira.alessandro@gmail.com )
"""
10
import os
11 12 13 14 15 16 17 18 19 20 21 22
import sys
if sys.version_info[0] < 3:
    import Tkinter as Tk
else:
    import tkinter as Tk
import tkMessageBox

import tk as tk_local

from interface import Interface, InterfaceException as IException

class TkInterface(Interface):
23
    """Implements graphical interface and functionalities using Tkinter."""
24
    
25 26 27 28 29 30 31 32 33 34 35
    directory = os.getcwd()

    if(directory[-1] == '/'):
        directory = directory[:-1]

    path = directory[::-1].find("/")
    directory = directory[:-path] + "data"
    directory = os.listdir(directory)
    
    MAX_CLASSES = len(directory)
    print MAX_CLASSES
36 37 38 39
    
    utils = tk_local.Utils

    def __init__(self, title):
40 41 42 43 44 45 46
        """Constructor.

        Parameters
        ----------
        title : string
            Title of interface window.
        """
47 48 49 50 51 52 53 54
        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 = []
        
55
        self._image = tk_local.Image(self._root)
56 57 58 59 60
        self._grid = None
        self._logger = None
        self._conf_dialog = None
        
    def set_subtitle(self, subtitle):
61 62 63 64 65 66 67
        """Set complement of window title.

        Parameters
        ----------
        subtitle : string
            Complement of window title.
        """
68 69 70 71
        self._root.wm_title(self.title + ' - ' + subtitle)
    

    def add_menu(self, label):
72 73 74 75 76 77 78
        """Add a menu.

        Parameters
        ----------
        label : string
            Menu label.
        """
79 80 81
        self._menus.append( tk_local.Menu(self._root, label) )
    
    def add_command(self, label, action, shortcut = None):
82 83 84 85 86 87 88 89 90 91 92
        """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.
        """
93 94 95
        self._menus[-1].add_command( label = label, action = lambda *_: self._apply( action ), shortcut = shortcut )

    def add_separator(self):
96 97
        """Add a separator to last menu added.
        """
98 99
        self._menus[-1].add_separator( )
        
100
    def add_check_button(self, label, action, shortcut = None, default_state = True):
101 102 103 104 105 106 107 108
        """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.
109
        shortcut : string, optional, default = None
110
            Check button shortcut number ou letter. Not case sensitive.
111 112
        default_state : boolean, optional, default = True
            Initial state of check button. If true set to on.
113
        """
114
        self._menus[-1].add_check_button(label = label, action = lambda *_: self._apply( action ), shortcut = shortcut, default_state = default_state)
115 116

    def render_menu(self):
117 118
        """Add to window GUI the last menu added.
        """
119 120 121 122 123 124 125 126 127
        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):
128 129 130 131 132 133 134 135 136 137 138
        """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.
        """
139 140 141
        if self._image is not None:
            self._image.close()
           
142
        self._image.render(image, lambda event, *_: self._apply( onclick, event ))
143 144 145 146
            
        if title is not None:
            self.set_subtitle(title)
            
147
    def toggle_image_toolbar(self):
148 149
        """Toogle the matplotlib image toolbar.
        """
150 151 152
        self._image.toggle_toolbar()
            
    def toggle_image_axes(self):
153 154
        """Toogle the axes of image.
        """
155
        self._image.toggle_axes()
156

157
    def refresh_image(self, image = None, title = None):
158 159 160 161 162 163 164 165 166
        """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.
        """
167 168 169 170
        self._image.refresh(image)
            
        if title is not None:
            self.set_subtitle(title)
171 172

    def close_image(self):
173 174
        """Close the image.
        """
175 176 177
        if tkMessageBox.askokcancel("Quit", "Do you want to close the image?"):
            return self._image.close()

178 179

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

        
    def add_panel(self, left = True):
235 236 237 238 239 240 241
        """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.
        """
242 243 244 245 246 247
        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):
248 249 250 251 252 253 254 255 256
        """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.
        """
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
        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):
277 278 279 280 281 282 283 284 285
        """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.
        """
286 287 288 289
        self.clear_panel_classes()
        self.add_panel_classes(classes, selected)
        
    def clear_panel_classes(self):
290 291
        """Remove all classes from panel.
        """
292 293 294 295 296
        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):
297 298
        """Remove the panel from window GUI.
        """
299 300 301 302 303
        if self._grid is not None:
            self._grid.pack_forget();
            self._grid.destroy();
        
        
304
    def popup(self, message):
305 306 307 308 309 310 311
        """Open a non-blocking popup.
        
        Parameters
        ----------
        message : string
            Message to be shown in popup.
        """
312
        tk_local.Popup(self._root).show(message)
313 314 315

        
    def dialogue_config(self, title, configs, callback):
316 317 318 319 320 321 322 323 324 325 326
        """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.
        """
327 328 329
        self._conf_dialog = tk_local.ConfigDialog(self._root, title, configs, command_ok = lambda *_: self._apply( callback ))
        
    def dialogue_choose_one(self, title, configs, callback):
330 331 332 333 334 335 336 337 338 339 340
        """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.
        """
341 342
        self._conf_dialog = tk_local.ChooseOneDialog(self._root, title, configs, command_ok = lambda *_: self._apply( callback ))
        
343
    def dialogue_select(self, title, configs, callback):
344 345 346 347 348 349 350 351 352 353 354
        """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.
        """
355 356
        self._conf_dialog = tk_local.SelectDialog(self._root, title, configs, command_ok = lambda *_: self._apply( callback ))
        
357
    def get_config_and_destroy(self):
358 359
        """Execute last created dialogue, get the results and erase it.
        """
360 361 362 363 364 365 366 367 368 369 370 371 372 373
        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()
374
            raise IException("Illegal values, please try again")
375 376 377


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

        
    def show(self):
428 429
        """Open a rendered GUI.
        """
430 431 432 433
        self._root.protocol("WM_DELETE_WINDOW", self.quit)
        self._root.mainloop()
        
    def quit(self):
434 435
        """Ask if user want to quit. If yes, close the GUI.
        """
436
        if tkMessageBox.askokcancel("Quit", "Do you want to quit?"):
437 438 439 440 441
            try:
                import weka.core.jvm as jvm
                jvm.stop()
            except:
                pass
442 443 444 445
            self._root.quit()
            self._root.destroy()

    def debug(self, event):
446 447
        """Just a debug test.
        """
448 449 450 451
        print("DEBUG")
        

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