Module GUI2Exe
[hide private]
[frames] | no frames]

Source Code for Module GUI2Exe

   1  __author__  = "Andrea Gavana <andrea.gavana@gmail.com>, <gavana@kpo.kz>" 
   2  __date__    = "01 Apr 2007, 13:15 GMT" 
   3  __version__ = "0.1" 
   4  __docformat__ = "epytext" 
   5   
   6  # Start the imports 
   7  import os 
   8  import wx 
   9  import time 
  10   
  11  import wx.aui 
  12  import wx.lib.dialogs 
  13   
  14  # This is somehow needed by distutils, otherwise it bombs on Windows 
  15  # when you have py2App installed (!) 
  16  import setuptools 
  17   
  18  # I need webbrowser for the help and tips and tricks 
  19  import webbrowser 
  20   
  21  # Let's import few modules I have written for GUI2Exe 
  22  from ProjectTreeCtrl import ProjectTreeCtrl 
  23  from MessageWindow import MessageWindow 
  24  from AUINotebookPage import AUINotebookPage 
  25  from DataBase import DataBase 
  26  from Project import Project 
  27  from Process import Process 
  28  from Widgets import CustomCodeViewer, Py2ExeMissing 
  29  from Utilities import opj, odict 
  30  from Constants import _auiImageList, _pywild, _defaultCompilers 
  31   
  32  # And import the fancy AdvancedSplash 
  33  import AdvancedSplash as AS 
  34   
  35  # I need that to have restorable perspectives 
  36  ID_FirstPerspective = wx.ID_HIGHEST + 10001 
  37   
  38  # It looks like that, while py2exe and py2app are installed on site-packages 
  39  # (or at least you can actually __import__ them), for the other 2 beasts 
  40  # is far more complicated, at least on Windows 
  41   
