1# ------------------------------------------------------------------------------ 2# 3# Copyright (c) 2005, Enthought, Inc. 4# All rights reserved. 5# 6# This software is provided without warranty under the terms of the BSD 7# license included in LICENSE.txt and may be redistributed only 8# under the conditions described in the aforementioned license. The license 9# is also available online at http://www.enthought.com/licenses/BSD.txt 10# 11# Thanks for using Enthought open source! 12# 13# Author: David C. Morrill 14# Date: 07/01/2005 15# 16# ------------------------------------------------------------------------------ 17 18""" Defines the table column descriptor used by the editor and editor factory 19 classes for numeric and table editors. 20""" 21 22import os 23 24from traits.api import ( 25 Any, 26 Bool, 27 Callable, 28 Constant, 29 Enum, 30 Expression, 31 Float, 32 HasPrivateTraits, 33 Instance, 34 Int, 35 Property, 36 Str, 37 Either, 38) 39 40from traits.trait_base import user_name_for, xgetattr 41 42from .editor_factory import EditorFactory 43from .menu import Menu 44from .ui_traits import Image, AView, EditorStyle 45from .toolkit_traits import Color, Font 46from .view import View 47 48# Set up a logger: 49import logging 50 51 52logger = logging.getLogger(__name__) 53 54 55# Flag used to indicate user has not specified a column label 56UndefinedLabel = "???" 57 58 59class TableColumn(HasPrivateTraits): 60 """ Represents a column in a table editor. 61 """ 62 63 # ------------------------------------------------------------------------- 64 # Trait definitions: 65 # ------------------------------------------------------------------------- 66 67 #: Column label to use for this column: 68 label = Str(UndefinedLabel) 69 70 #: Type of data contained by the column: 71 # XXX currently no other types supported, but potentially there could be... 72 type = Enum("text", "bool") 73 74 #: Text color for this column: 75 text_color = Color("black") 76 77 #: Text font for this column: 78 text_font = Either(None, Font) 79 80 #: Cell background color for this column: 81 cell_color = Color("white", allow_none=True) 82 83 #: Cell background color for non-editable columns: 84 read_only_cell_color = Color(0xF4F3EE, allow_none=True) 85 86 #: Cell graph color: 87 graph_color = Color(0xDDD9CC) 88 89 #: Horizontal alignment of text in the column: 90 horizontal_alignment = Enum("left", ["left", "center", "right"]) 91 92 #: Vertical alignment of text in the column: 93 vertical_alignment = Enum("center", ["top", "center", "bottom"]) 94 95 #: Horizontal cell margin 96 horizontal_margin = Int(4) 97 98 #: Vertical cell margin 99 vertical_margin = Int(3) 100 101 #: The image to display in the cell: 102 image = Image 103 104 #: Renderer used to render the contents of this column: 105 renderer = Any # A toolkit specific renderer 106 107 #: Is the table column visible (i.e., viewable)? 108 visible = Bool(True) 109 110 #: Is this column editable? 111 editable = Bool(True) 112 113 #: Is the column automatically edited/viewed (i.e. should the column editor 114 #: or popup be activated automatically on mouse over)? 115 auto_editable = Bool(False) 116 117 #: Should a checkbox be displayed instead of True/False? 118 show_checkbox = Bool(True) 119 120 #: Can external objects be dropped on the column? 121 droppable = Bool(False) 122 123 #: Context menu to display when this column is right-clicked: 124 menu = Instance(Menu) 125 126 #: The tooltip to display when the mouse is over the column: 127 tooltip = Str() 128 129 #: The width of the column (< 0.0: Default, 0.0..1.0: fraction of total 130 #: table width, > 1.0: absolute width in pixels): 131 width = Float(-1.0) 132 133 #: The width of the column while it is being edited (< 0.0: Default, 134 #: 0.0..1.0: fraction of total table width, > 1.0: absolute width in 135 #: pixels): 136 edit_width = Float(-1.0) 137 138 #: The height of the column cell's row while it is being edited 139 #: (< 0.0: Default, 0.0..1.0: fraction of total table height, 140 #: > 1.0: absolute height in pixels): 141 edit_height = Float(-1.0) 142 143 #: The resize mode for this column. This takes precedence over other 144 #: settings (like **width**, above). 145 #: - "interactive": column can be resized by users or programmatically 146 #: - "fixed": users cannot resize the column, but it can be set programmatically 147 #: - "stretch": the column will be resized to fill the available space 148 #: - "resize_to_contents": column will be sized to fit the contents, but then cannot be resized 149 resize_mode = Enum("interactive", "fixed", "stretch", "resize_to_contents") 150 151 #: The view (if any) to display when clicking a non-editable cell: 152 view = AView 153 154 #: Optional maximum value a numeric cell value can have: 155 maximum = Float(trait_value=True) 156 157 # ------------------------------------------------------------------------- 158 #: Returns the actual object being edited: 159 # ------------------------------------------------------------------------- 160 161 def get_object(self, object): 162 """ Returns the actual object being edited. 163 """ 164 return object 165 166 def get_label(self): 167 """ Gets the label of the column. 168 """ 169 return self.label 170 171 def get_width(self): 172 """ Returns the width of the column. 173 """ 174 return self.width 175 176 def get_edit_width(self, object): 177 """ Returns the edit width of the column. 178 """ 179 return self.edit_width 180 181 def get_edit_height(self, object): 182 """ Returns the height of the column cell's row while it is being 183 edited. 184 """ 185 return self.edit_height 186 187 def get_type(self, object): 188 """ Gets the type of data for the column for a specified object. 189 """ 190 return self.type 191 192 def get_text_color(self, object): 193 """ Returns the text color for the column for a specified object. 194 """ 195 return self.text_color_ 196 197 def get_text_font(self, object): 198 """ Returns the text font for the column for a specified object. 199 """ 200 return self.text_font 201 202 def get_cell_color(self, object): 203 """ Returns the cell background color for the column for a specified 204 object. 205 """ 206 if self.is_editable(object): 207 return self.cell_color_ 208 return self.read_only_cell_color_ 209 210 def get_graph_color(self, object): 211 """ Returns the cell background graph color for the column for a 212 specified object. 213 """ 214 return self.graph_color_ 215 216 def get_horizontal_alignment(self, object): 217 """ Returns the horizontal alignment for the column for a specified 218 object. 219 """ 220 return self.horizontal_alignment 221 222 def get_vertical_alignment(self, object): 223 """ Returns the vertical alignment for the column for a specified 224 object. 225 """ 226 return self.vertical_alignment 227 228 def get_image(self, object): 229 """ Returns the image to display for the column for a specified object. 230 """ 231 return self.image 232 233 def get_renderer(self, object): 234 """ Returns the renderer for the column of a specified object. 235 """ 236 return self.renderer 237 238 def is_editable(self, object): 239 """ Returns whether the column is editable for a specified object. 240 """ 241 return self.editable 242 243 def is_auto_editable(self, object): 244 """ Returns whether the column is automatically edited/viewed for a 245 specified object. 246 """ 247 return self.auto_editable 248 249 def is_droppable(self, object, value): 250 """ Returns whether a specified value is valid for dropping on the 251 column for a specified object. 252 """ 253 return self.droppable 254 255 def get_menu(self, object): 256 """ Returns the context menu to display when the user right-clicks on 257 the column for a specified object. 258 """ 259 return self.menu 260 261 def get_tooltip(self, object): 262 """ Returns the tooltip to display when the user mouses over the column 263 for a specified object. 264 """ 265 return self.tooltip 266 267 def get_view(self, object): 268 """ Returns the view to display when clicking a non-editable cell. 269 """ 270 return self.view 271 272 def get_maximum(self, object): 273 """ Returns the maximum value a numeric column can have. 274 """ 275 return self.maximum 276 277 def on_click(self, object): 278 """ Called when the user clicks on the column. 279 """ 280 pass 281 282 def on_dclick(self, object): 283 """ Called when the user clicks on the column. 284 """ 285 pass 286 287 def cmp(self, object1, object2): 288 """ Returns the result of comparing the column of two different objects. 289 290 This is deprecated. 291 """ 292 return (self.key(object1) > self.key(object2)) - ( 293 self.key(object1) < self.key(object2) 294 ) 295 296 def __str__(self): 297 """ Returns the string representation of the table column. 298 """ 299 return self.get_label() 300 301 302class ObjectColumn(TableColumn): 303 """ A column for editing objects. 304 """ 305 306 # ------------------------------------------------------------------------- 307 # Trait definitions: 308 # ------------------------------------------------------------------------- 309 310 #: Name of the object trait associated with this column: 311 name = Str() 312 313 #: Column label to use for this column: 314 label = Property() 315 316 #: Trait editor used to edit the contents of this column: 317 editor = Instance(EditorFactory) 318 319 #: The editor style to use to edit the contents of this column: 320 style = EditorStyle 321 322 #: Format string to apply to column values: 323 format = Str("%s") 324 325 #: Format function to apply to column values: 326 format_func = Callable() 327 328 # ------------------------------------------------------------------------- 329 # Trait view definitions: 330 # ------------------------------------------------------------------------- 331 332 traits_view = View( 333 [ 334 ["name", "label", "type", "|[Column Information]"], 335 [ 336 "horizontal_alignment{Horizontal}@", 337 "vertical_alignment{Vertical}@", 338 "|[Alignment]", 339 ], 340 ["editable", "9", "droppable", "9", "visible", "-[Options]>"], 341 "|{Column}", 342 ], 343 [ 344 [ 345 "text_color@", 346 "cell_color@", 347 "read_only_cell_color@", 348 "|[UI Colors]", 349 ], 350 "|{Colors}", 351 ], 352 [["text_font@", "|[Font]<>"], "|{Font}"], 353 ["menu@", "|{Menu}"], 354 ["editor@", "|{Editor}"], 355 ) 356 357 def _get_label(self): 358 """ Gets the label of the column. 359 """ 360 if self._label is not None: 361 return self._label 362 return user_name_for(self.name) 363 364 def _set_label(self, label): 365 old, self._label = self._label, label 366 if old != label: 367 self.trait_property_changed("label", old, label) 368 369 def get_raw_value(self, object): 370 """ Gets the unformatted value of the column for a specified object. 371 """ 372 try: 373 return xgetattr(self.get_object(object), self.name) 374 except Exception as e: 375 from traitsui.api import raise_to_debug 376 377 raise_to_debug() 378 return None 379 380 def get_value(self, object): 381 """ Gets the formatted value of the column for a specified object. 382 """ 383 try: 384 if self.format_func is not None: 385 return self.format_func(self.get_raw_value(object)) 386 387 return self.format % (self.get_raw_value(object),) 388 except: 389 logger.exception( 390 "Error occurred trying to format a %s value" 391 % self.__class__.__name__ 392 ) 393 return "Format!" 394 395 def get_drag_value(self, object): 396 """Returns the drag value for the column. 397 """ 398 return self.get_raw_value(object) 399 400 def set_value(self, object, value): 401 """ Sets the value of the column for a specified object. 402 """ 403 target, name = self.target_name(object) 404 setattr(target, name, value) 405 406 def get_editor(self, object): 407 """ Gets the editor for the column of a specified object. 408 """ 409 if self.editor is not None: 410 return self.editor 411 412 target, name = self.target_name(object) 413 414 return target.base_trait(name).get_editor() 415 416 def get_style(self, object): 417 """ Gets the editor style for the column of a specified object. 418 """ 419 return self.style 420 421 def key(self, object): 422 """ Returns the value to use for sorting. 423 """ 424 return self.get_raw_value(object) 425 426 def is_droppable(self, object, value): 427 """ Returns whether a specified value is valid for dropping on the 428 column for a specified object. 429 """ 430 if self.droppable: 431 try: 432 target, name = self.target_name(object) 433 target.base_trait(name).validate(target, name, value) 434 return True 435 except: 436 pass 437 438 return False 439 440 def target_name(self, object): 441 """ Returns the target object and name for the column. 442 """ 443 object = self.get_object(object) 444 name = self.name 445 col = name.rfind(".") 446 if col < 0: 447 return (object, name) 448 449 return (xgetattr(object, name[:col]), name[col + 1 :]) 450 451 452class ExpressionColumn(ObjectColumn): 453 """ A column for displaying computed values. 454 """ 455 456 # ------------------------------------------------------------------------- 457 # Trait definitions: 458 # ------------------------------------------------------------------------- 459 460 #: The Python expression used to return the value of the column: 461 expression = Expression 462 463 #: Is this column editable? 464 editable = Constant(False) 465 466 #: The globals dictionary that should be passed to the expression 467 #: evaluation: 468 globals = Any({}) 469 470 def get_raw_value(self, object): 471 """ Gets the unformatted value of the column for a specified object. 472 """ 473 try: 474 return eval(self.expression_, self.globals, {"object": object}) 475 except Exception: 476 logger.exception( 477 "Error evaluating table column expression: %s" 478 % self.expression 479 ) 480 return None 481 482 483class NumericColumn(ObjectColumn): 484 """ A column for editing Numeric arrays. 485 """ 486 487 # ------------------------------------------------------------------------- 488 # Trait definitions: 489 # ------------------------------------------------------------------------- 490 491 #: Column label to use for this column 492 label = Property() 493 494 #: Text color this column when selected 495 selected_text_color = Color("black") 496 497 #: Text font for this column when selected 498 selected_text_font = Font 499 500 #: Cell background color for this column when selected 501 selected_cell_color = Color(0xD8FFD8) 502 503 #: Formatting string for the cell value 504 format = Str("%s") 505 506 #: Horizontal alignment of text in the column; this value overrides the 507 #: default. 508 horizontal_alignment = "center" 509 510 def _get_label(self): 511 """ Gets the label of the column. 512 """ 513 if self._label is not None: 514 return self._label 515 return self.name 516 517 def _set_label(self, label): 518 old, self._label = self._label, label 519 if old != label: 520 self.trait_property_changed("label", old, label) 521 522 def get_type(self, object): 523 """ Gets the type of data for the column for a specified object row. 524 """ 525 return self.type 526 527 def get_text_color(self, object): 528 """ Returns the text color for the column for a specified object row. 529 """ 530 if self._is_selected(object): 531 return self.selected_text_color_ 532 return self.text_color_ 533 534 def get_text_font(self, object): 535 """ Returns the text font for the column for a specified object row. 536 """ 537 if self._is_selected(object): 538 return self.selected_text_font 539 return self.text_font 540 541 def get_cell_color(self, object): 542 """ Returns the cell background color for the column for a specified 543 object row. 544 """ 545 if self.is_editable(object): 546 if self._is_selected(object): 547 return self.selected_cell_color_ 548 return self.cell_color_ 549 return self.read_only_cell_color_ 550 551 def get_horizontal_alignment(self, object): 552 """ Returns the horizontal alignment for the column for a specified 553 object row. 554 """ 555 return self.horizontal_alignment 556 557 def get_vertical_alignment(self, object): 558 """ Returns the vertical alignment for the column for a specified 559 object row. 560 """ 561 return self.vertical_alignment 562 563 def is_editable(self, object): 564 """ Returns whether the column is editable for a specified object row. 565 """ 566 return self.editable 567 568 def is_droppable(self, object, row, value): 569 """ Returns whether a specified value is valid for dropping on the 570 column for a specified object row. 571 """ 572 return self.droppable 573 574 def get_menu(self, object, row): 575 """ Returns the context menu to display when the user right-clicks on 576 the column for a specified object row. 577 """ 578 return self.menu 579 580 def get_value(self, object): 581 """ Gets the value of the column for a specified object row. 582 """ 583 try: 584 value = getattr(object, self.name) 585 try: 586 return self.format % (value,) 587 except: 588 return "Format!" 589 except: 590 return "Undefined!" 591 592 def set_value(self, object, row, value): 593 """ Sets the value of the column for a specified object row. 594 """ 595 column = self.get_data_column(object) 596 column[row] = type(column[row])(value) 597 598 def get_editor(self, object): 599 """ Gets the editor for the column of a specified object row. 600 """ 601 return super(NumericColumn, self).get_editor(object) 602 603 def get_data_column(self, object): 604 """ Gets the entire contents of the specified object column. 605 """ 606 return getattr(object, self.name) 607 608 def _is_selected(self, object): 609 """ Returns whether a specified object row is selected. 610 """ 611 if ( 612 hasattr(object, "model_selection") 613 and object.model_selection is not None 614 ): 615 return True 616 return False 617 618 619class ListColumn(TableColumn): 620 """ A column for editing lists. 621 """ 622 623 # ------------------------------------------------------------------------- 624 # Trait definitions: 625 # ------------------------------------------------------------------------- 626 627 # Label to use for this column 628 label = Property() 629 630 #: Index of the list element associated with this column 631 index = Int() 632 633 # Is this column editable? This value overrides the base class default. 634 editable = False 635 636 # ------------------------------------------------------------------------- 637 # Trait view definitions: 638 # ------------------------------------------------------------------------- 639 640 traits_view = View( 641 [ 642 ["index", "label", "type", "|[Column Information]"], 643 ["text_color@", "cell_color@", "|[UI Colors]"], 644 ] 645 ) 646 647 def _get_label(self): 648 """ Gets the label of the column. 649 """ 650 if self._label is not None: 651 return self._label 652 return "Column %d" % (self.index + 1) 653 654 def _set_label(self, label): 655 old, self._label = self._label, label 656 if old != label: 657 self.trait_property_changed("label", old, label) 658 659 def get_value(self, object): 660 """ Gets the value of the column for a specified object. 661 """ 662 return str(object[self.index]) 663 664 def set_value(self, object, value): 665 """ Sets the value of the column for a specified object. 666 """ 667 object[self.index] = value 668 669 def get_editor(self, object): 670 """ Gets the editor for the column of a specified object. 671 """ 672 return None 673 674 def key(self, object): 675 """ Returns the value to use for sorting. 676 """ 677 return object[self.index] 678