You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
10 KiB

  1. """
  2. Primary orchestration and control point for Gooey.
  3. """
  4. import sys
  5. import wx
  6. from wx.adv import TaskBarIcon
  7. from gooey.gui import cli
  8. from gooey.gui import events
  9. from gooey.gui import seeder
  10. from gooey.gui.components import modals
  11. from gooey.gui.components.config import ConfigPage, TabbedConfigPage
  12. from gooey.gui.components.console import Console
  13. from gooey.gui.components.footer import Footer
  14. from gooey.gui.components.header import FrameHeader
  15. from gooey.gui.components.menubar import MenuBar
  16. from gooey.gui.components.sidebar import Sidebar
  17. from gooey.gui.components.tabbar import Tabbar
  18. from gooey.gui.lang.i18n import _
  19. from gooey.gui.processor import ProcessController
  20. from gooey.gui.pubsub import pub
  21. from gooey.gui.util import wx_util
  22. from gooey.gui.util.wx_util import transactUI
  23. from gooey.python_bindings import constants
  24. class GooeyApplication(wx.Frame):
  25. """
  26. Main window for Gooey.
  27. """
  28. def __init__(self, buildSpec, *args, **kwargs):
  29. super(GooeyApplication, self).__init__(None, *args, **kwargs)
  30. self._state = {}
  31. self.buildSpec = buildSpec
  32. self.applyConfiguration()
  33. self.menu = MenuBar(buildSpec)
  34. self.SetMenuBar(self.menu)
  35. self.header = FrameHeader(self, buildSpec)
  36. self.configs = self.buildConfigPanels(self)
  37. self.navbar = self.buildNavigation()
  38. self.footer = Footer(self, buildSpec)
  39. self.console = Console(self, buildSpec)
  40. self.layoutComponent()
  41. self.clientRunner = ProcessController(
  42. self.buildSpec.get('progress_regex'),
  43. self.buildSpec.get('progress_expr'),
  44. self.buildSpec.get('hide_progress_msg'),
  45. self.buildSpec.get('encoding'),
  46. self.buildSpec.get('requires_shell'),
  47. )
  48. pub.subscribe(events.WINDOW_START, self.onStart)
  49. pub.subscribe(events.WINDOW_RESTART, self.onStart)
  50. pub.subscribe(events.WINDOW_STOP, self.onStopExecution)
  51. pub.subscribe(events.WINDOW_CLOSE, self.onClose)
  52. pub.subscribe(events.WINDOW_CANCEL, self.onCancel)
  53. pub.subscribe(events.WINDOW_EDIT, self.onEdit)
  54. pub.subscribe(events.CONSOLE_UPDATE, self.console.logOutput)
  55. pub.subscribe(events.EXECUTION_COMPLETE, self.onComplete)
  56. pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar)
  57. # Top level wx close event
  58. self.Bind(wx.EVT_CLOSE, self.onClose)
  59. if self.buildSpec['poll_external_updates']:
  60. self.fetchExternalUpdates()
  61. if self.buildSpec.get('auto_start', False):
  62. self.onStart()
  63. def applyConfiguration(self):
  64. self.SetTitle(self.buildSpec['program_name'])
  65. self.SetBackgroundColour(self.buildSpec.get('body_bg_color'))
  66. def onStart(self, *args, **kwarg):
  67. """
  68. Verify user input and kick off the client's program if valid
  69. """
  70. with transactUI(self):
  71. config = self.navbar.getActiveConfig()
  72. config.resetErrors()
  73. if config.isValid():
  74. if self.buildSpec['clear_before_run']:
  75. self.console.clear()
  76. self.clientRunner.run(self.buildCliString())
  77. self.showConsole()
  78. else:
  79. config.displayErrors()
  80. self.Layout()
  81. def onEdit(self):
  82. """Return the user to the settings screen for further editing"""
  83. with transactUI(self):
  84. if self.buildSpec['poll_external_updates']:
  85. self.fetchExternalUpdates()
  86. self.showSettings()
  87. def buildCliString(self):
  88. """
  89. Collect all of the required information from the config screen and
  90. build a CLI string which can be used to invoke the client program
  91. """
  92. config = self.navbar.getActiveConfig()
  93. group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()]
  94. positional = config.getPositionalArgs()
  95. optional = config.getOptionalArgs()
  96. return cli.buildCliString(
  97. self.buildSpec['target'],
  98. group['command'],
  99. positional,
  100. optional,
  101. suppress_gooey_flag=self.buildSpec['suppress_gooey_flag']
  102. )
  103. def onComplete(self, *args, **kwargs):
  104. """
  105. Display the appropriate screen based on the success/fail of the
  106. host program
  107. """
  108. with transactUI(self):
  109. if self.clientRunner.was_success():
  110. if self.buildSpec.get('return_to_config', False):
  111. self.showSettings()
  112. else:
  113. self.showSuccess()
  114. if self.buildSpec.get('show_success_modal', True):
  115. wx.CallAfter(modals.showSuccess)
  116. else:
  117. if self.clientRunner.wasForcefullyStopped:
  118. self.showForceStopped()
  119. else:
  120. self.showError()
  121. if self.buildSpec.get('show_failure_modal'):
  122. wx.CallAfter(modals.showFailure)
  123. def onStopExecution(self):
  124. """Displays a scary message and then force-quits the executing
  125. client code if the user accepts"""
  126. if not self.buildSpec['show_stop_warning'] or modals.confirmForceStop():
  127. self.clientRunner.stop()
  128. def fetchExternalUpdates(self):
  129. """
  130. !Experimental!
  131. Calls out to the client code requesting seed values to use in the UI
  132. !Experimental!
  133. """
  134. seeds = seeder.fetchDynamicProperties(
  135. self.buildSpec['target'],
  136. self.buildSpec['encoding']
  137. )
  138. for config in self.configs:
  139. config.seedUI(seeds)
  140. def onCancel(self):
  141. """Close the program after confirming"""
  142. if modals.confirmExit():
  143. self.onClose()
  144. def onClose(self, *args, **kwargs):
  145. """Stop any actively running client program, cleanup the top
  146. level WxFrame and shutdown the current process"""
  147. # issue #592 - we need to run the same onStopExecution machinery
  148. # when the exit button is clicked to ensure everything is cleaned
  149. # up correctly.
  150. if self.clientRunner.running():
  151. self.onStopExecution()
  152. self.Destroy()
  153. sys.exit()
  154. def layoutComponent(self):
  155. sizer = wx.BoxSizer(wx.VERTICAL)
  156. sizer.Add(self.header, 0, wx.EXPAND)
  157. sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)
  158. sizer.Add(self.navbar, 1, wx.EXPAND)
  159. sizer.Add(self.console, 1, wx.EXPAND)
  160. sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND)
  161. sizer.Add(self.footer, 0, wx.EXPAND)
  162. self.SetMinSize((400, 300))
  163. self.SetSize(self.buildSpec['default_size'])
  164. self.SetSizer(sizer)
  165. self.console.Hide()
  166. self.Layout()
  167. if self.buildSpec.get('fullscreen', True):
  168. self.ShowFullScreen(True)
  169. # Program Icon (Windows)
  170. icon = wx.Icon(self.buildSpec['images']['programIcon'], wx.BITMAP_TYPE_PNG)
  171. self.SetIcon(icon)
  172. if sys.platform != 'win32':
  173. # OSX needs to have its taskbar icon explicitly set
  174. # bizarrely, wx requires the TaskBarIcon to be attached to the Frame
  175. # as instance data (self.). Otherwise, it will not render correctly.
  176. self.taskbarIcon = TaskBarIcon(iconType=wx.adv.TBI_DOCK)
  177. self.taskbarIcon.SetIcon(icon)
  178. def buildNavigation(self):
  179. """
  180. Chooses the appropriate layout navigation component based on user prefs
  181. """
  182. if self.buildSpec['navigation'] == constants.TABBED:
  183. navigation = Tabbar(self, self.buildSpec, self.configs)
  184. else:
  185. navigation = Sidebar(self, self.buildSpec, self.configs)
  186. if self.buildSpec['navigation'] == constants.HIDDEN:
  187. navigation.Hide()
  188. return navigation
  189. def buildConfigPanels(self, parent):
  190. page_class = TabbedConfigPage if self.buildSpec['tabbed_groups'] else ConfigPage
  191. return [page_class(parent, widgets, self.buildSpec)
  192. for widgets in self.buildSpec['widgets'].values()]
  193. def showSettings(self):
  194. self.navbar.Show(True)
  195. self.console.Show(False)
  196. self.header.setImage('settings_img')
  197. self.header.setTitle(_("settings_title"))
  198. self.header.setSubtitle(self.buildSpec['program_description'])
  199. self.footer.showButtons('cancel_button', 'start_button')
  200. self.footer.progress_bar.Show(False)
  201. def showConsole(self):
  202. self.navbar.Show(False)
  203. self.console.Show(True)
  204. self.header.setImage('running_img')
  205. self.header.setTitle(_("running_title"))
  206. self.header.setSubtitle(_('running_msg'))
  207. self.footer.showButtons('stop_button')
  208. self.footer.progress_bar.Show(True)
  209. if not self.buildSpec['progress_regex']:
  210. self.footer.progress_bar.Pulse()
  211. def showComplete(self):
  212. self.navbar.Show(False)
  213. self.console.Show(True)
  214. buttons = (['edit_button', 'restart_button', 'close_button']
  215. if self.buildSpec.get('show_restart_button', True)
  216. else ['edit_button', 'close_button'])
  217. self.footer.showButtons(*buttons)
  218. self.footer.progress_bar.Show(False)
  219. def showSuccess(self):
  220. self.showComplete()
  221. self.header.setImage('check_mark')
  222. self.header.setTitle(_('finished_title'))
  223. self.header.setSubtitle(_('finished_msg'))
  224. self.Layout()
  225. def showError(self):
  226. self.showComplete()
  227. self.header.setImage('error_symbol')
  228. self.header.setTitle(_('finished_title'))
  229. self.header.setSubtitle(_('finished_error'))
  230. def showForceStopped(self):
  231. self.showComplete()
  232. if self.buildSpec.get('force_stop_is_error', True):
  233. self.showError()
  234. else:
  235. self.showSuccess()
  236. self.header.setSubtitle(_('finished_forced_quit'))