class AboutDialog(wx.Dialog): ## Constructor # # \param parent # The <b><i>wx.Window</i></b> parent instance # \param size # Window size <b><i>tuple</i></b> def __init__(self, parent, size=(600, 558)): wx.Dialog.__init__(self, parent, wx.ID_ABOUT, GT(u'About'), size=size, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) self.SetMinSize(wx.Size(400, 375)) self.CenterOnParent() # Create a tabbed interface tabs = wx.Notebook(self, -1) # Pages self.t_about = wx.Panel(tabs, -1) t_credits = wx.Panel(tabs, -1) t_changelog = wx.Panel(tabs, -1) t_license = wx.Panel(tabs, -1) # Add pages to tabbed interface tabs.AddPage(self.t_about, GT(u'About')) tabs.AddPage(t_credits, GT(u'Credits')) tabs.AddPage(t_changelog, GT(u'Changelog')) tabs.AddPage(t_license, GT(u'License')) # FIXME: Center verticall on about tab self.about_layout_V1 = BoxSizer(wx.VERTICAL) self.about_layout_V1.AddStretchSpacer() self.about_layout_V1.AddStretchSpacer() self.t_about.SetAutoLayout(True) self.t_about.SetSizer(self.about_layout_V1) self.t_about.Layout() ## List of credits self.credits = ListCtrl(t_credits) self.credits.SetSingleStyle(wx.LC_REPORT) self.credits.InsertColumn(0, GT(u'Name'), width=150) self.credits.InsertColumn(1, GT(u'Job'), width=200) self.credits.InsertColumn(2, GT(u'Email'), width=240) credits_sizer = BoxSizer(wx.VERTICAL) credits_sizer.Add(self.credits, 1, wx.EXPAND) t_credits.SetAutoLayout(True) t_credits.SetSizer(credits_sizer) t_credits.Layout() ## Changelog text area self.changelog = TextAreaPanel(t_changelog, style=wx.TE_READONLY) self.changelog.SetFont(MONOSPACED_MD) log_sizer = BoxSizer(wx.VERTICAL) log_sizer.Add(self.changelog, 1, wx.EXPAND) t_changelog.SetSizer(log_sizer) t_changelog.Layout() ## Licensing information text area self.license = TextAreaPanel(t_license, style=wx.TE_READONLY) self.license.SetFont(MONOSPACED_MD) license_sizer = BoxSizer(wx.VERTICAL) license_sizer.Add(self.license, 1, wx.EXPAND) t_license.SetSizer(license_sizer) t_license.Layout() # System info sys_info = wx.Panel(tabs, -1) tabs.AddPage(sys_info, GT(u'System Information')) ## System's <a href="https://www.python.org/">Python</a> version self.py_info = wx.StaticText( sys_info, -1, GT(u'Python version: {}').format(PY_VER_STRING)) ## System's <a href="https://wxpython.org/">wxPython</a> version self.wx_info = wx.StaticText( sys_info, -1, GT(u'wxPython version: {}').format(WX_VER_STRING)) ## Debreate's installation prefix install_prefix = wx.StaticText( sys_info, label=GT(u'App location: {}').format(PATH_app)) if INSTALLED: install_prefix.SetLabel( GT(u'Installation prefix: {}').format(PREFIX)) self.py_info.SetFont(sys_info_font) self.wx_info.SetFont(sys_info_font) sysinfo_layout_V1 = BoxSizer(wx.VERTICAL) sysinfo_layout_V1.AddStretchSpacer() sysinfo_layout_V1.Add(self.py_info, 0, wx.ALIGN_CENTER | wx.BOTTOM, 5) sysinfo_layout_V1.Add(self.wx_info, 0, wx.ALIGN_CENTER | wx.TOP, 5) sysinfo_layout_V1.AddSpacer(20) sysinfo_layout_V1.Add(install_prefix, 0, wx.ALIGN_CENTER | wx.TOP, 5) sysinfo_layout_V1.AddStretchSpacer() if OS_name: os_info = wx.StaticText(sys_info, label=OS_name) os_info.SetFont(sys_info_font) if OS_version: os_info.SetLabel(u'{} {}'.format(os_info.LabelText, OS_version)) if OS_codename: os_info.SetLabel(u'{} {}'.format(os_info.LabelText, OS_codename)) sysinfo_layout_V1.Insert(1, os_info, 0, wx.ALIGN_CENTER | wx.BOTTOM, 5) sysinfo_layout_V1.InsertSpacer(2, 20) if OS_upstream_name: os_upstream_info = wx.StaticText(sys_info, label=OS_upstream_name) if OS_upstream_version: os_upstream_info.SetLabel(u'{} {}'.format( os_upstream_info.LabelText, OS_upstream_version)) if OS_upstream_codename: os_upstream_info.SetLabel(u'{} {}'.format( os_upstream_info.LabelText, OS_upstream_codename)) sysinfo_layout_V1.Insert(2, os_upstream_info, 0, wx.ALIGN_CENTER | wx.BOTTOM, 5) sys_info.SetSizer(sysinfo_layout_V1) sys_info.Layout() # Button to close the dialog btn_confirm = CreateButton(self, btnid.CONFIRM) sizer = BoxSizer(wx.VERTICAL) sizer.Add(tabs, 1, wx.EXPAND) sizer.Add(btn_confirm, 0, wx.ALIGN_RIGHT | lyt.PAD_RTB, 5) self.SetSizer(sizer) self.Layout() ## Displays logo in 'about' tab # # \param graphic # Path to image file def SetGraphic(self, graphic): insertion_point = GetContainerItemCount(self.about_layout_V1) - 1 if not isinstance(graphic, wx.Bitmap): graphic = wx.Image(graphic) graphic.Rescale(64, 64, wx.IMAGE_QUALITY_HIGH) graphic = graphic.ConvertToBitmap() self.about_layout_V1.Insert( insertion_point, wx.StaticBitmap(self.t_about, wx.ID_ANY, graphic), 0, wx.ALL | wx.ALIGN_CENTER, 10) self.t_about.Layout() ## Displays version in 'about' tab # # \param version # <b><i>String</i></b> to display def SetVersion(self, version): insertion_point = GetContainerItemCount(self.about_layout_V1) - 1 app_label = wx.StaticText(self.t_about, label=u'{} {}'.format(APP_name, version)) app_label.SetFont(bigfont) self.about_layout_V1.Insert(insertion_point, app_label, 0, wx.ALL | wx.ALIGN_CENTER, 10) self.t_about.Layout() ## Display author's name # # \param author # <b><i>String</i></b> to display def SetAuthor(self, author): insertion_point = GetContainerItemCount(self.about_layout_V1) - 1 self.about_layout_V1.Insert(insertion_point, wx.StaticText(self.t_about, label=author), 0, wx.ALL | wx.ALIGN_CENTER, 10) self.t_about.Layout() ## Sets a hotlink to the app's homepage # # TODO: Remove: Deprecated, unused # # \param URL # URL to open when link is clicked def SetWebsite(self, URL): self.website.SetLabel(URL) self.website.SetURL(URL) ## Adds URL hotlinks to about dialog # # \param url_list # Label/URL <b><i>Tuple</i></b> (<i>string</i>, <i>string</i>) list def SetWebsites(self, url_list): insertion_point = GetContainerItemCount(self.about_layout_V1) - 1 link_layout = BoxSizer(wx.VERTICAL) for label, link in url_list: link_layout.Add( Hyperlink(self.t_about, wx.ID_ANY, label=label, url=link), 0, wx.ALIGN_CENTER, 10) self.about_layout_V1.Insert(insertion_point, link_layout, 0, wx.ALL | wx.ALIGN_CENTER, 10) self.t_about.Layout() ## Displays a description about the app on the 'about' tab def SetDescription(self, desc): # Place between spacers insertion_point = GetContainerItemCount(self.about_layout_V1) - 1 self.about_layout_V1.Insert(insertion_point, wx.StaticText(self.t_about, label=desc), 0, wx.ALL | wx.ALIGN_CENTER, 10) self.t_about.Layout() ## Adds a developer to the list of credits # # \param name # Developer's name # \param email # Developer's email address def AddDeveloper(self, name, email): next_item = self.credits.GetItemCount() self.credits.InsertStringItem(next_item, name) self.credits.SetStringItem(next_item, 2, email) self.credits.SetStringItem(next_item, 1, GT(u'Developer')) ## Adds a packager to the list of credits # # \param name # Packager's name # \param email # Packager's email address def AddPackager(self, name, email): next_item = self.credits.GetItemCount() self.credits.InsertStringItem(next_item, name) self.credits.SetStringItem(next_item, 2, email) self.credits.SetStringItem(next_item, 1, GT(u'Packager')) ## Adds a translator to the list of credits # # \param name # Translator's name # \param email # Translator's email address # \param lang # Locale code of the translation def AddTranslator(self, name, email, lang): job = GT(u'Translation') job = u'{} ({})'.format(job, lang) next_item = self.credits.GetItemCount() self.credits.InsertStringItem(next_item, name) self.credits.SetStringItem(next_item, 2, email) self.credits.SetStringItem(next_item, 1, job) ## Adds a general job to the credits list # # \param name # Contributer's name # \param job # Job description # \param email # str : Job holder's email address def AddJob(self, name, job, email=wx.EmptyString): next_item = self.credits.GetItemCount() self.credits.InsertStringItem(next_item, name) self.credits.SetStringItem(next_item, 1, job) if email != wx.EmptyString: self.credits.SetStringItem(next_item, 2, email) ## Adds list of jobs for single contributer # # \param name # <b><i>string</i></b>: # Contributer's name # \param jobs # <b><i>Tuple</i></b> (<i>string</i>, <i>string</i>): # List of contributer's jobs # \param email # <b><i>string</i></b>: # Optional contributer's email address def AddJobs(self, name, jobs, email=wx.EmptyString): if isinstance(jobs, str) or isinstance(jobs, unicode): Logger.Debug(__name__, GT(u'Converting string argument "jobs" to tuple')) jobs = (jobs, ) for x, value in enumerate(jobs): next_item = self.credits.GetItemCount() if x == 0: self.credits.InsertStringItem(next_item, name) if email != wx.EmptyString: self.credits.SetStringItem(next_item, 2, email) else: self.credits.InsertStringItem(next_item, wx.EmptyString) self.credits.SetStringItem(next_item, 1, value) ## FIXME: Unused? def NoResizeCol(self, event=None): if event: event.Veto() ## Sets text to be shown on the 'Changelog' tab # # FIXME: Change to create in class constructor def SetChangelog(self): ## Defines where the changelog is located # # By default it is located in the folder 'doc' # under the applications root directory. The # install script or Makefile should change this # to reflect installed path. if INSTALLED: # FIXME: Read compressed .gz changelog CHANGELOG = u'{}/share/doc/debreate/changelog.gz'.format(PREFIX) else: CHANGELOG = u'{}/docs/changelog'.format(PREFIX) if os.path.isfile(CHANGELOG): changelog_mimetype = GetFileMimeType(CHANGELOG) Logger.Debug( __name__, GT(u'Changelog mimetype: {}').format(changelog_mimetype)) # Set log text in case of read error log_text = GT(u'Error reading changelog: {}\n\t').format(CHANGELOG) log_text = u'{}{}'.format( log_text, GT(u'Cannot decode, unrecognized mimetype: {}').format( changelog_mimetype)) if changelog_mimetype == u'application/gzip': temp_dir = CreateStage() shutil.copy(CHANGELOG, temp_dir) CMD_gzip = GetExecutable(u'gzip') if CMD_gzip: prev_dir = os.getcwd() os.chdir(temp_dir) gzip_output = commands.getstatusoutput(u'{} -fd {}'.format( CMD_gzip, os.path.basename(CHANGELOG))) Logger.Debug( __name__, GT(u'gzip decompress; Code: {}, Output: {}').format( gzip_output[0], gzip_output[1])) os.chdir(prev_dir) changelog_file = os.path.basename(CHANGELOG).split(u'.')[0] changelog_file = u'{}/{}'.format(temp_dir, changelog_file) if os.path.isfile(changelog_file): log_text = ReadFile(changelog_file) RemoveStage(temp_dir) elif changelog_mimetype == u'text/plain': log_text = ReadFile(CHANGELOG) else: ShowErrorDialog(log_text, parent=self) else: log_text = GT( u'ERROR: Could not locate changelog file:\n\t\'{}\' not found'. format(CHANGELOG)) self.changelog.SetValue(log_text) self.changelog.SetInsertionPoint(0) ## Sets text to be shown on the 'License' tab def SetLicense(self): ## Defines where the LICENSE.txt is located # # By default it is located in the folder 'doc' # under the applications root directory. The # install script or Makefile should change this # to reflect installed path. if INSTALLED: license_path = u'{}/share/doc/debreate/copyright'.format(PREFIX) else: license_path = u'{}/docs/LICENSE.txt'.format(PREFIX) if os.path.isfile(license_path): lic_text = ReadFile(license_path) else: lic_text = GT( u'ERROR: Could not locate license file:\n\t\'{}\' not found'. format(license_path)) lic_text += u'\n\nCopyright © {} {} <{}>'.format( GetYear(), AUTHOR_name, AUTHOR_email) lic_text += u'\n\nhttps://opensource.org/licenses/MIT' self.license.SetValue(lic_text) self.license.SetInsertionPoint(0) ## Defines action to take when 'Ok' button is press # # Closes the dialog. # # \param event # <b><em>(wx.EVT_BUTTON)</em></b> def OnOk(self, event=None): self.Close()
class LogWindow(wx.Dialog): def __init__(self, parent, logFile): wx.Dialog.__init__(self, parent, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) self.SetIcon(APP_logo) self.LogFile = FileItem(logFile) self.SetTitle() self.LogPollThread = Thread(self.PollLogFile) self.DspLog = TextAreaPanel(self, style=wx.TE_READONLY) self.DspLog.font_size = 8 self.DspLog.SetFont(GetMonospacedFont(self.DspLog.font_size)) btn_open = CreateButton(self, btnid.BROWSE, GT(u'Open and Display Log File'), u'browse') btn_font = CreateButton(self, btnid.ZOOM, GT(u'Zoom Text'), u'zoom') btn_refresh = CreateButton(self, btnid.REFRESH, GT(u'Refresh'), u'refresh') btn_hide = CreateButton(self, btnid.CLOSE, GT(u'Hide'), u'hide') # *** Event Handling *** # EVT_REFRESH_LOG(self, wx.ID_ANY, self.OnLogTimestampChanged) wx.EVT_BUTTON(self, btnid.BROWSE, self.OnOpenLogFile) wx.EVT_BUTTON(self, btnid.ZOOM, self.OnChangeFont) wx.EVT_BUTTON(self, btnid.REFRESH, self.RefreshLog) wx.EVT_BUTTON(self, btnid.CLOSE, self.OnClose) wx.EVT_CLOSE(self, self.OnClose) wx.EVT_SHOW(self, self.OnShow) wx.EVT_SHOW(GetMainWindow(), self.OnShowMainWindow) # *** Layout *** # layout_btnF1 = wx.FlexGridSizer(cols=5) layout_btnF1.AddGrowableCol(1, 1) layout_btnF1.Add(btn_open, 0, wx.LEFT, 5) layout_btnF1.AddStretchSpacer(1) layout_btnF1.Add(btn_font, 0, wx.RIGHT, 5) layout_btnF1.Add(btn_refresh, 0, wx.RIGHT, 5) layout_btnF1.Add(btn_hide, 0, wx.RIGHT, 5) layout_mainV1 = BoxSizer(wx.VERTICAL) layout_mainV1.Add(self.DspLog, 1, wx.ALL | wx.EXPAND, 5) layout_mainV1.Add(layout_btnF1, 0, wx.EXPAND | wx.BOTTOM, 5) self.SetAutoLayout(True) self.SetSizer(layout_mainV1) self.Layout() self.SetMinSize(self.GetSize()) self.SetSize(wx.Size(600, 600)) self.AlignWithMainWindow() # Make sure log window is not shown at initialization self.Show(False) ## Positions the log window relative to the main window def AlignWithMainWindow(self): debreate_pos = GetMainWindow().GetPosition() width = self.GetSize()[0] posX = debreate_pos[0] - width posY = debreate_pos[1] self.SetPosition(wx.Point(posX, posY)) ## Hides the log window & clears contents def HideLog(self): self.Show(False) self.DspLog.Clear() ## Changes the font size def OnChangeFont(self, event=None): font_sizes = { 7: 8, 8: 10, 10: 7, } current_font_size = self.DspLog.font_size for S in font_sizes: if S == current_font_size: self.DspLog.SetFont(GetMonospacedFont(font_sizes[S])) self.DspLog.font_size = font_sizes[S] return Logger.Error(__name__, GT(u'Can\'t change log window font')) ## Hides the log window when close event occurs def OnClose(self, event=None): self.HideLog() ## Called by refresh event to update the log display def OnLogTimestampChanged(self, event=None): self.RefreshLog() ## Opens a new log file def OnOpenLogFile(self, event=None): log_select = GetFileOpenDialog(self, GT(u'Open Log'), directory=PATH_logs) if ShowDialog(log_select): logFile = log_select.GetPath() if os.path.isfile(logFile): self.SetLogFile(logFile) return ShowErrorDialog(u'{}: {}'.format(GT(u'File does not exist'), logFile), parent=self) ## Guarantees that menu item is synced with window's shown status def OnShow(self, event=None): menu_debug = GetMenu(menuid.DEBUG) # In case main window has been destroyed, but sub thread still active if GetMainWindow(): window_shown = self.IsShown() m_checked = menu_debug.IsChecked(menuid.LOG) if m_checked != window_shown: menu_debug.Check(menuid.LOG, window_shown) else: Logger.Warn(__name__, u'Log thread still active!') ## Use an event to show the log window # # By waiting until the main window emits a show event # a separate item is not added in the system window # list for the log. def OnShowMainWindow(self, event=None): main_window = GetMainWindow() # Make sure the main window has not been destroyed before showing log if main_window and main_window.IsShown(): if GetMenu(menuid.DEBUG).IsChecked(menuid.LOG): self.ShowLog() ## Toggles the log window shown or hidden def OnToggleWindow(self, event=None): show = GetMenu(menuid.DEBUG).IsChecked(menuid.LOG) if show: self.ShowLog() return self.HideLog() if event: event.Skip(True) ## Creates a thread that polls for changes in log file def PollLogFile(self, args=None): while self and self.IsShown(): if self.LogFile.TimestampChanged(): print(u'Log timestamp changed, loading new log ...') wx.PostEvent(self, RefreshLogEvent(0)) time.sleep(LOG_WINDOW_REFRESH_INTERVAL) ## Fills log with text file contents def RefreshLog(self, event=None): if self.LogFile.IsFile(): log_data = self.LogFile.Read() if not self.DspLog.IsEmpty(): self.DspLog.Clear() self.DspLog.SetValue(log_data) try: # Yield here to make sure last line is displayed # FIXME: Causes delay when debug enabled wx.SafeYield() self.DspLog.ShowPosition(self.DspLog.GetLastPosition()) except wx.PyDeadObjectError: tb_error = GS(traceback.format_exc()) Logger.Warn( __name__, u'Error refreshing log window. Details below:\n\n{}'. format(tb_error)) ## Changes the file to be loaded & displayed # # \param logFile # Absolute path of file to load def SetLogFile(self, logFile): self.LogFile = FileItem(logFile) self.RefreshLog() self.SetTitle() ## Updates the window's title using path of log file def SetTitle(self): new_title = self.LogFile.GetPath() if new_title: return wx.Dialog.SetTitle(self, new_title) ## Shows the log window def ShowLog(self): self.RefreshLog() self.Show(True) if not self.LogPollThread.IsActive(): Logger.Debug(__name__, u'Starting log polling thread ...') self.LogPollThread.Start() else: Logger.Debug(__name__, u'Log polling thread is already started')