try: import wx except ModuleNotFoundError: raise ImportError('Module wxPython missing') import platform from typing import Union import webbrowser from threading import Thread from datetime import date from . import panel from ..data import Position, AsterEphemerides, Event, MoonPhase from ..exceptions import UnavailableFeatureError from ..ephemerides import get_ephemerides, get_moon_phase from ..events import search_events from ..version import VERSION from ..dumper import PdfDumper from ..i18n import _ OS_DARWIN = 'Darwin' WINDOW_TITLE = 'Kosmorro' MIN_SIZE = wx.Size(700, 200) if platform.system() != OS_DARWIN else wx.Size(700, 160) # Events definitions myEVT_EPHEMERIDES_COMPUTED = wx.NewEventType() EVT_EPHEMERIDES_COMPUTED = wx.PyEventBinder(myEVT_EPHEMERIDES_COMPUTED, 1) class EphemeridesComputedEvent(wx.PyCommandEvent): def __init__(self, etype, eid, value=None): super(EphemeridesComputedEvent, self).__init__(etype, eid) self.value = value myEVT_MOON_PHASE_COMPUTED = wx.NewEventType() EVT_MOON_PHASE_COMPUTED = wx.PyEventBinder(myEVT_MOON_PHASE_COMPUTED, 1) class MoonPhaseComputedEvent(wx.PyCommandEvent): def __init__(self, etype, eid, value=None): super(MoonPhaseComputedEvent, self).__init__(etype, eid) self.value = value myEVT_EVENTS_COMPUTED = wx.NewEventType() EVT_EVENTS_COMPUTED = wx.PyEventBinder(myEVT_EVENTS_COMPUTED, 1) class EventsComputedEvent(wx.PyCommandEvent): def __init__(self, etype, eid, value=None): super(EventsComputedEvent, self).__init__(etype, eid) self.value = value # Computing threads class MoonPhaseComputer(Thread): def __init__(self, parent, compute_date: date): super(MoonPhaseComputer, self).__init__() self._parent = parent self.compute_date = compute_date self.moon_phase = None def run(self): self.moon_phase = get_moon_phase(self.compute_date) wx.PostEvent(self._parent, EphemeridesComputedEvent(myEVT_MOON_PHASE_COMPUTED, -1, self.moon_phase)) class EphemeridesComputer(Thread): def __init__(self, parent, compute_date: date, position: Position, timezone: int): super(EphemeridesComputer, self).__init__() self._parent = parent self.compute_date = compute_date self.position = position self.timezone = timezone self.ephemerides = [] def run(self): self.ephemerides = get_ephemerides(self.compute_date, self.position, self.timezone) wx.PostEvent(self._parent, EphemeridesComputedEvent(myEVT_EPHEMERIDES_COMPUTED, -1, self.ephemerides)) class EventsComputer(Thread): def __init__(self, parent, compute_date: date, timezone: int): super(EventsComputer, self).__init__() self._parent = parent self.compute_date = compute_date self.timezone = timezone self.events = [] def run(self): self.events = search_events(self.compute_date, self.timezone) wx.PostEvent(self._parent, EventsComputedEvent(myEVT_EVENTS_COMPUTED, -1, self.events)) # Main window class MainWindow(wx.Frame): ephemerides: [AsterEphemerides] events: [Event] moon_phase: Union[None, MoonPhase] _progress_bar: wx.Gauge def __init__(self): super(MainWindow, self).__init__(None, title=WINDOW_TITLE, style=wx.DEFAULT_FRAME_STYLE & ~(wx.RESIZE_BORDER | wx.MAXIMIZE_BOX)) self.export_path = None self.config_panel = panel.ConfigPanel(self) self.Bind(panel.EVT_COMPUTE_BUTTON, self.compute) self.Bind(panel.EVT_EXPORT_BUTTON, self.export_button_clicked) self.result_presenter = panel.ResultPanel(self) self.sizer = wx.BoxSizer(wx.VERTICAL) self.sizer.AddMany([(self.config_panel, 0, wx.EXPAND | wx.ALL, 5), (self.result_presenter, 0, wx.EXPAND | wx.ALL, 5)]) self.SetMinSize(MIN_SIZE) self.make_menu_bar() self.make_status_bar() self._progress_bar.Hide() self.SetSizer(self.sizer) self.resize(center=True) def make_status_bar(self): status_bar = self.CreateStatusBar() status_bar.SetFieldsCount(2) status_bar.SetStatusWidths([-2, -1]) self._progress_bar = wx.Gauge(status_bar, -1, style=wx.GA_HORIZONTAL | wx.GA_SMOOTH) rect = status_bar.GetFieldRect(1) self._progress_bar.SetPosition((rect.x + 2, rect.y + 2)) self._progress_bar.SetSize((rect.width - 4, rect.height - 4)) self.result_presenter.Hide() def resize(self, center: bool = False): self.Fit() if center: self.Center() def make_menu_bar(self): menu_bar = wx.MenuBar() if platform.system() != OS_DARWIN: # "Application" menu app_menu = wx.Menu() exit_item = app_menu.Append(wx.ID_EXIT) # "Help" menu help_menu = wx.Menu() about_item = help_menu.Append(wx.ID_ABOUT) menu_bar.Append(app_menu, _('&Application')) menu_bar.Append(help_menu, _('&Help')) self.Bind(wx.EVT_MENU, self.on_exit, exit_item) else: # On macOS, put the menu items to the place they belong to: the application name menu apple_menu = menu_bar.OSXGetAppleMenu() about_item = apple_menu.Insert(0, wx.ID_ABOUT) self.Bind(wx.EVT_MENU, self.on_about, about_item) self.SetMenuBar(menu_bar) def on_exit(self, event): self.Close(True) def on_about(self, event): message = wx.MessageDialog(caption='Kosmorro version %s' % VERSION, message='© Jérôme Deuchnord - 2020\n\n' + _('This is a free software licensed under the ' 'GNU Affero General Public License.'), style=wx.OK | wx.HELP | wx.ICON_INFORMATION, parent=self) message.SetOKLabel(_('Close')) message.SetHelpLabel(_('Website')) btn = message.ShowModal() if btn == wx.ID_HELP: webbrowser.open('http://kosmorro.space') def compute(self, __=None): # Reset data self.moon_phase = None self.ephemerides = None self.events = None self.config_panel.disable_buttons() self.SetCursor(wx.Cursor(wx.CURSOR_WAIT)) self.SetStatusText(_('Computing…')) self._progress_bar.SetValue(0) self._progress_bar.Show() thread_moon_phase = MoonPhaseComputer(self, self.config_panel.compute_date) thread_ephemerides = EphemeridesComputer(self, self.config_panel.compute_date, self.config_panel.position, self.config_panel.timezone) thread_events = EventsComputer(self, self.config_panel.compute_date, self.config_panel.timezone) self.Bind(EVT_MOON_PHASE_COMPUTED, self.on_moon_phase_computed) self.Bind(EVT_EPHEMERIDES_COMPUTED, self.on_ephemerides_computed) self.Bind(EVT_EVENTS_COMPUTED, self.on_events_computed) thread_moon_phase.start() if self.config_panel.activate_position and self.config_panel.position is not None: thread_ephemerides.start() else: self.ephemerides = [] self._progress_bar.SetValue(50) thread_events.start() def export_button_clicked(self, __): self.export_path = self.get_export_file_path() if self.export_path is None: self.SetStatusText('Aborted.') return self.compute() def on_moon_phase_computed(self, event): self.moon_phase = event.value self._progress_bar.SetValue(self._progress_bar.GetValue() + 10) if self.is_all_computed(): self.process_results() def on_ephemerides_computed(self, event): self.ephemerides = event.value self._progress_bar.SetValue(self._progress_bar.GetValue() + 50) if self.is_all_computed(): self.process_results() def on_events_computed(self, event): self.events = event.value self._progress_bar.SetValue(self._progress_bar.GetValue() + 40) if self.is_all_computed(): self.process_results() def is_all_computed(self) -> bool: return self.moon_phase is not None and \ self.ephemerides is not None and \ self.events is not None def process_results(self): self.config_panel.enable_buttons() self.SetStatusText('') self._progress_bar.Hide() self.SetCursor(wx.Cursor(wx.CURSOR_ARROW)) if self.export_path is not None: self.export() else: self.show_results() def export(self, dumper_class = PdfDumper): ephemerides = self.ephemerides if len(self.ephemerides) > 0 else None dumper = dumper_class(date=self.config_panel.compute_date, ephemerides=ephemerides, events=self.events, moon_phase=self.moon_phase, timezone=self.config_panel.timezone, show_graph=True) try: with open(self.export_path, 'wb') as file: file.write(dumper.to_string()) self.SetStatusText(_('PDF export saved in "{path}"!').format(path=self.export_path)) except UnavailableFeatureError as error: wx.MessageDialog(self, error.msg, caption=_('Error while exporting your document'), style=wx.OK | wx.ICON_ERROR).ShowModal() finally: self.export_path = None def show_results(self): self.result_presenter.moon_phase = self.moon_phase self.result_presenter.ephemerides = self.ephemerides self.result_presenter.events = self.events self.result_presenter.render() self.result_presenter.Show() self.resize(center=True) def get_export_file_path(self) -> Union[None, str]: dialog = wx.FileDialog(parent=self, message=_('Please select an export file location'), wildcard='*.pdf', style=wx.FD_SAVE) if dialog.ShowModal() == wx.ID_OK: return dialog.GetPath() else: return None def start() -> bool: app = wx.App() app.SetAppName('Kosmorro') window = MainWindow() window.Show() return app.MainLoop() == 0