1# ------------------------------------------------------------------------------ 2# Copyright (c) 2007, Riverbank Computing Limited 3# All rights reserved. 4# 5# This software is provided without warranty under the terms of the BSD license. 6# However, when used with the GPL version of PyQt the additional terms described 7# in the PyQt GPL exception also apply. 8# 9# Author: Riverbank Computing Limited 10# ------------------------------------------------------------------------------ 11 12""" Defines the concrete implementations of the traits Toolkit interface for 13the PyQt user interface toolkit. 14""" 15 16 17 18# Make sure that importing from this backend is OK: 19from traitsui.toolkit import assert_toolkit_import 20 21assert_toolkit_import(["qt4", "qt"]) 22 23# Ensure that we can import Pyface backend. This starts App as a side-effect. 24from pyface.toolkit import toolkit_object as pyface_toolkit 25 26_app = pyface_toolkit("init:_app") 27 28from traits.trait_notifiers import set_ui_handler 29from pyface.api import SystemMetrics 30from pyface.qt import QtCore, QtGui, qt_api 31 32from traitsui.toolkit import Toolkit 33 34 35# ------------------------------------------------------------------------- 36# Handles UI notification handler requests that occur on a thread other than 37# the UI thread: 38# ------------------------------------------------------------------------- 39 40_QT_TRAITS_EVENT = QtCore.QEvent.Type(QtCore.QEvent.registerEventType()) 41 42 43class _CallAfter(QtCore.QObject): 44 """ This class dispatches a handler so that it executes in the main GUI 45 thread (similar to the wx function). 46 """ 47 48 # The list of pending calls. 49 _calls = [] 50 51 # The mutex around the list of pending calls. 52 _calls_mutex = QtCore.QMutex() 53 54 def __init__(self, handler, *args, **kwds): 55 """ Initialise the call. 56 """ 57 QtCore.QObject.__init__(self) 58 59 # Save the details of the call. 60 self._handler = handler 61 self._args = args 62 self._kwds = kwds 63 64 # Add this to the list. 65 self._calls_mutex.lock() 66 self._calls.append(self) 67 self._calls_mutex.unlock() 68 69 # Move to the main GUI thread. 70 self.moveToThread(QtGui.QApplication.instance().thread()) 71 72 # Post an event to be dispatched on the main GUI thread. Note that 73 # we do not call QTimer.singleShot, which would be simpler, because 74 # that only works on QThreads. We want regular Python threads to work. 75 event = QtCore.QEvent(_QT_TRAITS_EVENT) 76 QtGui.QApplication.instance().postEvent(self, event) 77 78 def event(self, event): 79 """ QObject event handler. 80 """ 81 if event.type() == _QT_TRAITS_EVENT: 82 # Invoke the handler 83 self._handler(*self._args, **self._kwds) 84 85 # We cannot remove from self._calls here. QObjects don't like being 86 # garbage collected during event handlers (there are tracebacks, 87 # plus maybe a memory leak, I think). 88 QtCore.QTimer.singleShot(0, self._finished) 89 90 return True 91 else: 92 return QtCore.QObject.event(self, event) 93 94 def _finished(self): 95 """ Remove the call from the list, so it can be garbage collected. 96 """ 97 self._calls_mutex.lock() 98 del self._calls[self._calls.index(self)] 99 self._calls_mutex.unlock() 100 101 102def ui_handler(handler, *args, **kwds): 103 """ Handles UI notification handler requests that occur on a thread other 104 than the UI thread. 105 """ 106 _CallAfter(handler, *args, **kwds) 107 108 109# Tell the traits notification handlers to use this UI handler 110set_ui_handler(ui_handler) 111 112 113class _KeyEventHook(QtCore.QObject): 114 """ 115 Event hook for handling Qt key events. 116 """ 117 118 def __init__(self, handler): 119 super(_KeyEventHook, self).__init__() 120 self._handler = handler 121 122 def eventFilter(self, object, event): 123 if event.type() == QtCore.QEvent.KeyPress: 124 return self._handler(event) 125 else: 126 return QtCore.QObject.eventFilter(self, object, event) 127 128 129class GUIToolkit(Toolkit): 130 """ Implementation class for PyQt toolkit. 131 """ 132 133 def ui_panel(self, ui, parent): 134 """ Creates a PyQt panel-based user interface using information 135 from the specified UI object. 136 """ 137 from . import ui_panel 138 139 ui_panel.ui_panel(ui, parent) 140 141 def ui_subpanel(self, ui, parent): 142 """ Creates a PyQt subpanel-based user interface using information 143 from the specified UI object. 144 """ 145 from . import ui_panel 146 147 ui_panel.ui_subpanel(ui, parent) 148 149 def ui_livemodal(self, ui, parent): 150 """ Creates a PyQt modal "live update" dialog user interface using 151 information from the specified UI object. 152 """ 153 from . import ui_live 154 155 ui_live.ui_livemodal(ui, parent) 156 157 def ui_live(self, ui, parent): 158 """ Creates a PyQt non-modal "live update" window user interface 159 using information from the specified UI object. 160 """ 161 from . import ui_live 162 163 ui_live.ui_live(ui, parent) 164 165 def ui_modal(self, ui, parent): 166 """ Creates a PyQt modal dialog user interface using information 167 from the specified UI object. 168 """ 169 from . import ui_modal 170 171 ui_modal.ui_modal(ui, parent) 172 173 def ui_nonmodal(self, ui, parent): 174 """ Creates a PyQt non-modal dialog user interface using 175 information from the specified UI object. 176 """ 177 from . import ui_modal 178 179 ui_modal.ui_nonmodal(ui, parent) 180 181 def ui_wizard(self, ui, parent): 182 """ Creates a PyQt wizard dialog user interface using information 183 from the specified UI object. 184 """ 185 import ui_wizard 186 187 ui_wizard.ui_wizard(ui, parent) 188 189 def view_application( 190 self, 191 context, 192 view, 193 kind=None, 194 handler=None, 195 id="", 196 scrollable=None, 197 args=None, 198 ): 199 """ Creates a PyQt modal dialog user interface that 200 runs as a complete application, using information from the 201 specified View object. 202 203 Parameters 204 ---------- 205 context : object or dictionary 206 A single object or a dictionary of string/object pairs, whose trait 207 attributes are to be edited. If not specified, the current object is 208 used. 209 view : view or string 210 A View object that defines a user interface for editing trait 211 attribute values. 212 kind : string 213 The type of user interface window to create. See the 214 **traitsui.view.kind_trait** trait for values and 215 their meanings. If *kind* is unspecified or None, the **kind** 216 attribute of the View object is used. 217 handler : Handler object 218 A handler object used for event handling in the dialog box. If 219 None, the default handler for Traits UI is used. 220 id : string 221 A unique ID for persisting preferences about this user interface, 222 such as size and position. If not specified, no user preferences 223 are saved. 224 scrollable : Boolean 225 Indicates whether the dialog box should be scrollable. When set to 226 True, scroll bars appear on the dialog box if it is not large enough 227 to display all of the items in the view at one time. 228 229 """ 230 from . import view_application 231 232 return view_application.view_application( 233 context, view, kind, handler, id, scrollable, args 234 ) 235 236 def position(self, ui): 237 """ Positions the associated dialog window on the display. 238 """ 239 view = ui.view 240 window = ui.control 241 242 # Set up the default position of the window: 243 parent = window.parent() 244 if parent is None: 245 px = 0 246 py = 0 247 pdx = SystemMetrics().screen_width 248 pdy = SystemMetrics().screen_height 249 else: 250 pos = parent.pos() 251 if int(parent.windowFlags()) & QtCore.Qt.Window == 0: 252 pos = parent.mapToGlobal(pos) 253 px = pos.x() 254 py = pos.y() 255 pdx = parent.width() 256 pdy = parent.height() 257 258 # Get the window's prefered size. 259 size_hint = window.sizeHint() 260 261 # Calculate the correct width and height for the window: 262 cur_width = size_hint.width() 263 cur_height = size_hint.height() 264 width = view.width 265 height = view.height 266 267 if width < 0.0: 268 width = cur_width 269 elif width <= 1.0: 270 width = int(width * SystemMetrics().screen_width) 271 else: 272 width = int(width) 273 274 if height < 0.0: 275 height = cur_height 276 elif height <= 1.0: 277 height = int(height * SystemMetrics().screen_height) 278 else: 279 height = int(height) 280 281 # Calculate the correct position for the window: 282 x = view.x 283 y = view.y 284 285 if x < -99999.0: 286 x = px + ((pdx - width) // 2) 287 elif x <= -1.0: 288 x = px + pdx - width + int(x) + 1 289 elif x < 0.0: 290 x = px + pdx - width + int(x * pdx) 291 elif x <= 1.0: 292 x = px + int(x * pdx) 293 else: 294 x = int(x) 295 296 if y < -99999.0: 297 y = py + ((pdy - height) // 2) 298 elif y <= -1.0: 299 y = py + pdy - height + int(y) + 1 300 elif x < 0.0: 301 y = py + pdy - height + int(y * pdy) 302 elif y <= 1.0: 303 y = py + int(y * pdy) 304 else: 305 y = int(y) 306 307 # Position and size the window as requested: 308 layout = window.layout() 309 if layout.sizeConstraint() == QtGui.QLayout.SetFixedSize: 310 layout.setSizeConstraint(QtGui.QLayout.SetDefaultConstraint) 311 window.move(max(0, x), max(0, y)) 312 window.setFixedSize(QtCore.QSize(width, height)) 313 else: 314 window.setGeometry(max(0, x), max(0, y), width, height) 315 316 def show_help(self, ui, control): 317 """ Shows a help window for a specified UI and control. 318 """ 319 from . import ui_panel 320 321 ui_panel.show_help(ui, control) 322 323 def save_window(self, ui): 324 """ Saves user preference information associated with a UI window. 325 """ 326 from . import helper 327 328 helper.save_window(ui) 329 330 def rebuild_ui(self, ui): 331 """ Rebuilds a UI after a change to the content of the UI. 332 """ 333 if ui.control is not None: 334 ui.recycle() 335 ui.info.ui = ui 336 ui.rebuild(ui, ui.parent) 337 338 def set_title(self, ui): 339 """ Sets the title for the UI window. 340 """ 341 ui.control.setWindowTitle(ui.title) 342 343 def set_icon(self, ui): 344 """ Sets the icon for the UI window. 345 """ 346 from pyface.image_resource import ImageResource 347 348 if isinstance(ui.icon, ImageResource): 349 ui.control.setWindowIcon(ui.icon.create_icon()) 350 351 def key_event_to_name(self, event): 352 """ Converts a keystroke event into a corresponding key name. 353 """ 354 from . import key_event_to_name 355 356 return key_event_to_name.key_event_to_name(event) 357 358 def hook_events(self, ui, control, events=None, handler=None): 359 """ Hooks all specified events for all controls in a UI so that they 360 can be routed to the correct event handler. 361 """ 362 if events is None: 363 # FIXME: Implement drag and drop events ala toolkit.py for wx 364 return 365 366 elif events == "keys": 367 # It's unsafe to parent the event filter with the object it's 368 # filtering, so we store a reference to it here to ensure that it's 369 # not garbage collected prematurely. 370 ui._key_event_hook = _KeyEventHook(handler=handler) 371 control.installEventFilter(ui._key_event_hook) 372 373 def skip_event(self, event): 374 """ Indicates that an event should continue to be processed by the 375 toolkit. 376 """ 377 event.ignore() 378 379 def destroy_control(self, control): 380 """ Destroys a specified GUI toolkit control. 381 """ 382 # Block signals to prevent any editors from being updated (the control 383 # will not be deleted immediately). 384 control.blockSignals(True) 385 386 # This may be called from within the finished() signal handler so we 387 # need to do the delete after the handler has returned. 388 control.hide() 389 control.deleteLater() 390 391 # PyQt v4.3.1 and earlier deleteLater() didn't transfer ownership to 392 # C++, which is necessary for the QObject system to garbage collect it. 393 if qt_api == "pyqt": 394 if QtCore.PYQT_VERSION < 0x040302: 395 import sip 396 397 sip.transferto(control, None) 398 399 def destroy_children(self, control): 400 """ Destroys all of the child controls of a specified GUI toolkit 401 control. 402 """ 403 for w in control.children(): 404 # Only destroy widgets. 405 if isinstance(w, QtGui.QWidget): 406 # This may be called from within the finished() signal handler 407 # so we need to do the delete after the handler has returned. 408 w.deleteLater() 409 410 def image_size(self, image): 411 """ Returns a ( width, height ) tuple containing the size of a 412 specified toolkit image. 413 """ 414 return (image.width(), image.height()) 415 416 def constants(self): 417 """ Returns a dictionary of useful constants. 418 419 Currently, the dictionary should have the following key/value pairs: 420 421 - 'WindowColor': the standard window background color in the toolkit 422 specific color format. 423 """ 424 return { 425 "WindowColor": QtGui.QApplication.palette().color( 426 QtGui.QPalette.Window 427 ) 428 } 429 430 def color_trait(self, *args, **traits): 431 from . import color_trait as ct 432 433 return ct.PyQtColor(*args, **traits) 434 435 def rgb_color_trait(self, *args, **traits): 436 from . import rgb_color_trait as rgbct 437 438 return rgbct.RGBColor(*args, **traits) 439 440 def font_trait(self, *args, **traits): 441 from . import font_trait as ft 442 443 return ft.PyQtFont(*args, **traits) 444 445 # ------------------------------------------------------------------------- 446 # 'Editor' class methods: 447 # ------------------------------------------------------------------------- 448 449 # Generic UI-base editor: 450 def ui_editor(self): 451 from . import ui_editor 452 453 return ui_editor.UIEditor 454 455 # ------------------------------------------------------------------------- 456 # 'EditorFactory' factory methods: 457 # ------------------------------------------------------------------------- 458 459 # Array: 460 def array_editor(self, *args, **traits): 461 from . import array_editor as ae 462 463 return ae.ToolkitEditorFactory(*args, **traits) 464 465 # Boolean: 466 def boolean_editor(self, *args, **traits): 467 from . import boolean_editor as be 468 469 return be.ToolkitEditorFactory(*args, **traits) 470 471 # Button: 472 def button_editor(self, *args, **traits): 473 from . import button_editor as be 474 475 return be.ToolkitEditorFactory(*args, **traits) 476 477 # Check list: 478 def check_list_editor(self, *args, **traits): 479 from . import check_list_editor as cle 480 481 return cle.ToolkitEditorFactory(*args, **traits) 482 483 # Code: 484 def code_editor(self, *args, **traits): 485 from . import code_editor as ce 486 487 return ce.ToolkitEditorFactory(*args, **traits) 488 489 # Color: 490 def color_editor(self, *args, **traits): 491 from . import color_editor as ce 492 493 return ce.ToolkitEditorFactory(*args, **traits) 494 495 # Compound: 496 def compound_editor(self, *args, **traits): 497 from . import compound_editor as ce 498 499 return ce.ToolkitEditorFactory(*args, **traits) 500 501 def styled_date_editor(self, *args, **traits): 502 from . import styled_date_editor as sde 503 504 return sde.ToolkitEditorFactory(*args, **traits) 505 506 # Custom: 507 def custom_editor(self, *args, **traits): 508 from . import custom_editor as ce 509 510 return ce.ToolkitEditorFactory(*args, **traits) 511 512 # Directory: 513 def directory_editor(self, *args, **traits): 514 from . import directory_editor as de 515 516 return de.ToolkitEditorFactory(*args, **traits) 517 518 # Drop (drag and drop target): 519 def drop_editor(self, *args, **traits): 520 from . import drop_editor as de 521 522 return de.ToolkitEditorFactory(*args, **traits) 523 524 # Drag and drop: 525 def dnd_editor(self, *args, **traits): 526 import dnd_editor as dnd 527 528 return dnd.ToolkitEditorFactory(*args, **traits) 529 530 # Enum(eration): 531 def enum_editor(self, *args, **traits): 532 from . import enum_editor as ee 533 534 return ee.ToolkitEditorFactory(*args, **traits) 535 536 # File: 537 def file_editor(self, *args, **traits): 538 from . import file_editor as fe 539 540 return fe.ToolkitEditorFactory(*args, **traits) 541 542 # Font: 543 def font_editor(self, *args, **traits): 544 from . import font_editor as fe 545 546 return fe.ToolkitEditorFactory(*args, **traits) 547 548 # Key Binding: 549 def key_binding_editor(self, *args, **traits): 550 from . import key_binding_editor as kbe 551 552 return kbe.ToolkitEditorFactory(*args, **traits) 553 554 # History: 555 def history_editor(self, *args, **traits): 556 from . import history_editor as he 557 558 return he.HistoryEditor(*args, **traits) 559 560 # HTML: 561 def html_editor(self, *args, **traits): 562 from . import html_editor as he 563 564 return he.ToolkitEditorFactory(*args, **traits) 565 566 # Image: 567 def image_editor(self, *args, **traits): 568 from . import image_editor as ie 569 570 return ie.ImageEditor(*args, **traits) 571 572 # Image enum(eration): 573 def image_enum_editor(self, *args, **traits): 574 from . import image_enum_editor as iee 575 576 return iee.ToolkitEditorFactory(*args, **traits) 577 578 # Instance: 579 def instance_editor(self, *args, **traits): 580 from . import instance_editor as ie 581 582 return ie.ToolkitEditorFactory(*args, **traits) 583 584 # List: 585 def list_editor(self, *args, **traits): 586 from . import list_editor as le 587 588 return le.ToolkitEditorFactory(*args, **traits) 589 590 # ListStr: 591 def list_str_editor(self, *args, **traits): 592 from . import list_str_editor as lse 593 594 return lse.ListStrEditor(*args, **traits) 595 596 # Null: 597 def null_editor(self, *args, **traits): 598 from . import null_editor as ne 599 600 return ne.ToolkitEditorFactory(*args, **traits) 601 602 # Ordered set: 603 def ordered_set_editor(self, *args, **traits): 604 import ordered_set_editor as ose 605 606 return ose.ToolkitEditorFactory(*args, **traits) 607 608 # Plot: 609 def plot_editor(self, *args, **traits): 610 import plot_editor as pe 611 612 return pe.ToolkitEditorFactory(*args, **traits) 613 614 # Range: 615 def range_editor(self, *args, **traits): 616 from . import range_editor as re 617 618 return re.ToolkitEditorFactory(*args, **traits) 619 620 # RGB Color: 621 def rgb_color_editor(self, *args, **traits): 622 from . import rgb_color_editor as rgbce 623 624 return rgbce.ToolkitEditorFactory(*args, **traits) 625 626 # Set: 627 def set_editor(self, *args, **traits): 628 from . import set_editor as se 629 630 return se.ToolkitEditorFactory(*args, **traits) 631 632 # Shell: 633 def shell_editor(self, *args, **traits): 634 from . import shell_editor as se 635 636 return se.ToolkitEditorFactory(*args, **traits) 637 638 # Table: 639 def table_editor(self, *args, **traits): 640 from . import table_editor as te 641 642 return te.ToolkitEditorFactory(*args, **traits) 643 644 # Tabular: 645 def tabular_editor(self, *args, **traits): 646 from . import tabular_editor as te 647 648 return te.TabularEditor(*args, **traits) 649 650 # Text: 651 def text_editor(self, *args, **traits): 652 from . import text_editor as te 653 654 return te.ToolkitEditorFactory(*args, **traits) 655 656 # Title: 657 def title_editor(self, *args, **traits): 658 from . import title_editor 659 660 return title_editor.TitleEditor(*args, **traits) 661 662 # Tree: 663 def tree_editor(self, *args, **traits): 664 from . import tree_editor as te 665 666 return te.ToolkitEditorFactory(*args, **traits) 667 668 # Tuple: 669 def tuple_editor(self, *args, **traits): 670 from . import tuple_editor as te 671 672 return te.ToolkitEditorFactory(*args, **traits) 673 674 # Value: 675 def value_editor(self, *args, **traits): 676 from . import value_editor as ve 677 678 return ve.ToolkitEditorFactory(*args, **traits) 679