42 -def ImportCompilers():
43 """ Simple function that imports the available compilers (if any). """ 44 45 compilers = odict() 46 for compiler in _defaultCompilers: 47 try: 48 module = __import__(compiler) 49 compilers[compiler] = module.__version__ 50 51 except ImportError: 52 # No compiler, no party 53 pass 54 55 return compilers
56 57 # Ok, now get the compilers... 58 _compilers = ImportCompilers() 59 60 61
62 -class GUI2Exe(wx.Frame):
63
64 - def __init__(self, parent, id=-1, title="", pos=wx.DefaultPosition, size=wx.DefaultSize, 65 style=wx.DEFAULT_FRAME_STYLE):
66 """ Default wx.Frame class constructor. """ 67 68 wx.Frame.__init__(self, parent, id, title, pos, size, style) 69 70 # Yes, I know, I am obsessively addicted to wxAUI 71 self._mgr = wx.aui.AuiManager() 72 self._mgr.SetManagedWindow(self) 73 74 # Some default starting values for our class 75 self.installDir = os.getcwd() # where are we 76 self.autoSave = False # use autoSave? (not implemented yet...) 77 self.deleteBuild = True # delete the "build" directory (recommended) 78 self.process = None # keeps track of the compilation subprocess 79 self.exeTimer = wx.Timer(self) # I use that to monitor exe failures 80 self.timerCount = 0 # same as above 81 self.processTimer = wx.Timer(self) # used to monitor the external process 82 83 self.perspectives = [] 84 85 # Create the status bar 86 self.CreateBar() 87 # Set frame properties (title, icon...) 88 self.SetProperties() 89 # Create the menu bar (lots of code, mostly always the same) 90 self.CreateMenuBar() 91 # Build the wx.aui.AuiNotebook image list 92 # But why in the world is so different from wx.Notebook??? 93 self.BuildNBImageList() 94 95 # Look if there already exists a database for GUI2Exe 96 dbName = self.CheckForDatabase() 97 98 # This is the left CustomTreeCtrl that holds all our projects 99 self.projectTree = ProjectTreeCtrl(self) 100 # This is the main window, the central pane 101 self.mainPanel = wx.aui.AuiNotebook(self, -1, style=wx.aui.AUI_NB_DEFAULT_STYLE| 102 wx.aui.AUI_NB_WINDOWLIST_BUTTON) 103 # Let's be fancy and add a special logging window 104 self.messageWindow = MessageWindow(self) 105 # Call the database. This actually populates the project tree control 106 self.dataBase = DataBase(self, dbName) 107 108 # Add the panes to the wxAUI manager 109 # Very nice the bug introduced in wxPython 2.8.3 about wxAUI Maximize buttons... 110 self._mgr.AddPane(self.projectTree, wx.aui.AuiPaneInfo().Left(). 111 Caption("GUI2Exe Projects").MinSize(wx.Size(200, -1)). 112 FloatingSize(wx.Size(200, 300)).Layer(1).MaximizeButton()) 113 self._mgr.AddPane(self.mainPanel, wx.aui.AuiPaneInfo().CenterPane()) 114 self._mgr.AddPane(self.messageWindow, wx.aui.AuiPaneInfo().Bottom(). 115 Caption("Messages And Actions").MinSize(wx.Size(200, 100)). 116 FloatingSize(wx.Size(500, 300)).BestSize(wx.Size(200, size[1]/6)). 117 MaximizeButton()) 118 119 # Set all the flags for wxAUI 120 self.SetAllFlags() 121 # Bind the main frame events 122 self.BindEvents() 123 124 # Save the current perspective to be reloaded later if requested 125 self.perspectives.append(self._mgr.SavePerspective()) 126 # Update the wxAUI manager 127 self._mgr.Update() 128 # Read the default configuration file. At the moment it contains data 129 # only for py2exe, and it might be moved to wx.Config 130 self.ReadConfigurationFile()
131 132 133 # ================================== # 134 # GUI2Exe methods called in __init__ # 135 # ================================== # 136
137 - def CreateBar(self):
138 """ Creates the GUI2Exe status bar. """ 139 140 # Come on, let's see how fast the menubar is able to delete the text 141 # I have in the status bar... 142 self.statusBar = self.CreateStatusBar(2, wx.ST_SIZEGRIP) 143 self.statusBar.SetStatusWidths([-1, -2]) 144 self.FillStatusBar()
145 146
147 - def MenuData(self):
148 """ Handles all the information used to build the menu bar. """ 149 150 # That's really a bunch of data... 151 152 return (("&File", 153 ("&New project...\tCtrl+N", "Add a new project to the project tree", "project", self.OnNewProject, ""), 154 ("Switch project &database...\tCtrl+D", "Load another GUI2Exe database file", "switch_db", self.OnSwitchDB, ""), 155 ("", "", "", "", ""), 156 ("&Save project\tCtrl+S", "Save the current project to database", "save_project", self.OnSaveProject, ""), 157 ("&Export setup file...\tCtrl+E", "Export the Setup.py file", "export_setup", self.OnExportSetup, ""), 158 ("", "", "", "", ""), 159 ("&Quit\tCtrl+Q", "Exit GUI2Exe", "exit", self.OnClose, "")), 160 ("&Options", 161 ("Use &AutoSave", "AutoSaves your work every minute", "", self.OnAutoSave, wx.ITEM_CHECK), 162 ('De&lete "build" directory', "Delete the build folder at every compilation", "", self.OnDeleteBuild, wx.ITEM_CHECK), 163 ("", "", "", "", ""), 164 ("&Test executable\tCtrl+R", "Test the compiled file (if it exists)", "runexe", self.OnTestExecutable, ""), 165 ("Add &custom code...\tCtrl+U", "Add custom code to the setup script", "custom_code", self.OnCustomCode, "")), 166 ("&View", 167 ("&Setup script\tCtrl+P", "View the auto-generated setup script", "view_setup", self.OnViewSetup, ""), 168 ("", "", "", "", ""), 169 ("&Missing modules\tCtrl+M", "What the compiler thinks are the missing modules", "missingmodules", self.OnViewMissing, ""), 170 ("&Binary dependencies\tCtrl+B", "What the compiler says are the binary dependencies", "binarydependencies", self.OnViewMissing, ""), 171 ("", "", "", "", ""), 172 ("Compiler s&witches\tCtrl+W", "Show compilers switches and common options", "compiler_switches", self.OnCompilerSwitches, ""), 173 ("&Tips and tricks\tCtrl+T", "Show compilation tips and tricks", "tips_and_tricks", self.OnTipsAndTricks, "")), 174 ("&Configuration", 175 ("Save &panes configuration...", "Save the current GUI panes configuration", "save_aui_config", self.OnSaveConfig, ""), 176 ("Restore original &GUI\tCtrl+G", "Restore the original GUI appearance", "restore_aui", self.OnRestorePerspective, "")), 177 ("&Help", 178 ("GUI2Exe &help\tF1", "Opens the GUI2Exe help", "help", self.OnHelp, ""), 179 ("GUI2Exe &API\tF2", "Opens the GUI2Exe API reference", "api_reference", self.OnAPI, ""), 180 ("", "", "", "", ""), 181 ("Check for &upgrade\tF5", "Check for a GUI2Exe upgrade", "upgrade", self.OnCheckUpgrade, ""), 182 ("", "", "", "", ""), 183 ("&About GUI2Exe...", "About GUI2Exe and the Creator...", "about", self.OnAbout, "")))
184 185
186 - def CreateMenu(self, menuData):
187 """ Creates a menu based on input menu data. """ 188 189 menu = wx.Menu() 190 191 # Here is a bit trickier than what presented in Robin and Noel book, 192 # but not that much. 193 for eachLabel, eachStatus, eachIcon, eachHandler, eachKind in menuData: 194 195 if not eachLabel: 196 menu.AppendSeparator() 197 continue 198 199 # I need to find which menu holds the wxAUI-based "restore perspective" 200 # as I have to bind on wx.EVT_MENU_RANGE with a specific start id 201 id = (eachLabel.find("Restore") >= 0 and [ID_FirstPerspective] or [-1])[0] 202 # There are also few check menu items around... 203 kind = (eachKind and [eachKind] or [wx.ITEM_NORMAL])[0] 204 205 menuItem = wx.MenuItem(menu, id, eachLabel, eachStatus, kind=kind) 206 if eachIcon: 207 # Check menu items usually don't have associated icons 208 menuItem.SetBitmap(self.CreateBitmap(eachIcon)) 209 210 menu.AppendItem(menuItem) 211 if eachLabel.find('"build"') >= 0: 212 # By default the "remove build directory" is on 213 menuItem.Check(True) 214 215 # Bind the event 216 self.Bind(wx.EVT_MENU, eachHandler, menuItem) 217 218 return menu
219 220
221 - def CreateMenuBar(self):
222 """ Creates the main frame menu bar. """ 223 224 menuBar = wx.MenuBar() 225 226 # loop over the bunch of data above 227 for eachMenuData in self.MenuData(): 228 menuLabel = eachMenuData[0] 229 menuItems = eachMenuData[1:] 230 menuBar.Append(self.CreateMenu(menuItems), menuLabel) 231 232 # Bind the special "restore perspective" menu item 233 self.Bind(wx.EVT_MENU_RANGE, self.OnRestorePerspective, id=ID_FirstPerspective, 234 id2=ID_FirstPerspective+1000) 235 236 # I need to keep track of this menu 237 id = menuBar.FindMenu("Configuration") 238 self.configMenu = menuBar.GetMenu(id) 239 240 # We're done 241 self.SetMenuBar(menuBar)
242 243
244 - def BuildNBImageList(self):
245 """ Builds a fake image list for wx.aui.AuiNotebook. """ 246 247 # One day someone will explain why it doesn't handle wx.ImageList 248 # like every other Book. 249 self.nbImageList = [] 250 for png in _auiImageList: 251 bmp = self.CreateBitmap(png) 252 self.nbImageList.append(bmp)
253 254
255 - def SetProperties(self):
256 """ Sets the main frame properties (title, icon...). """ 257 258 self.SetIcon(wx.Icon(opj(self.installDir + "/images/GUI2Exe.png"), wx.BITMAP_TYPE_PNG)) 259 self.SetTitle("GUI2Exe v" + __version__)
260 261
262 - def SetAllFlags(self):
263 """ Sets all the fancy flags for wxAUI and friends. """ 264 265 # Allow to have active panes and transparent dragging 266 self._mgr.SetFlags(self._mgr.GetFlags() ^ wx.aui.AUI_MGR_ALLOW_ACTIVE_PANE) 267 self._mgr.SetFlags(self._mgr.GetFlags() ^ wx.aui.AUI_MGR_TRANSPARENT_DRAG) 268 269 # Try to give a decent look to the gradients 270 self._mgr.GetArtProvider().SetColor(wx.aui.AUI_DOCKART_INACTIVE_CAPTION_GRADIENT_COLOUR, 271 wx.Colour(128, 128, 128)) 272 self._mgr.GetArtProvider().SetColor(wx.aui.AUI_DOCKART_ACTIVE_CAPTION_GRADIENT_COLOUR, 273 wx.WHITE) 274 275 # Very useful method 276 self.mainPanel.SetUniformBitmapSize((16, 16))
277 278
279 - def CheckForDatabase(self):
280 """ Checks if a database exists. If it doesn't, creates one anew. """ 281 282 # We build the database inside the user config folder, where we 283 # also create a sub-directory called /.GUI2Exe 284 standardPath = wx.StandardPaths.Get() 285 configDir = opj(standardPath.GetUserConfigDir() + "/.GUI2Exe") 286 configDb = opj(configDir + "/GUI2Exe_Database.db") 287 288 if not os.path.isfile(configDb): 289 # No database 290 if not os.path.isdir(configDir): 291 # And no directory. Create a new one. 292 os.mkdir(configDir) 293 294 return configDb
295 296
297 - def BindEvents(self):
298 """ Binds all the events related to GUI2Exe. """ 299 300 self.Bind(wx.EVT_CLOSE, self.OnClose) 301 # This one won't work in any case, I think 302 self.Bind(wx.EVT_QUERY_END_SESSION, self.OnClose) 303 # We monitor the external compilation process, when started 304 self.Bind(wx.EVT_END_PROCESS, self.OnProcessEnded) 305 self.Bind(wx.EVT_TIMER, self.OnExeTimer, self.exeTimer) 306 # Bind the timer event. This allows us to monitor either the dry-run or 307 # the real compilation using an external process which sends back to 308 # us its output and error streams, and we monitor them in this event. 309 self.Bind(wx.EVT_TIMER, self.OnProcessTimer, self.processTimer) 310 311 # Let's do some fancy checking and painting during the page changing 312 # an page closing event for wx.aui.AuiNotebook 313 self.mainPanel.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnPageClosing) 314 self.mainPanel.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGING, self.OnPageChanging)
315 316
317 - def ReadConfigurationFile(self):
318 """ Reads the default configuration file (default project initialization). """ 319 320 # This should really be moved in wx.Config 321 self.defaultConfig = {"py2exe": {}} 322 fid = open(opj(self.installDir + "/defaultConfig.txt"), "rt") 323 324 section = 0 325 326 while 1: 327 328 tline = fid.readline() 329 330 if not tline: 331 fid.close() 332 break 333 334 if tline.find("%%") >= 0: 335 # Ok, we found a compiler 336 current = self.defaultConfig[tline.replace("%%", "").strip()] 337 continue 338 339 # I need the "--" separator to differentiate between string input 340 # and other things 341 if tline.find("--") >= 0: 342 section += 1 343 continue 344 345 config, projectSwitches = tline.strip().split(":") 346 347 if section: 348 # it's not a string. I know eval is evil. 349 projectSwitches = eval(projectSwitches) 350 else: 351 # it's a string, strip it 352 projectSwitches = projectSwitches.strip() 353 354 current[config] = projectSwitches
355 356 357 # ============================== # 358 # Event handlers for GUI2Exe # 359 # ============================== # 360
361 - def OnNewProject(self, event):
362 """ A new project is being created. """ 363 364 # Send it to the project tree control 365 self.projectTree.NewProject() 366 event.Skip()
367 368
369 - def OnSwitchDB(self, event):
370 """ Switch to another project database. """ 371 372 # Not implemented yet... 373 event.Skip()
374 375
376 - def OnSaveProject(self, event):
377 """ Saves the current project. """ 378 379 project = self.GetCurrentProject() 380 if not project: 381 # No page opened, you can't fool me 382 return 383 384 # Use an accessor function, as I need it also somewhere else below 385 self.SaveProject(project)
386 387
388 - def OnExportSetup(self, event):
389 """ Exports the Setup.py file. """ 390 391 page = self.GetCurrentPage() 392 if not page: 393 # No page opened, you can't fool me 394 return 395 396 outputs = page.PrepareForCompile() 397 if not outputs: 398 # Setup.py file creation went wrong. 399 # Have you set all the required variables? 400 return 401 402 # Compilation returns a big string containing the setup script and 403 # the directory where the main script lives. 404 setupScript, buildDir = outputs 405 406 # Launch the save dialog 407 dlg = wx.FileDialog(self, message="Save file as ...", defaultDir=buildDir, 408 defaultFile="Setup.py", wildcard=_pywild, 409 style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) 410 411 # Show the dialog and retrieve the user response. If it is the OK response, 412 # process the data. 413 if dlg.ShowModal() == wx.ID_OK: 414 path = dlg.GetPath() 415 # Normally, at this point you would save your data using the file and path 416 # data that the user provided to you. 417 fp = file(path, 'w') # Create file anew 418 fp.write(setupScript) 419 fp.close() 420 self.SendMessage("Message", "File %s successfully saved"%path) 421 422 # Destroy the dialog. Don't do this until you are done with it! 423 # BAD things can happen otherwise! 424 dlg.Destroy() 425 event.Skip()
426 427
428 - def OnClose(self, event):
429 """ Handles the wx.EVT_CLOSE event for the main frame. """ 430 431 if self.exeTimer.IsRunning(): 432 # Kill the monitoring timer 433 self.exeTimer.Stop() 434 435 if self.process is not None: 436 # Try to kill the process. Doesn't work very well 437 self.process.Kill() 438 self.processTimer.Stop() 439 440 # Loop over all the opened wx.aui.AuiNotebook pages to see if 441 # there are unsaved projects 442 for pageNumber in xrange(self.mainPanel.GetPageCount()-1, -1, -1): 443 if not self.HandlePageClosing(pageNumber, event): 444 # User pressed cancel 445 return 446 447 # Un-initialize the wxAUI manager 448 self._mgr.UnInit() 449 # We're gone 450 self.Destroy()
451 452
453 - def OnPageClosing(self, event):
454 """ Handles the wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE event. """ 455 456 # Get the selected page 457 selection = event.GetSelection() 458 # Use the auxiliary method to handle this (is needed also in OnClose) 459 self.HandlePageClosing(selection, event)
460 461
462 - def OnPageChanging(self, event):
463 """ Handles the wx.aui.EVT_AUINOTEBOOK_PAGE_CHANGING event. """ 464 465 # Highlight the new item with a bold font 466 newBook = self.mainPanel.GetPage(event.GetSelection()) 467 self.projectTree.HighlightItem(newBook.GetTreeItem(), True) 468 469 if event.GetOldSelection() >= 0: 470 # Restore the original font for the old item 471 oldBook = self.mainPanel.GetPage(event.GetOldSelection()) 472 self.projectTree.HighlightItem(oldBook.GetTreeItem(), False) 473 474 event.Skip()
475 476
477 - def OnAutoSave(self, event):
478 """ Enables/Disables the AutoSave feature. """ 479 480 # Not implemented yet... 481 self.autoSave = event.IsChecked() 482 event.Skip()
483 484
485 - def OnDeleteBuild(self, event):
486 """ Enables/Disables the automatic removal of the "build" folder. """ 487 488 # That's easy, we use it later 489 self.deleteBuild = event.IsChecked() 490 event.Skip()
491 492
493 - def OnTestExecutable(self, event):
494 """ Test the compiled executable. """ 495 496 project = self.GetCurrentProject() 497 if not project: 498 # No page opened, you can't fool me 499 return 500 501 if not project.HasBeenCompiled(): 502 # The project hasn't been compiled yet 503 msg = "This project has not been compiled yet." 504 self.RunError("Error", msg) 505 return 506 507 # Try to run the successful compilation accessor method 508 self.SuccessfulCompilation(project, False)
509 510
511 - def OnCustomCode(self, event):
512 """ Allows the user to add custom code to the Setup.py file. """ 513 514 project = self.GetCurrentProject() 515 if not project: 516 # No page opened, you can't fool me 517 return 518 519 currentPage = self.mainPanel.GetSelection() 520 # Retrieve the existing custom code (if any) 521 customCode = project.GetCustomCode() 522 # Run the styled text control with the Python code in it, and 523 # allow the user to edit it. 524 frame = CustomCodeViewer(self, readOnly=False, text=customCode, project=project, 525 page=currentPage) 526 event.Skip()
527 528
529 - def OnViewSetup(self, event):
530 """ Allows the user to see the Setup.py file. """ 531 532 wx.BeginBusyCursor() 533 # I simply recreate the Setup.py script in memory 534 self.RunCompile(view=True, run=False) 535 wx.EndBusyCursor() 536 event.Skip()
537 538
539 - def OnViewMissing(self, event):
540 """ Shows the missing modules and dlls. """ 541 542 project = self.GetCurrentProject() 543 if not project: 544 # No page opened, you can't fool me 545 return 546 547 if not project.HasBeenCompiled(): 548 # The project hasn't been compiled yet 549 msg = "This project has not been compiled yet." 550 self.RunError("Error", msg) 551 return 552 553 wx.BeginBusyCursor() 554 555 # Switch between the "show missing modules" and "show binary dependencies" 556 label = self.GetMenuBar().GetLabel(event.GetId()) 557 dll = (label.find("Binary") >= 0 and [True] or [False])[0] 558 # Run the appropriate frame 559 frame = Py2ExeMissing(self, project, dll) 560 561 wx.EndBusyCursor()
562 563
564 - def OnCompilerSwitches(self, event):
565 """ Shows the different compiler switches/options. """ 566 567 webbrowser.open_new(opj(self.installDir + "/docs/switches.html")) 568 event.Skip()
569 570
571 - def OnTipsAndTricks(self, event):
572 """ Shows the compilation tips and tricks. """ 573 574 webbrowser.open_new("http://www.py2exe.org/index.cgi/WorkingWithVariousPackagesAndModules") 575 event.Skip()
576 577
578 - def OnSaveConfig(self, event):
579 """ Saves the current GUI configuration, in terms of panes positions. """ 580 581 # Ask the user to enter a configuration name 582 dlg = wx.TextEntryDialog(self, "Enter A Name For The New Configuration:", 583 "Saving Panels Configuration") 584 dlg.SetValue(("Perspective %d")%(len(self.perspectives))) 585 586 if dlg.ShowModal() != wx.ID_OK: 587 # No choice made, go back 588 return 589 590 value = dlg.GetValue() 591 dlg.Destroy() 592 593 if not value.strip(): 594 # Empty configuration name? 595 self.RunError("Error", "Invalid perspective name!") 596 return 597 598 if len(self.perspectives) == 1: 599 # Append a separator on the Configuration menu 600 self.configMenu.AppendSeparator() 601 602 # Append a new item in the Configuration menu 603 item = wx.MenuItem(self.configMenu, ID_FirstPerspective + len(self.perspectives), value, 604 "Restore GUI configuration: %s"%value) 605 item.SetBitmap(self.CreateBitmap("aui_config")) 606 self.configMenu.AppendItem(item) 607 608 # Save the current perspective 609 self.perspectives.append(self._mgr.SavePerspective()) 610 611 event.Skip()
612 613
614 - def OnRestorePerspective(self, event):
615 """ Restore the selected GUI perspective. """ 616 617 self._mgr.LoadPerspective(self.perspectives[event.GetId() - ID_FirstPerspective]) 618 self._mgr.Update()
619 620
621 - def OnHelp(self, event):
622 """ Shows the GUI2Exe help file. """ 623 624 # Not implemented yet... 625 event.Skip()
626 627
628 - def OnAPI(self, event):
629 """ Shows the GUI2Exe API help file. """ 630 631 # Not implemented yet... 632 event.Skip()
633 634
635 - def OnCheckUpgrade(self, event):
636 """ Checks for a possible upgrade of GUI2Exe. """ 637 638 # Not implemented yet... 639 event.Skip()
640 641
642 - def OnAbout(self, event):
643 """ Shows the about dialog for GUI2Exe. """ 644 645 msg = "This is the about dialog of GUI2Exe.\n\n" + \ 646 "Version %s"%__version__ + "\n"+ \ 647 "Author: Andrea Gavana @ 01 Apr 2007\n\n" + \ 648 "Please report any bug/request of improvements\n" + \ 649 "to me at the following addresses:\n\n" + \ 650 "andrea.gavana@gmail.com\ngavana@kpo.kz\n\n" + \ 651 "Thanks to Robin Dunn and the wxPython mailing list\n" + \ 652 "for the ideas and useful suggestions." 653 654 self.RunError("Message", msg) 655 event.Skip()
656 657
658 - def OnProcessTimer(self, event):
659 """ Handles the wx.EVT_TIMER event for the main frame. """ 660 661 # This event runs only when a compilation process is alive 662 # When the process finishes (or dies), we simply stop the timer 663 if self.process is not None: 664 # Defer to the Process class the message scanning 665 self.process.HandleProcessMessages() 666 667 event.Skip()
668 669
670 - def OnProcessEnded(self, event):
671 """ Handles the wx.EVT_END_PROCESS for the main frame. """ 672 673 # We stop the timer that look for the compilation steps 674 self.processTimer.Stop() 675 676 # Handle the (eventual) remaning process messages 677 self.process.HandleProcessMessages(True) 678 # Destroy the process 679 wx.CallAfter(self.process.Destroy) 680 # Re-enable the buttons at the bottom 681 self.messageWindow.EnableButtons(True) 682 683 event.Skip()
684 685
686 - def OnExeTimer(self, event):
687 """ Handles the wx.EVT_TIMER event for the main frame. """ 688 689 # Look if the log file exists 690 self.timerCount += 1 691 logFile = self.currentExe + ".log" 692 if os.path.isfile(logFile): 693 # log file is there. The executable crashed for some reason 694 self.exeTimer.Stop() 695 # Examine the log file 696 self.ExamineLogFile(logFile) 697 698 if self.timerCount > 200: 699 # More than 20 seconds elapsed... the exe works or the machine 700 # is fantastically slow. Reset everything 701 self.exeTimer.Stop() 702 self.currentExe = None 703 self.timerCount = 0 704 705 event.Skip()
706 707 708 # ============================== # 709 # Auxiliary methods for GUI2Exe # 710 # ============================== # 711
712 - def HandlePageClosing(self, selection, event):
713 714 # Let's see if the project has been saved or not 715 unSaved = self.mainPanel.GetPageBitmap(selection) == self.nbImageList[1] 716 717 # Retrieve all the information we need before closing 718 page = self.mainPanel.GetPage(selection) 719 project = page.GetProject() 720 treeItem = page.GetTreeItem() 721 # Check if it is a close event or a wx.aui.AuiNotebookEvent 722 isCloseEvent = isinstance(event, wx.CloseEvent) 723 724 if not unSaved: 725 # Mark the item as non-edited anymore (if it exists) 726 self.projectTree.SetItemEditing(treeItem, False) 727 if not isCloseEvent: 728 event.Skip() 729 return True 730 731 # Not saved. If it wasn't ever saved before, not saving will delete 732 # the item from the project tree 733 msg = "Warning: the selected page contains unsaved data.\n\nDo you wish to save this project?" 734 answer = self.RunError("Question", msg) 735 736 if answer == wx.ID_CANCEL: 737 # You want to think about it, eh? 738 event.Veto() 739 return False 740 elif answer == wx.ID_YES: 741 # Save the project, defer to the database 742 self.dataBase.SaveProject(project) 743 if not isCloseEvent: 744 # We are handling wx.aui.AuiNotebook close event 745 event.Skip() 746 else: 747 # Check if the project exists 748 projectExists = self.dataBase.IsProjectExisting(project) 749 if not isCloseEvent: 750 event.Skip() 751 752 if not projectExists: 753 # Project is not in the database, so delete the tree item 754 self.projectTree.DeleteProject([treeItem]) 755 else: 756 # Mark the item as non-edited anymore (if it exists) 757 self.projectTree.SetItemEditing(treeItem, False) 758 759 return True
760 761
762 - def SaveProject(self, project):
763 """ Saves the current project. """ 764 765 # Send the data to the database 766 self.dataBase.SaveProject(project) 767 # Visually indicates that the project has been saved 768 self.UpdatePageBitmap(project.GetName(), 0, self.mainPanel.GetSelection())
769 770
771 - def UpdatePageBitmap(self, pageName, pageIcon, selection=None):
772 """ 773 Updates the wx.aui.AuiNotebook page image and text, to reflect the 774 current project state (saved/unsaved). 775 """ 776 777 if selection is None: 778 # Get the selection ourselves 779 selection = self.mainPanel.GetSelection() 780 781 if self.mainPanel.GetPageBitmap(selection) == self.nbImageList[pageIcon]: 782 # No way, the page bitmap is the same 783 return 784 785 # Change the bitmap and the text 786 self.mainPanel.SetPageBitmap(selection, self.nbImageList[pageIcon]) 787 self.mainPanel.SetPageText(selection, pageName)
788 789
790 - def RunError(self, kind, msg, sendMessage=False):
791 """ 792 An utility method that shows a message dialog with different functionalities 793 depending on the input. 794 """ 795 796 if sendMessage: 797 # Send a message also to the log window at the bottom 798 self.SendMessage(kind, msg.strip(".")) 799 800 if kind == "Message": # is a simple message 801 style = wx.OK | wx.ICON_INFORMATION 802 elif kind == "Warning": # is a warning 803 style = wx.OK | wx.ICON_EXCLAMATION 804 elif kind == "Question": # is a question 805 style = wx.YES_NO | wx.CANCEL | wx.ICON_QUESTION 806 else: # is an error 807 style = wx.OK | wx.ICON_ERROR 808 809 # Create the message dialog 810 dlg = wx.MessageDialog(None, msg, "GUI2Exe %s"%kind, style|wx.STAY_ON_TOP) 811 answer = dlg.ShowModal() 812 dlg.Destroy() 813 814 if kind == "Question": 815 # return the answer, it was a question 816 return answer
817 818
819 - def AddNewProject(self, treeItem, projectName):
820 """ Adds a new project to the current center pane. """ 821 822 # Freeze all. It speeds up a bit the drawing 823 busy = wx.BusyInfo("Creating new project...") 824 self.Freeze() 825 826 # Create a new project, dictionary-based 827 project = Project(self.defaultConfig, projectName) 828 # The Project method adds a page to the center pane 829 # I need it in another method as I use it also elsewhere below 830 self.Project(project, treeItem, True) 831 832 # Send a message to the log window at the bottom 833 self.SendMessage("Message", 'New project "%s" added'%projectName) 834 835 # Time to warm up... 836 self.Thaw() 837 del busy
838 839
840 - def LoadProject(self, treeItem, projectName):
841 """ Loads a project in the center pane. """ 842 843 # Check if there already is a page with the same name 844 page = self.IsAlreadyOpened(treeItem) 845 if page is not None: 846 # There is, bring the page into focus 847 self.mainPanel.SetSelection(page) 848 return 849 850 # Load the project from the database 851 project = self.dataBase.LoadProject(projectName) 852 # The Project method adds a page to the center pane 853 self.Project(project, treeItem, False) 854 # Send a message to the log window at the bottom 855 self.SendMessage("Message", 'Project "%s" successfully loaded'%projectName)
856 857
858 - def RenameProject(self, treeItem, oldName, newName):
859 """ The user has renamed the project in the project tree control. """ 860 861 # Rename the project in the database 862 project = self.dataBase.RenameProject(oldName, newName) 863 # Update information on screen (if needed) 864 page = self.WalkAUIPages(treeItem) 865 if page is None: 866 # This page is not opened, go back 867 return 868 869 # Set the new name in the wx.aui.AuiNotebook tab 870 self.mainPanel.SetPageText(page, newName) 871 book = self.mainPanel.GetPage(page) 872 if project is None: 873 # Never saved in the database 874 project = book.GetProject() 875 project.SetName(newName) 876 877 book.SetProject(project) 878 # Update the label in the central panel 879 book.GetPage(0).UpdateLabel(project.GetName(), project.GetCreationDate())
880 881
882 - def Project(self, project, treeItem, isNew):
883 """ Auxiliary method used to actually add a page to the center pane. """ 884 885 # Get the project name 886 projectName = project.GetName() 887 # Add a page to the wx.aui.AuiNotebook 888 page = AUINotebookPage(self.mainPanel, project, _compilers) 889 # Check project name and bitmap depending on the isNew state 890 pageName = (isNew and [projectName+"*"] or [projectName])[0] 891 bmp = (isNew and [1] or [0])[0] 892 893 # Assing project and treeItem to the added page 894 page.SetProject(project) 895 page.SetTreeItem(treeItem) 896 897 # Add the page to the wx.aui.AuiNotebook 898 self.mainPanel.AddPage(page, pageName, True, bitmap=self.nbImageList[bmp])
899 900
901 - def WalkAUIPages(self, treeItem):
902 """ Walks over all the opened page in the center pane. """ 903 904 # Loop over all the wx.aui.AuiNotebook pages 905 for indx in xrange(self.mainPanel.GetPageCount()): 906 page = self.mainPanel.GetPage(indx) 907 if page.GetTreeItem() == treeItem: 908 # Yes, the page is already opened 909 return indx 910 911 # No page like that is there 912 return None
913 914
915 - def IsAlreadyOpened(self, treeItem):
916 """ Looks if a page is already opened. """ 917 918 return self.WalkAUIPages(treeItem)
919 920
921 - def CloseAssociatedPage(self, treeItem):
922 """ 923 A method used to close a wx.aui.AuiNotebook page when an item in the 924 project tree is deleted. 925 """ 926 927 page = self.WalkAUIPages(treeItem) 928 if page is not None: 929 self.mainPanel.DeletePage(page)
930 931
932 - def ReassignPageItem(self, oldItem, newItem):
933 """ Reassigns an item to an opened page after a drag and drop operation. """ 934 935 page = self.WalkAUIPages(oldItem) 936 if page is not None: 937 # We found an opened page 938 page = self.mainPanel.GetPage(page) 939 page.SetTreeItem(newItem)
940 941
942 - def FillStatusBar(self):
943 """ Fills the statusbar fields with different information. """ 944 945 # Get the wxPython version information 946 wxPythonVersion = wx.version() 947 statusText = "wxPython %s"%wxPythonVersion 948 949 for compiler, version in _compilers.items(): 950 # adds the compilers information found 951 statusText += ", %s %s"%(compiler, version) 952 953 # Ah, by the way, thank you menu bar for deleting my status bar messages... 954 self.statusBar.SetStatusText(statusText, 1) 955 self.statusBar.SetStatusText("Welcome to GUI2Exe", 0)
956 957
958 - def CreateBitmap(self, fileName):
959 """ Utility function to create bitmap passing a filename. """ 960 961 bmp = wx.Bitmap(opj(self.installDir + "/images/%s.png"%fileName), wx.BITMAP_TYPE_PNG) 962 return bmp
963 964
965 - def GetCurrentPage(self):
966 """ Returns the current LabelBook page. """ 967 968 book = self.GetCurrentBook() 969 if not book: 970 # No page opened, you can't fool me 971 return 972 973 return book.GetPage(book.GetSelection())
974 975
976 - def GetCurrentBook(self):
977 """ Returns the current wx.aui.AuiNotebook page (a LabelBook). """ 978 979 if self.mainPanel.GetPageCount() == 0: 980 # No page opened, fire an error 981 msg = "No project has been loaded" 982 self.RunError("Error", msg +".", True) 983 return None 984 985 # Return the current page (is a LabelBook) 986 selection = self.mainPanel.GetSelection() 987 return self.mainPanel.GetPage(selection)
988 989
990 - def GetCurrentProject(self):
991 """ Returns the current project associated to a LabelBook. """ 992 993 book = self.GetCurrentBook() 994 if not book: 995 # No page opened, you can't fool me 996 return 997 998 return book.GetProject()
999 1000
1001 - def RunCompile(self, view, run):
1002 """ 1003 Auxiliary method. Depending on the input, it does different things: 1004 - view=True ==> Get the Setup.py script and displays it (run is discarded); 1005 - run=False ==> Start a dry-run (a compilation that does nothing); 1006 - run=True ==> Start the real compilation process. 1007 """ 1008 1009 page = self.GetCurrentPage() 1010 if not page: 1011 # No page opened, you can't fool me 1012 return 1013 1014 outputs = page.PrepareForCompile() 1015 if not outputs: 1016 # Setup.py file creation went wrong. 1017 # Have you set all the required variables? 1018 return 1019 1020 setupScript, buildDir = outputs 1021 1022 if view: # we just want to view the code 1023 frame = CustomCodeViewer(self, readOnly=True, text=setupScript) 1024 return 1025 1026 # Disable the run buttons 1027 self.messageWindow.EnableButtons(False) 1028 1029 # Get all the information we need about this project 1030 compiler = page.GetName() 1031 project = self.GetCurrentProject() 1032 currentPage = self.mainPanel.GetSelection() 1033 1034 # Start the external process, which is actually subclassed in 1035 # The Process class 1036 self.process = Process(self, buildDir, setupScript, run, compiler, 1037 project, currentPage) 1038 1039 # Start the monitoring timer 1040 self.processTimer.Start(500) 1041 # Start the process 1042 self.process.Start()
1043 1044
1045 - def KillCompile(self):
1046 """ Kills (or tries to) the compilation process. """ 1047 1048 if self.process: 1049 # Stop the process timer 1050 self.processTimer.Stop() 1051 # This doesn't work, at least on Windows. 1052 # The process is still there and there is no os.kill on Windows 1053 self.process.Kill() 1054 # Send a failure message to the log window at the bottom 1055 self.SendMessage("Warning", "Compilation killed by user") 1056 # Re-enable the run buttons 1057 self.messageWindow.EnableButtons(True)
1058 1059
1060 - def SuccessfulCompilation(self, project, ask=True):
1061 """ 1062 Assumes that the compilation process was successful and tries to test 1063 the new exe file. 1064 """ 1065 1066 if ask: 1067 # We came from an wx.EVT_END_PROCESS event 1068 msg = "The compiler has successfully built your executable.\n" \ 1069 "Do you wish to test your application?" 1070 answer = self.RunError("Question", msg) 1071 if answer != wx.ID_YES: 1072 return 1073 1074 # Get the executable name from the Project class 1075 exeName = project.GetExecutableName("py2exe") 1076 if not os.path.isfile(exeName): 1077 # No such file, have you compiled it? 1078 msg = "This project has never been compiled or its executable has been deleted." 1079 self.RunError("Error", msg) 1080 return 1081 1082 # Starts the compiled exe file 1083 msg = "Starting compiled executable..." 1084 busy = wx.BusyInfo(msg) 1085 self.SendMessage("Message", msg) 1086 1087 logFile = exeName + ".log" 1088 # Remove the log file or it will fool us later 1089 if os.path.isfile(logFile): 1090 try: 1091 os.remove(logFile) 1092 except: 1093 pass 1094 1095 self.currentExe = exeName 1096 # Start the monitoring timer to check if a log file is created 1097 self.exeTimer.Start(100) 1098 os.spawnl(os.P_NOWAIT, exeName) 1099 1100 del busy 1101 wx.SafeYield()
1102 1103
1104 - def ExamineLogFile(self, logFile):
1105 """ Examine a log file, created by an executable that crashed fpr some reason. """ 1106 1107 # Reset few variables, we don't need them anymore 1108 self.currentExe = None 1109 self.timerCount = 0 1110 1111 # Send the error message to the log window at the bottom 1112 self.SendMessage("Error", "Executable terminated with errors or warnings") 1113 1114 # Ask the user if he/she wants to examine the tracebacks in the log file 1115 msg = "It appears that the executable generated errors in file:\n\n%s" % logFile 1116 msg += "\n\nDo you want to examine the tracebacks?" 1117 answer = self.RunError("Question", msg) 1118 if answer != wx.ID_YES: 1119 return 1120 1121 wx.BeginBusyCursor() 1122 1123 # Read the log file 1124 fid = open(logFile, "rt") 1125 msg = fid.read() 1126 fid.close() 1127 # And displays it in a scrolled message dialog 1128 dlg = wx.lib.dialogs.ScrolledMessageDialog(self, msg, "Tracebacks in log file") 1129 wx.EndBusyCursor() 1130 dlg.ShowModal() 1131 dlg.Destroy()
1132 1133
1134 - def SendMessage(self, kind, message):
1135 """ Sends a message to the log window at the bottom. """ 1136 1137 self.messageWindow.SendMessage(kind, message)
1138 1139
1140 - def GetVersion(self):
1141 """ Return the current GUI2Exe version. """ 1142 1143 return __version__
1144 1145
1146 -class GUI2ExeSplashScreen(AS.AdvancedSplash):
1147
1148 - def __init__(self, app):
1149 """ A fancy splash screen :-D """ 1150 1151 bmp = wx.Bitmap("images/gui2exe_splash.png", wx.BITMAP_TYPE_PNG) 1152 AS.AdvancedSplash.__init__(self, None, bitmap=bmp, timeout=5000, 1153 extrastyle=AS.AS_TIMEOUT|AS.AS_CENTER_ON_SCREEN) 1154 1155 self.Bind(wx.EVT_CLOSE, self.OnClose) 1156 self.fc = wx.FutureCall(2000, self.ShowMain) 1157 self.app = app
1158 1159
1160 - def OnClose(self, evt):
1161 """ Handles the wx.EVT_CLOSE event for GUI2ExeSplashScreen. """ 1162 1163 # Make sure the default handler runs too so this window gets 1164 # destroyed 1165 evt.Skip() 1166 self.Hide() 1167 1168 # if the timer is still running then go ahead and show the 1169 # main frame now 1170 if self.fc.IsRunning(): 1171 # Stop the wx.FutureCall timer 1172 self.fc.Stop() 1173 self.ShowMain()
1174 1175
1176 - def ShowMain(self):
1177 """ Shows the main application (GUI2Exe). """ 1178 1179 # Get the size of the display 1180 size = wx.GetDisplaySize() 1181 # We run at 5/6 of that size 1182 xvideo, yvideo = 5*size.x/6, 5*size.y/6 1183 frame = GUI2Exe(None, -1, "", size=(xvideo, yvideo)) 1184 1185 self.app.SetTopWindow(frame) 1186 frame.CenterOnScreen() 1187 frame.Show() 1188 1189 if self.fc.IsRunning(): 1190 self.Raise()
1191 1192
1193 -class GUI2ExeApp(wx.App):
1194
1195 - def OnInit(self):
1196 1197 splash = GUI2ExeSplashScreen(self) 1198 splash.Show() 1199 1200 self.SetAppName("GUI2Exe") 1201 1202 return True
1203 1204 1205 if __name__ == "__main__": 1206 app = GUI2ExeApp(0) 1207 app.MainLoop() 1208