Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 

306 řádky
10 KiB

  1. try:
  2. import wx
  3. except ModuleNotFoundError:
  4. raise ImportError('Module wxPython missing')
  5. import platform
  6. from typing import Union
  7. import webbrowser
  8. from threading import Thread
  9. from datetime import date
  10. from . import panel
  11. from ..data import Position, AsterEphemerides, Event, MoonPhase
  12. from ..exceptions import UnavailableFeatureError
  13. from ..ephemerides import get_ephemerides, get_moon_phase
  14. from ..events import search_events
  15. from ..version import VERSION
  16. from ..dumper import PdfDumper
  17. from ..i18n import _
  18. OS_DARWIN = 'Darwin'
  19. WINDOW_TITLE = 'Kosmorro'
  20. MIN_SIZE = wx.Size(700, 200) if platform.system() != OS_DARWIN else wx.Size(700, 160)
  21. # Events definitions
  22. myEVT_EPHEMERIDES_COMPUTED = wx.NewEventType()
  23. EVT_EPHEMERIDES_COMPUTED = wx.PyEventBinder(myEVT_EPHEMERIDES_COMPUTED, 1)
  24. class EphemeridesComputedEvent(wx.PyCommandEvent):
  25. def __init__(self, etype, eid, value=None):
  26. super(EphemeridesComputedEvent, self).__init__(etype, eid)
  27. self.value = value
  28. myEVT_MOON_PHASE_COMPUTED = wx.NewEventType()
  29. EVT_MOON_PHASE_COMPUTED = wx.PyEventBinder(myEVT_MOON_PHASE_COMPUTED, 1)
  30. class MoonPhaseComputedEvent(wx.PyCommandEvent):
  31. def __init__(self, etype, eid, value=None):
  32. super(MoonPhaseComputedEvent, self).__init__(etype, eid)
  33. self.value = value
  34. myEVT_EVENTS_COMPUTED = wx.NewEventType()
  35. EVT_EVENTS_COMPUTED = wx.PyEventBinder(myEVT_EVENTS_COMPUTED, 1)
  36. class EventsComputedEvent(wx.PyCommandEvent):
  37. def __init__(self, etype, eid, value=None):
  38. super(EventsComputedEvent, self).__init__(etype, eid)
  39. self.value = value
  40. # Computing threads
  41. class MoonPhaseComputer(Thread):
  42. def __init__(self, parent, compute_date: date):
  43. super(MoonPhaseComputer, self).__init__()
  44. self._parent = parent
  45. self.compute_date = compute_date
  46. self.moon_phase = None
  47. def run(self):
  48. self.moon_phase = get_moon_phase(self.compute_date)
  49. wx.PostEvent(self._parent, EphemeridesComputedEvent(myEVT_MOON_PHASE_COMPUTED, -1, self.moon_phase))
  50. class EphemeridesComputer(Thread):
  51. def __init__(self, parent, compute_date: date, position: Position, timezone: int):
  52. super(EphemeridesComputer, self).__init__()
  53. self._parent = parent
  54. self.compute_date = compute_date
  55. self.position = position
  56. self.timezone = timezone
  57. self.ephemerides = []
  58. def run(self):
  59. self.ephemerides = get_ephemerides(self.compute_date, self.position, self.timezone)
  60. wx.PostEvent(self._parent, EphemeridesComputedEvent(myEVT_EPHEMERIDES_COMPUTED, -1, self.ephemerides))
  61. class EventsComputer(Thread):
  62. def __init__(self, parent, compute_date: date, timezone: int):
  63. super(EventsComputer, self).__init__()
  64. self._parent = parent
  65. self.compute_date = compute_date
  66. self.timezone = timezone
  67. self.events = []
  68. def run(self):
  69. self.events = search_events(self.compute_date, self.timezone)
  70. wx.PostEvent(self._parent, EventsComputedEvent(myEVT_EVENTS_COMPUTED, -1, self.events))
  71. # Main window
  72. class MainWindow(wx.Frame):
  73. ephemerides: [AsterEphemerides]
  74. events: [Event]
  75. moon_phase: Union[None, MoonPhase]
  76. _progress_bar: wx.Gauge
  77. def __init__(self):
  78. super(MainWindow, self).__init__(None, title=WINDOW_TITLE,
  79. style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX))
  80. self.export_path = None
  81. self.config_panel = panel.ConfigPanel(self)
  82. self.Bind(panel.EVT_COMPUTE_BUTTON, self.compute)
  83. self.Bind(panel.EVT_EXPORT_BUTTON, self.export_button_clicked)
  84. self.result_presenter = panel.ResultPanel(self)
  85. self.sizer = wx.BoxSizer(wx.VERTICAL)
  86. self.sizer.AddMany([(self.config_panel, 0, wx.EXPAND | wx.ALL, 5),
  87. (self.result_presenter, 0, wx.EXPAND | wx.ALL, 5)])
  88. self.SetMinSize(MIN_SIZE)
  89. self.make_menu_bar()
  90. self.make_status_bar()
  91. self._progress_bar.Hide()
  92. self.SetSizer(self.sizer)
  93. self.resize(center=True)
  94. def make_status_bar(self):
  95. status_bar = self.CreateStatusBar()
  96. status_bar.SetFieldsCount(2)
  97. status_bar.SetStatusWidths([-2, -1])
  98. self._progress_bar = wx.Gauge(status_bar, -1, style=wx.GA_HORIZONTAL | wx.GA_SMOOTH)
  99. rect = status_bar.GetFieldRect(1)
  100. self._progress_bar.SetPosition((rect.x + 2, rect.y + 2))
  101. self._progress_bar.SetSize((rect.width - 4, rect.height - 4))
  102. self.result_presenter.Hide()
  103. def resize(self, center: bool = False):
  104. self.Fit()
  105. if center:
  106. self.Center()
  107. def make_menu_bar(self):
  108. menu_bar = wx.MenuBar()
  109. if platform.system() != OS_DARWIN:
  110. # "Application" menu
  111. app_menu = wx.Menu()
  112. exit_item = app_menu.Append(wx.ID_EXIT)
  113. # "Help" menu
  114. help_menu = wx.Menu()
  115. about_item = help_menu.Append(wx.ID_ABOUT)
  116. menu_bar.Append(app_menu, _('&Application'))
  117. menu_bar.Append(help_menu, _('&Help'))
  118. self.Bind(wx.EVT_MENU, self.on_exit, exit_item)
  119. else:
  120. # On macOS, put the menu items to the place they belong to: the application name menu
  121. apple_menu = menu_bar.OSXGetAppleMenu()
  122. about_item = apple_menu.Insert(0, wx.ID_ABOUT)
  123. self.Bind(wx.EVT_MENU, self.on_about, about_item)
  124. self.SetMenuBar(menu_bar)
  125. def on_exit(self, event):
  126. self.Close(True)
  127. def on_about(self, event):
  128. message = wx.MessageDialog(caption='Kosmorro version %s' % VERSION,
  129. message='© Jérôme Deuchnord - 2020\n\n' +
  130. _('This is a free software licensed under the '
  131. 'GNU Affero General Public License.'),
  132. style=wx.OK | wx.HELP | wx.ICON_INFORMATION,
  133. parent=self)
  134. message.SetOKLabel(_('Close'))
  135. message.SetHelpLabel(_('Website'))
  136. btn = message.ShowModal()
  137. if btn == wx.ID_HELP:
  138. webbrowser.open('http://kosmorro.space')
  139. def compute(self, __=None):
  140. # Reset data
  141. self.moon_phase = None
  142. self.ephemerides = None
  143. self.events = None
  144. self.config_panel.disable_buttons()
  145. self.SetCursor(wx.Cursor(wx.CURSOR_WAIT))
  146. self.SetStatusText(_('Computing…'))
  147. self._progress_bar.SetValue(0)
  148. self._progress_bar.Show()
  149. thread_moon_phase = MoonPhaseComputer(self, self.config_panel.compute_date)
  150. thread_ephemerides = EphemeridesComputer(self, self.config_panel.compute_date, self.config_panel.position,
  151. self.config_panel.timezone)
  152. thread_events = EventsComputer(self, self.config_panel.compute_date, self.config_panel.timezone)
  153. self.Bind(EVT_MOON_PHASE_COMPUTED, self.on_moon_phase_computed)
  154. self.Bind(EVT_EPHEMERIDES_COMPUTED, self.on_ephemerides_computed)
  155. self.Bind(EVT_EVENTS_COMPUTED, self.on_events_computed)
  156. thread_moon_phase.start()
  157. if self.config_panel.activate_position and self.config_panel.position is not None:
  158. thread_ephemerides.start()
  159. else:
  160. self.ephemerides = []
  161. self._progress_bar.SetValue(50)
  162. thread_events.start()
  163. def export_button_clicked(self, __):
  164. self.export_path = self.get_export_file_path()
  165. if self.export_path is None:
  166. self.SetStatusText('Aborted.')
  167. return
  168. self.compute()
  169. def on_moon_phase_computed(self, event):
  170. self.moon_phase = event.value
  171. self._progress_bar.SetValue(self._progress_bar.GetValue() + 10)
  172. if self.is_all_computed():
  173. self.process_results()
  174. def on_ephemerides_computed(self, event):
  175. self.ephemerides = event.value
  176. self._progress_bar.SetValue(self._progress_bar.GetValue() + 50)
  177. if self.is_all_computed():
  178. self.process_results()
  179. def on_events_computed(self, event):
  180. self.events = event.value
  181. self._progress_bar.SetValue(self._progress_bar.GetValue() + 40)
  182. if self.is_all_computed():
  183. self.process_results()
  184. def is_all_computed(self) -> bool:
  185. return self.moon_phase is not None and \
  186. self.ephemerides is not None and \
  187. self.events is not None
  188. def process_results(self):
  189. self.config_panel.enable_buttons()
  190. self.SetStatusText('')
  191. self._progress_bar.Hide()
  192. self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))
  193. if self.export_path is not None:
  194. self.export()
  195. else:
  196. self.show_results()
  197. def export(self, dumper_class = PdfDumper):
  198. ephemerides = self.ephemerides if len(self.ephemerides) > 0 else None
  199. dumper = dumper_class(date=self.config_panel.compute_date, ephemerides=ephemerides,
  200. events=self.events, moon_phase=self.moon_phase, timezone=self.config_panel.timezone,
  201. show_graph=True)
  202. try:
  203. with open(self.export_path, 'wb') as file:
  204. file.write(dumper.to_string())
  205. self.SetStatusText(_('PDF export saved in "{path}"!').format(path=self.export_path))
  206. except UnavailableFeatureError as error:
  207. wx.MessageDialog(self, error.msg, caption=_('Error while exporting your document'),
  208. style=wx.OK | wx.ICON_ERROR).ShowModal()
  209. finally:
  210. self.export_path = None
  211. def show_results(self):
  212. self.result_presenter.moon_phase = self.moon_phase
  213. self.result_presenter.ephemerides = self.ephemerides
  214. self.result_presenter.events = self.events
  215. self.result_presenter.render()
  216. self.result_presenter.Show()
  217. self.resize(center=True)
  218. def get_export_file_path(self) -> Union[None, str]:
  219. dialog = wx.FileDialog(parent=self, message=_('Please select an export file location'),
  220. wildcard='*.pdf', style=wx.FD_SAVE)
  221. if dialog.ShowModal() == wx.ID_OK:
  222. return dialog.GetPath()
  223. else:
  224. return None
  225. def start() -> bool:
  226. app = wx.App()
  227. app.SetAppName('Kosmorro')
  228. window = MainWindow()
  229. window.Show()
  230. return app.MainLoop() == 0