def __init__(self, parent, log): wx.Frame.__init__(self, parent, title="BalloonTip wxPython Demo ;-)") self.statusbar = self.CreateStatusBar(2) self.statusbar.SetStatusWidths([-2, -1]) # statusbar fields statusbar_fields = [("Welcome To WxPython " + wx.VERSION_STRING), ("BalloonTip Demo")] for i in range(len(statusbar_fields)): self.statusbar.SetStatusText(statusbar_fields[i], i) self.SetIcon(images.Mondrian.GetIcon()) self.SetMenuBar(self.CreateMenuBar()) panel = wx.Panel(self, -1) mainsizer = wx.FlexGridSizer(3, 4, hgap=2, vgap=2) # Add A Button button = wx.Button(panel, -1, "Press Me!") # Add A TextCtrl textctrl = wx.TextCtrl(panel, -1, "I Am A TextCtrl") # Add A CheckBox checkbox = wx.CheckBox(panel, -1, "3-State Checkbox", style=wx.CHK_3STATE | wx.CHK_ALLOW_3RD_STATE_FOR_USER) samplelist = [ 'One', 'Two', 'Three', 'Four', 'Kick', 'The', 'Demo', 'Out', 'The', 'Door', ';-)' ] # Add A Choice choice = wx.Choice(panel, -1, choices=samplelist) # Add A Gauge gauge = wx.Gauge(panel, -1, 50, style=wx.GA_SMOOTH) # Add A ListBox listbox = wx.ListBox(panel, -1, choices=samplelist, style=wx.LB_SINGLE) # Add A TreeCtrl isz = (16, 16) treecontrol = wx.TreeCtrl(panel, -1) il = wx.ImageList(isz[0], isz[1]) fldridx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_FOLDER, wx.ART_OTHER, isz)) fldropenidx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_OTHER, isz)) fileidx = il.Add( wx.ArtProvider.GetBitmap(wx.ART_REPORT_VIEW, wx.ART_OTHER, isz)) treecontrol.SetImageList(il) self.il = il root = treecontrol.AddRoot("ROOT") treecontrol.SetItemData(root, None) treecontrol.SetItemImage(root, fldridx, wx.TreeItemIcon_Normal) treecontrol.SetItemImage(root, fldropenidx, wx.TreeItemIcon_Expanded) for ii in range(11): child = treecontrol.AppendItem(root, samplelist[ii]) treecontrol.SetItemData(child, None) treecontrol.SetItemImage(child, fldridx, wx.TreeItemIcon_Normal) treecontrol.SetItemImage(child, fldropenidx, wx.TreeItemIcon_Selected) # Add A Slider slider = wx.Slider(panel, -1, 25, 1, 100, style=wx.SL_HORIZONTAL | wx.SL_AUTOTICKS) # | wx.SL_LABELS) slider.SetTickFreq(5) # Add Another TextCtrl textctrl2 = wx.TextCtrl(panel, -1, "Another TextCtrl") # Add A GenStaticText statictext = StaticText(panel, -1, "Hello World!") statictext.SetFont( wx.Font(9, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False)) bmp = wx.ArtProvider.GetBitmap(wx.ART_INFORMATION, wx.ART_TOOLBAR, (16, 16)) # Add A GenBitmapButton bitmapbutton = BitmapButton(panel, -1, bmp) button2 = wx.Button(panel, -1, "Disable BalloonTip") tbicon = TaskBarIcon() tbicon.SetIcon(images.Mondrian.GetIcon()) controls = list(panel.GetChildren()) controls.append(tbicon) self.tbicon = tbicon # Add The Controls To The Main FlexGridSizer mainsizer.Add(button, 0, wx.EXPAND | wx.ALL, 10) mainsizer.Add(textctrl, 0, wx.EXPAND | wx.ALL, 10) mainsizer.Add(checkbox, 0, wx.EXPAND | wx.ALL, 10) mainsizer.Add(choice, 0, wx.EXPAND | wx.ALL, 10) mainsizer.Add(gauge, 0, wx.ALL, 10) mainsizer.Add(listbox, 0, wx.EXPAND | wx.ALL, 10) mainsizer.Add(treecontrol, 0, wx.EXPAND, wx.ALL, 10) mainsizer.Add(slider, 0, wx.ALL, 10) mainsizer.Add(textctrl2, 0, wx.ALL, 10) mainsizer.Add(statictext, 0, wx.EXPAND | wx.ALIGN_CENTER | wx.ALL, 10) mainsizer.Add(bitmapbutton, 0, wx.ALL, 10) mainsizer.Add(button2, 0, wx.ALL, 10) panel.SetSizer(mainsizer) mainsizer.Layout() # Declare The BalloonTip Background Colours bgcolours = [ None, wx.WHITE, wx.GREEN, wx.BLUE, wx.CYAN, wx.RED, None, None, wx.LIGHT_GREY, None, wx.WHITE, None, None ] # Declare The BalloonTip Top-Left Icons icons = [] for ii in range(4): bmp = wx.ArtProvider.GetBitmap(eval(ArtIDs[ii]), wx.ART_TOOLBAR, (16, 16)) icons.append(bmp) icons.extend([None] * 5) for ii in range(4, 9): bmp = wx.ArtProvider.GetBitmap(eval(ArtIDs[ii]), wx.ART_TOOLBAR, (16, 16)) icons.append(bmp) # Declare The BalloonTip Top Titles titles = [ "Button Help", "Texctrl Help", "CheckBox Help", "Choice Help", "Gauge Help", "", "", "Read Me Carefully!", "SpinCtrl Help", "StaticText Help", "BitmapButton Help", "Button Help", "Taskbar Help" ] fontone = wx.Font(9, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, True) fonttwo = wx.Font(14, wx.FONTFAMILY_SCRIPT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False) fontthree = wx.Font(9, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_NORMAL, False) fontfour = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, True) # Declare The BalloonTip Top Titles Fonts titlefonts = [ None, None, fontone, None, fonttwo, fontthree, None, None, None, fontfour, fontthree, None, None ] # Declare The BalloonTip Top Titles Colours titlecolours = [ None, None, wx.WHITE, wx.YELLOW, None, wx.WHITE, wx.BLUE, wx.RED, None, None, wx.LIGHT_GREY, None, None ] # Declare The BalloonTip Messages msg1 = "This Is The Default BalloonTip Window\nYou Can Customize It! "\ "Look At The Demo!" msg2 = "You Can Change The Background Colour\n Of The Balloon Window." msg3 = "You Can Also Change The Font And The\nColour For The Title." msg4 = "I Have Nothing Special To Suggest!\n\nWelcome To wxPython " + \ wx.VERSION_STRING + " !" msg5 = "What About If I Don't Want The Icon?\nNo Problem!" msg6 = "I Don't Have The Icon Nor The Title.\n\nDo You Love Me Anyway?" msg7 = "Some Comments On The Window Shape:\n\n- BT_ROUNDED: Creates A "\ "Rounded Rectangle;\n- BT_RECTANGLE: Creates A Rectangle.\n" msg8 = "Some Comments On The BalloonTip Style:\n\n"\ "BT_LEAVE: The BalloonTip Is Destroyed When\nThe Mouse Leaves"\ "The Target Widget;\n\nBT_CLICK: The BalloonTip Is Destroyed When\n"\ "You Click Any Region Of The BalloonTip;\n\nBT_BUTTON: The BalloonTip"\ " Is Destroyed When\nYou Click On The Top-Right Small Button." msg9 = "Some Comments On Delay Time:\n\nBy Default, The Delay Time After Which\n"\ "The BalloonTip Is Destroyed Is Very Long.\nYou Can Change It By Using"\ " The\nSetEndDelay() Method." msg10 = "I Have Nothing Special To Suggest!\n\nRead Me FAST, You Have Only 3 "\ "Seconds!" msg11 = "I Hope You Will Enjoy BalloonTip!\nIf This Is The Case, Please\n"\ "Post Some Comments On wxPython\nMailing List!" msg12 = "This Button Enable/Disable Globally\nThe BalloonTip On Your Application." msg13 = "This Is A BalloonTip For The\nTaskBar Icon Of Your Application.\n"\ "All The Styles For BalloonTip Work." messages = [ msg1, msg2, msg3, msg4, msg5, msg6, msg7, msg8, msg9, msg10, msg11, msg12, msg13 ] # Declare The BalloonTip Tip Messages Colours messagecolours = [ None, None, None, wx.WHITE, wx.BLUE, None, wx.BLUE, None, None, wx.RED, wx.GREEN, wx.BLUE, None ] fontone = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, True) fonttwo = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_NORMAL, False) fontthree = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, True) # Declare The BalloonTip Tip Messages Fonts messagefonts = [ None, None, None, fontone, None, None, fonttwo, None, fontthree, None, None, None, None ] # Declare The BalloonTip Frame Shapes windowshapes = [ BT.BT_ROUNDED, BT.BT_RECTANGLE, BT.BT_ROUNDED, BT.BT_RECTANGLE, BT.BT_ROUNDED, BT.BT_RECTANGLE, BT.BT_ROUNDED, BT.BT_ROUNDED, BT.BT_ROUNDED, BT.BT_RECTANGLE, BT.BT_ROUNDED, BT.BT_RECTANGLE, BT.BT_RECTANGLE ] # Declare The BalloonTip Destruction Style tipstyles = [ BT.BT_LEAVE, BT.BT_CLICK, BT.BT_BUTTON, BT.BT_LEAVE, BT.BT_CLICK, BT.BT_LEAVE, BT.BT_CLICK, BT.BT_BUTTON, BT.BT_BUTTON, BT.BT_CLICK, BT.BT_LEAVE, BT.BT_LEAVE, BT.BT_BUTTON ] # Set The Targets/Styles For The BalloonTip for ii, widget in enumerate(controls): tipballoon = BT.BalloonTip(topicon=icons[ii], toptitle=titles[ii], message=messages[ii], shape=windowshapes[ii], tipstyle=tipstyles[ii]) # Set The Target tipballoon.SetTarget(widget) # Set The Balloon Colour tipballoon.SetBalloonColour(bgcolours[ii]) # Set The Font For The Top Title tipballoon.SetTitleFont(titlefonts[ii]) # Set The Colour For The Top Title tipballoon.SetTitleColour(titlecolours[ii]) # Set The Font For The Tip Message tipballoon.SetMessageFont(messagefonts[ii]) # Set The Colour For The Tip Message tipballoon.SetMessageColour(messagecolours[ii]) # Set The Delay After Which The BalloonTip Is Created tipballoon.SetStartDelay(1000) if ii == 9: # Set The Delay After Which The BalloonTip Is Destroyed tipballoon.SetEndDelay(3000) # Store The Last BalloonTip Reference To Enable/Disable Globall The # BalloonTip. You Can Store Any Of Them, Not Necessarily The Last One. self.lasttip = tipballoon self.gauge = gauge self.count = 0 button2.Bind(wx.EVT_BUTTON, self.OnActivateBalloon) self.Bind(wx.EVT_IDLE, self.IdleHandler) frameSizer = wx.BoxSizer(wx.VERTICAL) frameSizer.Add(panel, 1, wx.EXPAND) self.SetSizer(frameSizer) frameSizer.Layout() self.Fit() self.CenterOnParent()
class GooeyApplication(wx.Frame): """ Main window for Gooey. """ def __init__(self, buildSpec, *args, **kwargs): super(GooeyApplication, self).__init__(None, *args, **kwargs) self._state = {} self.buildSpec = buildSpec self.applyConfiguration() self.menu = MenuBar(buildSpec) self.SetMenuBar(self.menu) self.header = FrameHeader(self, buildSpec) self.configs = self.buildConfigPanels(self) self.navbar = self.buildNavigation() self.footer = Footer(self, buildSpec) self.console = Console(self, buildSpec) self.layoutComponent() self.clientRunner = ProcessController( self.buildSpec.get('progress_regex'), self.buildSpec.get('progress_expr'), self.buildSpec.get('hide_progress_msg'), self.buildSpec.get('encoding'), self.buildSpec.get('requires_shell'), ) pub.subscribe(events.WINDOW_START, self.onStart) pub.subscribe(events.WINDOW_RESTART, self.onStart) pub.subscribe(events.WINDOW_STOP, self.onStopExecution) pub.subscribe(events.WINDOW_CLOSE, self.onClose) pub.subscribe(events.WINDOW_CANCEL, self.onCancel) pub.subscribe(events.WINDOW_EDIT, self.onEdit) pub.subscribe(events.CONSOLE_UPDATE, self.console.logOutput) pub.subscribe(events.EXECUTION_COMPLETE, self.onComplete) pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar) # Top level wx close event self.Bind(wx.EVT_CLOSE, self.onClose) if self.buildSpec['poll_external_updates']: self.fetchExternalUpdates() if self.buildSpec.get('auto_start', False): self.onStart() def applyConfiguration(self): self.SetTitle(self.buildSpec['program_name']) self.SetBackgroundColour(self.buildSpec.get('body_bg_color')) def onStart(self, *args, **kwarg): """ Verify user input and kick off the client's program if valid """ with transactUI(self): config = self.navbar.getActiveConfig() config.resetErrors() if config.isValid(): if self.buildSpec['clear_before_run']: self.console.clear() self.clientRunner.run(self.buildCliString()) self.showConsole() else: config.displayErrors() self.Layout() def onEdit(self): """Return the user to the settings screen for further editing""" with transactUI(self): if self.buildSpec['poll_external_updates']: self.fetchExternalUpdates() self.showSettings() def buildCliString(self): """ Collect all of the required information from the config screen and build a CLI string which can be used to invoke the client program """ config = self.navbar.getActiveConfig() group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()] positional = config.getPositionalArgs() optional = config.getOptionalArgs() return cli.buildCliString( self.buildSpec['target'], group['command'], positional, optional, suppress_gooey_flag=self.buildSpec['suppress_gooey_flag']) def onComplete(self, *args, **kwargs): """ Display the appropriate screen based on the success/fail of the host program """ with transactUI(self): if self.clientRunner.was_success(): if self.buildSpec.get('return_to_config', False): self.showSettings() else: self.showSuccess() if self.buildSpec.get('show_success_modal', True): wx.CallAfter(modals.showSuccess) else: if self.clientRunner.wasForcefullyStopped: self.showForceStopped() else: self.showError() if self.buildSpec.get('show_failure_modal'): wx.CallAfter(modals.showFailure) def onStopExecution(self): """Displays a scary message and then force-quits the executing client code if the user accepts""" if self.buildSpec['show_stop_warning'] and modals.confirmForceStop(): self.clientRunner.stop() def fetchExternalUpdates(self): """ !Experimental! Calls out to the client code requesting seed values to use in the UI !Experimental! """ seeds = seeder.fetchDynamicProperties(self.buildSpec['target'], self.buildSpec['encoding']) for config in self.configs: config.seedUI(seeds) def onCancel(self): """Close the program after confirming""" if modals.confirmExit(): self.onClose() def onClose(self, *args, **kwargs): """Cleanup the top level WxFrame and shutdown the process""" self.Destroy() sys.exit() def layoutComponent(self): sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.header, 0, wx.EXPAND) sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND) sizer.Add(self.navbar, 1, wx.EXPAND) sizer.Add(self.console, 1, wx.EXPAND) sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND) sizer.Add(self.footer, 0, wx.EXPAND) self.SetMinSize((400, 300)) self.SetSize(self.buildSpec['default_size']) self.SetSizer(sizer) self.console.Hide() self.Layout() # Program Icon (Windows) icon = wx.Icon(self.buildSpec['images']['programIcon'], wx.BITMAP_TYPE_PNG) self.SetIcon(icon) if sys.platform != 'win32': # OSX needs to have its taskbar icon explicitly set # bizarrely, wx requires the TaskBarIcon to be attached to the Frame # as instance data (self.). Otherwise, it will not render correctly. self.taskbarIcon = TaskBarIcon(iconType=wx.adv.TBI_DOCK) self.taskbarIcon.SetIcon(icon) def buildNavigation(self): """ Chooses the appropriate layout navigation component based on user prefs """ if self.buildSpec['navigation'] == constants.TABBED: navigation = Tabbar(self, self.buildSpec, self.configs) else: navigation = Sidebar(self, self.buildSpec, self.configs) if self.buildSpec['navigation'] == constants.HIDDEN: navigation.Hide() return navigation def buildConfigPanels(self, parent): page_class = TabbedConfigPage if self.buildSpec[ 'tabbed_groups'] else ConfigPage return [ page_class(parent, widgets, self.buildSpec) for widgets in self.buildSpec['widgets'].values() ] def showSettings(self): self.navbar.Show(True) self.console.Show(False) self.header.setImage('settings_img') self.header.setTitle(_("settings_title")) self.header.setSubtitle(self.buildSpec['program_description']) self.footer.showButtons('cancel_button', 'start_button') self.footer.progress_bar.Show(False) def showConsole(self): self.navbar.Show(False) self.console.Show(True) self.header.setImage('running_img') self.header.setTitle(_("running_title")) self.header.setSubtitle(_('running_msg')) self.footer.showButtons('stop_button') self.footer.progress_bar.Show(True) if not self.buildSpec['progress_regex']: self.footer.progress_bar.Pulse() def showComplete(self): self.navbar.Show(False) self.console.Show(True) buttons = (['edit_button', 'restart_button', 'close_button'] if self.buildSpec.get('show_restart_button', True) else ['edit_button', 'close_button']) self.footer.showButtons(*buttons) self.footer.progress_bar.Show(False) def showSuccess(self): self.showComplete() self.header.setImage('check_mark') self.header.setTitle(_('finished_title')) self.header.setSubtitle(_('finished_msg')) self.Layout() def showError(self): self.showComplete() self.header.setImage('error_symbol') self.header.setTitle(_('finished_title')) self.header.setSubtitle(_('finished_error')) def showForceStopped(self): self.showComplete() if self.buildSpec.get('force_stop_is_error', True): self.showError() else: self.showSuccess() self.header.setSubtitle(_('finished_forced_quit'))
class KlipFrame(wx.Frame): """ """ def __init__(self, model_): """ """ self.model = model_ self._book = None self._book_id = None self._search_target = None super(KlipFrame, self).__init__(None, size=wx.Size(1200, 760), title='Klip', name='Klip') # ensure the parent's __init__ is called icon = icons.klip.GetIcon() self.SetIcon(icon) self.tb = TaskBarIcon(wx.adv.TBI_DOCK) self.tb.SetIcon(icon) sp = wx.SplitterWindow(self, style=wx.SP_BORDER | wx.SP_3DBORDER) sp.SetSplitMode(wx.SPLIT_VERTICAL) sp.SetMinimumPaneSize(50) self.detailPanel = KlipDetailWindow(self) self.SetMinSize(wx.Size(800, 600)) # create a panel in the frame pnl_books = wx.Panel(sp) pnl_clips = wx.Panel(sp) pnl_clips.SetBackgroundColour(wx.Colour(255, 255, 255)) sp.SplitVertically(pnl_books, pnl_clips, int(round(self.GetSize().GetWidth() * 0.3))) sizer = wx.BoxSizer(wx.VERTICAL) self.search = wx.SearchCtrl(pnl_books, size=(200, -1), style=wx.TE_PROCESS_ENTER) self.search.ShowSearchButton(True) self.search.ShowCancelButton(True) self.Bind(wx.EVT_SEARCHCTRL_SEARCH_BTN, self.OnSearch, self.search) self.Bind(wx.EVT_SEARCHCTRL_CANCEL_BTN, self.OnCancelSearch, self.search) sizer.Add(self.search, 0, wx.EXPAND, 0) self.book_list = KlipListCtrl(pnl_books, wx.ID_ANY, style=wx.LC_REPORT | wx.LC_NO_HEADER | wx.LC_HRULES | wx.LC_SINGLE_SEL) font = self.book_list.GetFont() font.PointSize += 5 # font = font.Bold() self.book_list.SetFont(font) self.book_list.SetForegroundColour(wx.Colour(0x43, 0x43, 0x43)) self.book_list.ClearAll() self.book_list.SetBackgroundColour(wx.Colour(0xf6, 0xf6, 0xf6)) self.bl_width = self.book_list.GetSize().GetWidth() * 0.5 self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnBookSelected, self.book_list) self._total_books = wx.StaticText(pnl_books, label="BOOKS (%d)" % 0, pos=(25, 25)) sizer.Add(self._total_books, 0, wx.LEFT, 0) sizer.Add(self.book_list, 1, wx.EXPAND | wx.ALL, 0) pnl_books.SetSizer(sizer) # init right panel, show clippings. sizer = wx.BoxSizer(wx.VERTICAL) self.book_title = wx.StaticText(pnl_clips, label='') font = self.book_title.GetFont() font.PointSize += 10 font.Bold() self.book_title.SetFont(font) self.book_title.SetForegroundColour(wx.Colour(0x25, 0x91, 0xff)) sizer.Add(self.book_title, 0, wx.EXPAND, 10) self.book_info = wx.StaticText(pnl_clips, label='') font = self.book_info.GetFont() font.PointSize += 5 font.Bold() self.book_info.SetFont(font) # self.book_info.SetForegroundColour(wx.Colour(0x25, 0x91, 0xff)) sizer.Add(self.book_info, 0, wx.EXPAND, 10) self.clip_list = KlipListCtrl(pnl_clips, wx.ID_ANY, style=wx.LC_REPORT | wx.LC_NO_HEADER | wx.LC_HRULES | wx.LC_SINGLE_SEL) self.clip_list.InsertColumn(0, "Clip") # books = self.fillBooks() sizer.Add(self.clip_list, 1, wx.EXPAND | wx.ALL, 0) self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.showClipDetail, self.clip_list) self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnClipSelected, self.clip_list) pnl_clips.SetSizer(sizer) font = self.clip_list.GetFont() font.PointSize += 3 self.clip_list.SetFont(font) width = self.clip_list.GetSize().GetWidth() * 0.5 PDEBUG('Update Column Width: %d' % width) self.clip_list.SetColumnWidth(0, width) self.clip_list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClickClips) self.refreshContents() # create a menu bar self.makeMenuBar() def makeMenuBar(self): """ A menu bar is composed of menus, which are composed of menu items. This method builds a set of menus and binds handlers to be called when the menu item is selected. """ # Make a file menu with Hello and Exit items fileMenu = wx.Menu() # The "\t..." syntax defines an accelerator key that also triggers # the same event loadItem = fileMenu.Append(-1, "&Load...", "Load clippings...") cleanItem = fileMenu.Append(-1, "&Clean...", "Clean clippings...") fileMenu.AppendSeparator() # When using a stock ID we don't need to specify the menu item's # label exitItem = fileMenu.Append(wx.ID_EXIT) # Now a help menu for the about item helpMenu = wx.Menu() aboutItem = helpMenu.Append(wx.ID_ABOUT) # Make the menu bar and add the two menus to it. The '&' defines # that the next letter is the "mnemonic" for the menu item. On the # platforms that support it those letters are underlined and can be # triggered from the keyboard. menuBar = wx.MenuBar() menuBar.Append(fileMenu, "&File") menuBar.Append(helpMenu, "&Help") # Give the menu bar to the frame self.SetMenuBar(menuBar) # Finally, associate a handler function with the EVT_MENU event for # each of the menu items. That means that when that menu item is # activated then the associated handler function will be called. self.Bind(wx.EVT_MENU, self.OnLoadFile, loadItem) self.Bind(wx.EVT_MENU, self.cleanRecords, cleanItem) self.Bind(wx.EVT_MENU, self.OnExit, exitItem) self.Bind(wx.EVT_MENU, self.OnAbout, aboutItem) def OnExit(self, event): """Close the frame, terminating the application.""" self.Close(True) def cleanRecords(self, event): """Close the frame, terminating the application.""" total = self.model.cleanUpBooks() if total > 0: pass # Add dialog?? self.refreshContents() def refreshContents(self): """ """ self.fillBooks() total_books = self.book_list.GetItemCount() PDEBUG('BOOK_ID: %s, total: %s', self._book_id, total_books) if self._book_id is None: self.book_list.Select(0) elif self._book_id >= total_books: self.book_list.Select(total_books - 1) else: self.book_list.Select(self._book_id) pass def OnLoadFile(self, event): """Load clips from file..""" (books, clips) = self.model.loadFile(getClipPath()) if books > 0: self.fillBooks() if clips > 0 or books > 0: self.showClipsOfBook() wx.MessageBox("Loaded %d books with %d clips." % (books, clips)) def OnAbout(self, event): """Display an About Dialog""" wx.MessageBox("This is a wxPython Hello World sample", "About Hello World 2", wx.OK | wx.ICON_INFORMATION) def OnCancelSearch(self, evt): PDEBUG('ENTER') self.showClipsOfBook() def OnSearch(self, evt): PDEBUG('ENTER') target = self.search.GetValue() args = target.split() if not args: PDEBUG('empty search target...') return it = self.model.searchClips(args) self.showClips('Result for "%s"' % target, it) pass def fillBooks(self): """Fill book list. """ self.book_list.DeleteAllItems() self.book_list.InsertColumn(0, "Book") # 0 will insert at the start of the list iter = self.model.getBooks() idx = 0 while iter.next(): item = wx.ListItem() item.SetData(iter.id) item.SetId(idx) item.SetText(u" %s" % (iter.name)) self.book_list.InsertItem(item) idx += 1 self._total_books.SetLabel('BOOKS (%d)' % idx) self.book_list.SetColumnWidth(0, self.bl_width) return idx def OnBookSelected(self, event): """ """ self._book_id = event.GetItem().GetData() PDEBUG('BOOK_ID: %d', self._book_id) self.showClipsOfBook() self._clip = None pass def OnClipSelected(self, event): self._cur_item = event.GetItem() id = self._cur_item.GetData() txt = self._cur_item.GetText() PDEBUG('ID: %d -- %s', id, txt) self._clip = self.model.getClipById(id) PDEBUG('CLIP: %s', self._clip) pass def OnRightClickClips(self, evt): """ """ PDEBUG('enter: %s', evt) # only do this part the first time so the events are only bound once if not hasattr(self, "popupID1"): self.popupID1 = wx.NewIdRef() self.popupID2 = wx.NewIdRef() self.popupID3 = wx.NewIdRef() self.popupID5 = wx.NewIdRef() self.popupID6 = wx.NewIdRef() self.Bind(wx.EVT_MENU, self.OnNew, id=self.popupID1) self.Bind(wx.EVT_MENU, self.OnEdit, id=self.popupID2) self.Bind(wx.EVT_MENU, self.OnDelete, id=self.popupID3) # make a menu menu = wx.Menu() # add some items menu.Append(self.popupID1, "New Clip") menu.Append(self.popupID2, "Edit Selected") menu.Append(self.popupID3, "Delete Selected") # Popup the menu. If an item is selected then its handler # will be called before PopupMenu returns. self.PopupMenu(menu) menu.Destroy() pass def OnNew(self, evt): """ """ self.detailPanel.Show(True) self.detailPanel.OnNew(evt) pass def OnEdit(self, evt): """ """ PDEBUG('NOT IMPLEMENT.') self.detailPanel.Show(True) self.detailPanel.OnNew(evt) pass def OnDelete(self, evt): PDEBUG('NOT IMPLEMENT.') if self._clip is None: PDEBUG('oops') self.dropClip(self._clip) self._clip = None def showClipsOfBook(self): """Show clips of book. """ PDEBUG('BOOK_ID: %d', self._book_id) book_iter = self.model.getBookById(self._book_id) if not book_iter.next(): print('Failed to load book info, book_id: %d' % (self._book_id)) sys.exit(1) book = book_iter.name author = book_iter.author iter = self.model.getClipsByBookId(self._book_id) self.showClips(book, author, iter) pass def showClips(self, title, author, it): """Show contents in clip_li.s """ self.book_title.SetLabel(" %s -- %s" % (title, author)) self.clip_list.DeleteAllItems() idx = 0 while it.next(): item = wx.ListItem() item.SetData(it.id) item.SetId(idx) item.SetText(u" %s" % (it.content)) self.clip_list.InsertItem(item) idx += 1 self.book_info.SetLabel(' Total clips: %d' % idx) pass def showClipDetail(self, event): self._cur_item = event.GetItem() id = self._cur_item.GetData() clip = self.model.getClipById(id) self.detailPanel.UpdateContent(clip) # TODO: adjust position of detail window. # self.detailPanel.Position(wx.Point(0,0), wx.Size(0,0)) self.detailPanel.Show(True) pass def updateClip(self, clip, text): self.model.updateClip(clip, text) self.clip_list.SetItemText(self._cur_item.GetId(), u" %s" % text) pass def dropClip(self, clip): idx = self._cur_item.GetId() PDEBUG('DELETE IDX: %d', idx) self.model.dropClip(clip) self.clip_list.DeleteItem(idx) if idx < self.clip_list.GetItemCount(): self.clip_list.Select(idx) self.Refresh() self.clip_list.SetFocus() # If there are no clips left for current book, ask if we should remove # current book... if self.clip_list.GetItemCount() == 0: self.refreshContents() pass def newClip(self, content, typ, date): self.model.newClip(self._book, content, typ, date) self.showClipsOfBook() pass
class ImageGallery(wx.Frame): """ GUI Interface and functionality for Image Gallery. """ def __init__(self, properties=None, parent=None, id=ID_IMAGE_GALLERY, **kwargs): if properties is not None: global p p = properties global db db = dbconnect.DBConnect() wx.Frame.__init__(self, parent, id=id, title='CPA/ImageGallery - %s' % \ (os.path.basename(p._filename)), size=(800, 600), **kwargs) if parent is None and not sys.platform.startswith('win'): from wx.adv import TaskBarIcon self.tbicon = TaskBarIcon() self.tbicon.SetIcon(icons.get_cpa_icon(), 'CPA/ImageGallery') else: self.SetIcon(icons.get_cpa_icon()) self.SetName('ImageGallery') db.register_gui_parent(self) global dm dm = DataModel() if not p.is_initialized(): logging.critical('ImageGallery requires a properties file. Exiting.') raise Exception('ImageGallery requires a properties file. Exiting.') self.pmb = None self.worker = None self.trainingSet = None self.classBins = [] self.binsCreated = 0 self.chMap = p.image_channel_colors[:] self.toggleChMap = p.image_channel_colors[ :] # used to store previous color mappings when toggling colors on/off with ctrl+1,2,3... self.brightness = 1.0 self.scale = 1.0 self.contrast = 'Linear' self.defaultTSFileName = None self.defaultModelFileName = None self.lastScoringFilter = None self.menuBar = wx.MenuBar() self.SetMenuBar(self.menuBar) self.CreateMenus() self.CreateStatusBar() #### Create GUI elements # Top level - three split windows self.splitter = wx.SplitterWindow(self, style=wx.NO_BORDER | wx.SP_3DSASH) self.bins_splitter = wx.SplitterWindow(self.splitter, style=wx.NO_BORDER | wx.SP_3DSASH) # fetch & rules self.fetch_panel = wx.Panel(self.splitter) # sorting bins self.gallery_panel = wx.Panel(self.bins_splitter) o_label = p.object_name[0] if p.classification_type == 'image' else '' + ' image gallery' self.gallery_box = wx.StaticBox(self.gallery_panel, label=o_label) self.gallery_sizer = wx.StaticBoxSizer(self.gallery_box, wx.VERTICAL) self.galleryBin = sortbin.SortBin(parent=self.gallery_panel, classifier=self, label='image gallery', parentSizer=self.gallery_sizer) self.gallery_sizer.Add(self.galleryBin, proportion=1, flag=wx.EXPAND) self.gallery_panel.SetSizer(self.gallery_sizer) self.objects_bin_panel = wx.Panel(self.bins_splitter) # fetch objects interface self.startId = wx.TextCtrl(self.fetch_panel, id=-1, value='1', size=(60, -1), style=wx.TE_PROCESS_ENTER) self.endId = wx.TextCtrl(self.fetch_panel, id=-1, value='100', size=(60, -1), style=wx.TE_PROCESS_ENTER) self.fetchChoice = wx.Choice(self.fetch_panel, id=-1, choices=['range','all','individual']) self.fetchChoice.SetSelection(0) self.filterChoice = wx.Choice(self.fetch_panel, id=-1, choices=['experiment'] + p._filters_ordered + p._groups_ordered + [ CREATE_NEW_FILTER]) self.fetchFromGroupSizer = wx.BoxSizer(wx.HORIZONTAL) self.fetchBtn = wx.Button(self.fetch_panel, -1, 'Fetch!') #### Create Sizers self.fetchSizer = wx.BoxSizer(wx.HORIZONTAL) self.classified_bins_sizer = wx.BoxSizer(wx.HORIZONTAL) #### Add elements to sizers and splitters # fetch panel self.fetchSizer.AddStretchSpacer() self.fetchSizer.Add(wx.StaticText(self.fetch_panel, -1, 'Fetch '), flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(5, 20, 0) self.fetchSizer.Add(self.fetchChoice, flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(5, 20, 0) self.fetchTxt = wx.StaticText(self.fetch_panel, -1, label='of image IDs:') self.fetchSizer.Add(self.fetchTxt, flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(5, 20, 0) self.fetchSizer.Add(self.startId, flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(5, 20, 0) self.fetchTxt2 = wx.StaticText(self.fetch_panel, -1, label='to') self.fetchSizer.Add(self.fetchTxt2, flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(5, 20, 0) self.fetchSizer.Add(self.endId, flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(5, 20, 0) #self.fetchSizer.Add(self.obClassChoice, flag=wx.ALIGN_CENTER_VERTICAL) #self.fetchSizer.Add(5, 20, 0) self.fetchTxt3 = wx.StaticText(self.fetch_panel, -1, label='images') self.fetchSizer.Add(self.fetchTxt3, flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(5, 20, 0) self.fetchSizer.Add(wx.StaticText(self.fetch_panel, -1, 'from'), flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(5, 20, 0) self.fetchSizer.Add(self.filterChoice, flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(10, 20, 0) self.fetchSizer.Add(self.fetchFromGroupSizer, flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.Add(5, 20, 0) self.fetchSizer.Add(self.fetchBtn, flag=wx.ALIGN_CENTER_VERTICAL) self.fetchSizer.AddStretchSpacer() self.fetch_panel.SetSizerAndFit(self.fetchSizer) # classified bins panel self.objects_bin_panel.SetSizer(self.classified_bins_sizer) # splitter windows self.splitter.SplitHorizontally(self.fetch_panel, self.bins_splitter, self.fetch_panel.GetMinSize()[1]) self.bins_splitter.SplitHorizontally(self.gallery_panel, self.objects_bin_panel) self.splitter.SetSashGravity(0.0) self.bins_splitter.SetSashGravity(0.5) self.splitter.SetMinimumPaneSize(max(50, self.fetch_panel.GetMinHeight())) self.bins_splitter.SetMinimumPaneSize(50) self.SetMinSize((self.fetch_panel.GetMinWidth(), 4 * 50 + self.fetch_panel.GetMinHeight())) # Set initial state self.filterChoice.SetSelection(0) # JEN - Start Add # self.openDimensReduxBtn.Disable() # JEN - End Add self.fetchSizer.Hide(self.fetchFromGroupSizer) ##################### #### GUI Section #### ##################### # add the default classes #for class in range(1, num_classes+1): self.AddSortClass('objects of selected image') #self.AddSortClass('negative') self.Layout() self.Center() self.MapChannels(p.image_channel_colors[:]) self.BindMouseOverHelpText() #self.Bind(wx.EVT_BUTTON, self.OnInspect, self.inspectBtn) # JEN - Start Add # self.Bind(wx.EVT_BUTTON, self.OpenDimensRedux, self.openDimensReduxBtn) # JEN - End Add self.Bind(wx.EVT_BUTTON, self.OnFetch, self.fetchBtn) self.startId.Bind(wx.EVT_TEXT, self.ValidateIntegerField) self.startId.Bind(wx.EVT_TEXT_ENTER, self.OnFetch) self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(wx.EVT_CHAR, self.OnKey) # Doesn't work for windows tilecollection.EVT_TILE_UPDATED(self, self.OnTileUpdated) self.Bind(sortbin.EVT_QUANTITY_CHANGED, self.QuantityChanged) self.Bind(wx.EVT_CHOICE, self.OnSelectFetchChoice, self.fetchChoice) self.Bind(wx.EVT_CHOICE, self.OnSelectFilter, self.filterChoice) # JK - End Add def BindMouseOverHelpText(self): self.startId.SetToolTip(wx.ToolTip('The number of %s to fetch.' % (p.object_name[1]))) #self.obClassChoice.SetToolTip(wx.ToolTip('The phenotype of the %s.' % (p.object_name[1]))) #self.obClassChoice.GetToolTip().SetDelay(3000) self.filterChoice.SetToolTip(wx.ToolTip( 'Filters fetched %s to be from a subset of your images. (See groups and filters in the properties file)' % ( p.object_name[1]))) self.filterChoice.GetToolTip().SetDelay(3000) self.fetchBtn.SetToolTip(wx.ToolTip('Fetches images of %s to be sorted.' % (p.object_name[1]))) self.galleryBin.SetToolTip( wx.ToolTip('%s gallery of our dataset' % (p.object_name[1].capitalize()))) def OnKey(self, evt): ''' Keyboard shortcuts ''' keycode = evt.GetKeyCode() chIdx = keycode - 49 if evt.ControlDown() or evt.CmdDown(): # ctrl+N toggles channel #N on/off if len(self.chMap) > chIdx >= 0: self.ToggleChannel(chIdx) else: evt.Skip() else: evt.Skip() def ToggleChannel(self, chIdx): if self.chMap[chIdx] == 'None': for (idx, color, item, menu) in list(self.chMapById.values()): if idx == chIdx and color.lower() == self.toggleChMap[chIdx].lower(): item.Check() self.chMap[chIdx] = self.toggleChMap[chIdx] self.MapChannels(self.chMap) else: for (idx, color, item, menu) in list(self.chMapById.values()): if idx == chIdx and color.lower() == 'none': item.Check() self.chMap[chIdx] = 'None' self.MapChannels(self.chMap) def CreateMenus(self): ''' Create file menu and menu items ''' # View Menu viewMenu = wx.Menu() self.fileMenu = wx.Menu() loadMenuItem = self.fileMenu.Append(-1, item='Load Image Set\tCtrl+O', helpString='Loads images specfied in a csv file.') # JEN - End Add exitMenuItem = self.fileMenu.Append(id=wx.ID_EXIT, item='Exit\tCtrl+Q', helpString='Exit Image Gallery') self.GetMenuBar().Append(self.fileMenu, 'File') imageControlsMenuItem = viewMenu.Append(-1, item='Image Controls\tCtrl+Shift+I', helpString='Launches a control panel for adjusting image brightness, size, etc.') self.GetMenuBar().Append(viewMenu, 'View') # Rules menu # rulesMenu = wx.Menu() # rulesEditMenuItem = rulesMenu.Append(-1, text=u'Edit…', help='Lets you edit the rules') # self.GetMenuBar().Append(rulesMenu, 'Rules') # Channel Menus self.CreateChannelMenus() # Advanced menu advancedMenu = wx.Menu() fetchObjMenuItem = advancedMenu.Append(-1, item='Fetch all objects from displayed images', helpString='Fetch all objects from displayed images') fetchAllObjMenuItem = advancedMenu.Append(-1, item='Fetch all objects', helpString='Fetch all objects') saveImgMenuItem = advancedMenu.Append(-1, item='Save image thumbnails as PNG', helpString='Save image thumbnails as PNG') saveObjMenuItem = advancedMenu.Append(-1, item='Save object thumbnails as PNG', helpString='Save object thumbnails as PNG') self.GetMenuBar().Append(advancedMenu, 'Advanced') self.GetMenuBar().Append(cpa.helpmenu.make_help_menu(self, manual_url="https://cellprofiler.org/xiv-image-gallery"), 'Help') self.Bind(wx.EVT_MENU, self.OnShowImageControls, imageControlsMenuItem) self.Bind(wx.EVT_MENU, self.OnFetchObjThumbnails ,fetchObjMenuItem) self.Bind(wx.EVT_MENU, self.OnFetchAllObjThumbnails ,fetchAllObjMenuItem) self.Bind(wx.EVT_MENU, self.OnSaveImgThumbnails ,saveImgMenuItem) self.Bind(wx.EVT_MENU, self.OnSaveObjThumbnails ,saveObjMenuItem) self.Bind(wx.EVT_MENU, self.OnClose, exitMenuItem) self.Bind(wx.EVT_CLOSE, self.OnClose) self.Bind(wx.EVT_MENU, self.OnLoadImageSet, loadMenuItem) def CreateChannelMenus(self): ''' Create color-selection menus for each channel. ''' # Clean up existing channel menus try: menus = set([items[2].Menu for items in list(self.chMapById.values())]) for menu in menus: for i, mbmenu in enumerate(self.MenuBar.Menus): if mbmenu[0] == menu: self.MenuBar.Remove(i) for menu in menus: menu.Destroy() if 'imagesMenu' in self.__dict__: self.MenuBar.Remove(self.MenuBar.FindMenu('Images')) self.imagesMenu.Destroy() except: pass # Initialize variables self.imagesMenu = wx.Menu() chIndex = 0 self.chMapById = {} self.imMapById = {} channel_names = [] startIndex = 0 channelIds = [] for i, chans in enumerate(p.channels_per_image): chans = int(chans) # Construct channel names, for RGB images, append a # to the end of # each channel. name = p.image_names[i] if chans == 1: channel_names += [name] elif chans == 3: # RGB channel_names += ['%s [%s]' % (name, x) for x in 'RGB'] elif chans == 4: # RGBA channel_names += ['%s [%s]' % (name, x) for x in 'RGBA'] else: channel_names += ['%s [%s]' % (name, x + 1) for x in range(chans)] # Zip channel names with channel map zippedChNamesChMap = list(zip(channel_names, self.chMap)) # Loop over all the image names in the properties file for i, chans in enumerate(p.image_names): channelIds = [] # Loop over all the channels for j in range(0, int(p.channels_per_image[i])): (channel, setColor) = zippedChNamesChMap[chIndex] channel_menu = wx.Menu() for color in ['Red', 'Green', 'Blue', 'Cyan', 'Magenta', 'Yellow', 'Gray', 'None']: id = wx.NewId() # Create a radio item that maps an id and a color. item = channel_menu.AppendRadioItem(id, color) # Add a new chmapbyId object self.chMapById[id] = (chIndex, color, item, channel_menu) # If lowercase color matches what it was originally set to... if color.lower() == setColor.lower(): # Check off the item item.Check() # Bind self.Bind(wx.EVT_MENU, self.OnMapChannels, item) # Add appropriate Ids to imMapById if ((int(p.channels_per_image[i]) == 1 and color == 'Gray') or (int(p.channels_per_image[i]) > 1 and j == 0 and color == 'Red') or (int(p.channels_per_image[i]) > 1 and j == 2 and color == 'Blue') or (int(p.channels_per_image[i]) > 1 and j == 1 and color == 'Green')): channelIds = channelIds + [id] # Add new menu item self.GetMenuBar().Append(channel_menu, channel) chIndex += 1 # New id for the image as a whole id = wx.NewId() item = self.imagesMenu.AppendRadioItem(id, p.image_names[i]) # Effectively this code creates a data structure that stores relevant info with ID as a key self.imMapById[id] = (int(p.channels_per_image[i]), item, startIndex, channelIds) # Binds the event menu to OnFetchImage (below) and item self.Bind(wx.EVT_MENU, self.OnFetchImage, item) startIndex += int(p.channels_per_image[i]) # Add the "none" image and check it off. id = wx.NewId() item = self.imagesMenu.AppendRadioItem(id, 'None') self.Bind(wx.EVT_MENU, self.OnFetchImage, item) item.Check() # Add new "Images" menu bar item self.GetMenuBar().Append(self.imagesMenu, 'Images') ####################################### # OnFetchImage # # Allows user to display one image at a time. If image is single channel, # displays the image as gray. If image is multichannel, displays image as # RGB. # @param self, evt ####################################### def OnFetchImage(self, evt=None): # Set every channel to black and set all the toggle options to 'none' for ids in list(self.chMapById.keys()): (chIndex, color, item, channel_menu) = self.chMapById[ids] if (color.lower() == 'none'): item.Check() for ids in list(self.imMapById.keys()): (cpi, itm, si, channelIds) = self.imMapById[ids] if cpi == 3: self.chMap[si] = 'none' self.chMap[si + 1] = 'none' self.chMap[si + 2] = 'none' self.toggleChMap[si] = 'none' self.toggleChMap[si + 1] = 'none' self.toggleChMap[si + 2] = 'none' else: self.chMap[si] = 'none' self.toggleChMap[si] = 'none' # Determine what image was selected based on the event. Set channel to appropriate color(s) if evt.GetId() in self.imMapById: (chanPerIm, item, startIndex, channelIds) = self.imMapById[evt.GetId()] if chanPerIm == 1: # Set channel map and toggleChMap values. self.chMap[startIndex] = 'gray' self.toggleChMap[startIndex] = 'gray' # Toggle the option for the independent channel menu (chIndex, color, item, channel_menu) = self.chMapById[channelIds[0]] item.Check() else: RGB = ['red', 'green', 'blue'] + ['none'] * chanPerIm for i in range(chanPerIm): # Set chMap and toggleChMap values self.chMap[startIndex + i] = RGB[i] self.toggleChMap[startIndex + i] = RGB[i] # Toggle the option in the independent channel menus (chIndex, color, item, channel_menu) = self.chMapById[channelIds[i]] item.Check() self.MapChannels(self.chMap) ####################################### # /OnFetchImage ####################################### def OnLoadImageSet(self, evt): ''' Present user with file select dialog, then load selected training set. ''' dlg = wx.FileDialog(self, "Select the file containing your classifier training set.", defaultDir=os.getcwd(), wildcard='CSV files (*.csv)|*.csv', style=wx.FD_OPEN | wx.FD_CHANGE_DIR) if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetPath() name, file_extension = os.path.splitext(filename) if '.csv' == file_extension: self.LoadImageSet(filename) else: logging.error("Couldn't load the file! Make sure it is .csv") # Loads a CSV file with ImageNumber column and fetches all images with this number def LoadImageSet(self, filename): ''' Loads the selected file, parses out object keys, and fetches the tiles for CSV ''' # pause tile loading with tilecollection.load_lock(): self.PostMessage('Loading image set from %s' % filename) import pandas as pd df = pd.read_csv(filename) def cb(): keys = [tuple([k,-1]) for k in df['ImageNumber']] self.galleryBin.AddObjects(keys, self.chMap, pos='last', display_whole_image=True) self.PostMessage('Image set loaded') if df.shape[0] > 100: dlg = wx.MessageDialog(self, 'The whole collection consists of %s images. Downloading could be slow. Do you still want to continue?' % ( df.shape[0]), 'Load whole image set?', wx.YES_NO | wx.ICON_QUESTION) response = dlg.ShowModal() # Call fetch filter with all keys if response == wx.ID_YES: self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() # Need to run this after removing all tiles! wx.CallAfter(cb) else: self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() # Need to run this after removing all tiles! wx.CallAfter(cb) def OnFetch(self, evt): start = int(self.startId.Value) end = int(self.endId.Value) fltr_sel = self.filterChoice.GetStringSelection() fetch_sel = self.fetchChoice.GetStringSelection() statusMsg = 'Fetched images %d - %d ' % (start, end) # Need to flatten it due to the fact that img key can look like this: # (image_id,) or this (table_id, image_id) def flatten(*args): for x in args: if hasattr(x, '__iter__'): for y in flatten(*x): yield y else: yield x # Fetching all images with filter if fetch_sel == 'all': # Easy just fetch all images if fltr_sel == 'experiment': self.FetchAll() return # Fetch all images with self defined filter elif fltr_sel in p._filters_ordered: imKeys = db.GetFilteredImages(fltr_sel) if imKeys == []: self.PostMessage('No images were found in filter "%s"' % (fltr_sel)) return # Are you sure? if len(imKeys) > 100: dlg = wx.MessageDialog(self, 'The whole collection consists of %s images. Downloading could be slow. Do you still want to continue?' % ( len(imKeys)), 'Load whole image set?', wx.YES_NO | wx.ICON_QUESTION) response = dlg.ShowModal() # Call fetch filter with all keys if response == wx.ID_YES: self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() # Need to run this after removing all tiles! def cb(): filteredImKeys = db.GetFilteredImages(fltr_sel) imKeys = [tuple(list(flatten(x,-1))) for x in filteredImKeys] self.galleryBin.AddObjects(imKeys, self.chMap, pos='last', display_whole_image=True) wx.CallAfter(cb) statusMsg += ' from filter "%s"' % (fltr_sel) # data set is small, lets go for it! else: self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() # Need to run this after removing all tiles! def cb(): filteredImKeys = db.GetFilteredImages(fltr_sel) imKeys = [tuple(list(flatten(x,-1))) for x in filteredImKeys] self.galleryBin.AddObjects(imKeys, self.chMap, pos='last', display_whole_image=True) wx.CallAfter(cb) statusMsg += ' from filter "%s"' % (fltr_sel) # fetching all images for predefined filter elif fltr_sel in p._groups_ordered: groupName = fltr_sel groupKey = self.GetGroupKeyFromGroupSizer(groupName) imKeys = dm.GetImagesInGroupWithWildcards(groupName, groupKey) colNames = dm.GetGroupColumnNames(groupName) if imKeys == []: self.PostMessage('No images were found in group %s: %s' % (groupName, ', '.join(['%s=%s' % (n, v) for n, v in zip(colNames, groupKey)]))) return # Are you sure? if len(imKeys) > 100: dlg = wx.MessageDialog(self, 'The whole collection consists of %s images. Downloading could be slow. Do you still want to continue?' % ( len(imKeys)), 'Load whole image set?', wx.YES_NO | wx.ICON_QUESTION) response = dlg.ShowModal() # Yes, I am sure! if response == wx.ID_YES: self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() groupName = fltr_sel groupKey = self.GetGroupKeyFromGroupSizer(groupName) filteredImKeys = dm.GetImagesInGroupWithWildcards(groupName, groupKey) colNames = dm.GetGroupColumnNames(groupName) def cb(): imKeys = [tuple(list(flatten(x,-1))) for x in filteredImKeys] self.galleryBin.AddObjects(imKeys, self.chMap, pos='last', display_whole_image=True) statusMsg += ' from group %s: %s' % (groupName, ', '.join(['%s=%s' % (n, v) for n, v in zip(colNames, groupKey)])) wx.CallAfter(cb) # dataset is small, lets go for it! else: self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() groupName = fltr_sel groupKey = self.GetGroupKeyFromGroupSizer(groupName) filteredImKeys = dm.GetImagesInGroupWithWildcards(groupName, groupKey) colNames = dm.GetGroupColumnNames(groupName) def cb(): imKeys = [tuple(list(flatten(x,-1))) for x in filteredImKeys] self.galleryBin.AddObjects(imKeys, self.chMap, pos='last', display_whole_image=True) statusMsg += ' from group %s: %s' % (groupName, ', '.join(['%s=%s' % (n, v) for n, v in zip(colNames, groupKey)])) wx.CallAfter(cb) # Fetching individual images elif fetch_sel == 'individual': if p.table_id: imgKey = [(start,end,-1)] else: imgKey = [(end,-1)] self.galleryBin.AddObjects(imgKey, self.chMap, pos='last', display_whole_image=True) return # Fetching images with range elif fltr_sel == 'experiment': self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() # Need to run this after removing all tiles! def cb(): imKeys = db.GetAllImageKeys() imKeys = [tuple(list(flatten(x,-1))) for x in imKeys] self.galleryBin.AddObjects(imKeys[(start - 1):end], self.chMap, pos='last', display_whole_image=True) wx.CallAfter(cb) statusMsg += ' from whole experiment' elif fltr_sel in p._filters_ordered: self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() # Need to run this after removing all tiles! def cb(): filteredImKeys = db.GetFilteredImages(fltr_sel) if filteredImKeys == []: self.PostMessage('No images were found in filter "%s"' % (fltr_sel)) return imKeys = [tuple(list(flatten(x,-1))) for x in filteredImKeys] self.galleryBin.AddObjects(imKeys[(start - 1):end], self.chMap, pos='last', display_whole_image=True) wx.CallAfter(cb) statusMsg += ' from filter "%s"' % (fltr_sel) elif fltr_sel in p._groups_ordered: # if the filter name is a group then it's actually a group self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() groupName = fltr_sel groupKey = self.GetGroupKeyFromGroupSizer(groupName) filteredImKeys = dm.GetImagesInGroupWithWildcards(groupName, groupKey) colNames = dm.GetGroupColumnNames(groupName) def cb(): if filteredImKeys == []: self.PostMessage('No images were found in group %s: %s' % (groupName, ', '.join(['%s=%s' % (n, v) for n, v in zip(colNames, groupKey)]))) return imKeys = [tuple(list(flatten(x,-1))) for x in filteredImKeys] self.galleryBin.AddObjects(imKeys[(start - 1):end], self.chMap, pos='last', display_whole_image=True) statusMsg += ' from group %s: %s' % (groupName, ', '.join(['%s=%s' % (n, v) for n, v in zip(colNames, groupKey)])) wx.CallAfter(cb) self.PostMessage(statusMsg) def FetchAll(self): def flatten(*args): for x in args: if hasattr(x, '__iter__'): for y in flatten(*x): yield y else: yield x imKeys = db.GetAllImageKeys() # A lot of images if len(imKeys) > 200: # double check dlg = wx.MessageDialog(self, 'The whole collection consists of %s images. Downloading could be slow. Do you still want to continue?' % ( len(imKeys)), 'Load whole image set?', wx.YES_NO | wx.ICON_QUESTION) response = dlg.ShowModal() if response == wx.ID_YES: self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() # Need to run this after removing all tiles! def cb(): imKeys = db.GetAllImageKeys() imKeys = [tuple(list(flatten(x,-1))) for x in imKeys] self.galleryBin.AddObjects(imKeys, self.chMap, pos='last', display_whole_image=True) self.PostMessage("Loaded all images") wx.CallAfter(cb) else: self.galleryBin.SelectAll() self.galleryBin.RemoveSelectedTiles() # Need to run this after removing all tiles! def cb(): imKeys = db.GetAllImageKeys() imKeys = [tuple(list(flatten(x,-1))) for x in imKeys] self.galleryBin.AddObjects(imKeys, self.chMap, pos='last', display_whole_image=True) self.PostMessage("Loaded all images") wx.CallAfter(cb) def AddSortClass(self, label): ''' Create a new SortBin in a new StaticBoxSizer with the given label. This sizer is then added to the classified_bins_sizer. ''' bin = sortbin.SortBin(parent=self.objects_bin_panel, label=label, classifier=self) box = wx.StaticBox(self.objects_bin_panel, label=label) # NOTE: bin must be created after sizer or drop events will occur on the sizer sizer = wx.StaticBoxSizer(box, wx.VERTICAL) bin.parentSizer = sizer sizer.Add(bin, proportion=1, flag=wx.EXPAND) self.classified_bins_sizer.Add(sizer, proportion=1, flag=wx.EXPAND) self.classBins.append(bin) self.objects_bin_panel.Layout() self.binsCreated += 1 self.QuantityChanged() # IMPORTANT: required for drag and drop to work on Linux # see: http://trac.wxwidgets.org/ticket/2763 box.Lower() def RemoveSortClass(self, label, clearModel=True): pass def all_sort_bins(self): return [self.galleryBin] + self.classBins def QuantityChanged(self, evt=None): pass def OnTileUpdated(self, evt): ''' When the tile loader returns the tile image update the tile. ''' self.galleryBin.UpdateTile(evt.data) for bin in self.classBins: bin.UpdateTile(evt.data) def OnMapChannels(self, evt): ''' Responds to selection from the color mapping menus. ''' (chIdx, color, item, menu) = self.chMapById[evt.GetId()] item.Check() self.chMap[chIdx] = color.lower() if color.lower() != 'none': self.toggleChMap[chIdx] = color.lower() self.MapChannels(self.chMap) def MapChannels(self, chMap): ''' Tell all bins to apply a new channel-color mapping to their tiles. ''' # TODO: Need to update color menu selections self.chMap = chMap for bin in self.all_sort_bins(): bin.MapChannels(chMap) def ValidateImageKey(self, evt): # Unused? Mar 2021 ''' Checks that the image field specifies an existing image. ''' txtCtrl = evt.GetEventObject() try: if p.table_id: imKey = (int(self.tableTxt.Value), int(self.imageTxt.Value)) else: imKey = (int(self.imageTxt.Value),) if dm.GetObjectCountFromImage(imKey) > 0: txtCtrl.SetForegroundColour('#000001') self.SetStatusText('Image contains %s %s.' % (dm.GetObjectCountFromImage(imKey), p.object_name[1])) else: txtCtrl.SetForegroundColour('#888888') # Set field to GRAY if image contains no objects self.SetStatusText('Image contains zero %s.' % (p.object_name[1])) except(Exception): txtCtrl.SetForegroundColour('#FF0000') # Set field to red if image doesn't exist self.SetStatusText('No such image.') def OnSelectFetchChoice(self, evt): ''' Handler for fetch filter selection. ''' fetchChoice = self.fetchChoice.GetStringSelection() # Select from a specific image if fetchChoice == 'range': self.fetchTxt.SetLabel('of image IDs:') self.fetchTxt2.SetLabel('to') self.fetchTxt2.Show() self.fetchTxt3.SetLabel('images') self.fetchTxt3.Show() self.startId.Show() self.endId.Show() self.filterChoice.Enable() self.fetch_panel.Layout() elif fetchChoice == 'all': self.fetchTxt.SetLabel('') self.fetchTxt2.Hide() self.fetchTxt3.SetLabel('images') self.fetchTxt3.Show() self.startId.Hide() self.endId.Hide() self.filterChoice.Enable() self.fetch_panel.Layout() elif fetchChoice == 'individual': self.fetchTxt.SetLabel('image ID:') if p.table_id: self.startId.Show() else: self.startId.Hide() self.endId.Show() self.fetchTxt2.Hide() self.fetchTxt3.Hide() self.filterChoice.Disable() self.fetch_panel.Layout() def OnSelectFilter(self, evt): ''' Handler for fetch filter selection. ''' filter = self.filterChoice.GetStringSelection() # Select from a specific image if filter == 'experiment' or filter in p._filters_ordered: self.fetchSizer.Hide(self.fetchFromGroupSizer, True) elif filter == 'image' or filter in p._groups_ordered: self.SetupFetchFromGroupSizer(filter) self.fetchSizer.Show(self.fetchFromGroupSizer, True) elif filter == CREATE_NEW_FILTER: self.fetchSizer.Hide(self.fetchFromGroupSizer, True) from .columnfilter import ColumnFilterDialog cff = ColumnFilterDialog(self, tables=[p.image_table], size=(600, 300)) if cff.ShowModal() == wx.OK: fltr = cff.get_filter() fname = cff.get_filter_name() p._filters[fname] = fltr items = self.filterChoice.GetItems() self.filterChoice.SetItems(items[:-1] + [fname] + items[-1:]) self.filterChoice.Select(len(items) - 1) else: self.filterChoice.Select(0) cff.Destroy() self.fetch_panel.Layout() self.fetch_panel.Refresh() def SetupFetchFromGroupSizer(self, group): ''' This sizer displays input fields for inputting each element of a particular group's key. A group with 2 columns: Gene, and Well, would be represented by two combo boxes. ''' if group == 'image': fieldNames = ['table', 'image'] if p.table_id else ['image'] fieldTypes = [int, int] validKeys = dm.GetAllImageKeys() else: fieldNames = dm.GetGroupColumnNames(group) fieldTypes = dm.GetGroupColumnTypes(group) validKeys = dm.GetGroupKeysInGroup(group) self.groupInputs = [] self.groupFieldValidators = [] self.fetchFromGroupSizer.Clear(True) for i, field in enumerate(fieldNames): label = wx.StaticText(self.fetch_panel, wx.NewId(), field + ':') # Values to be sorted BEFORE being converted to str validVals = list(set([col[i] for col in validKeys])) validVals.sort() validVals = [str(col) for col in validVals] if group == 'image' or fieldTypes[i] == int or fieldTypes[i] == int: fieldInp = wx.TextCtrl(self.fetch_panel, -1, value=validVals[0], size=(80, -1)) else: fieldInp = wx.Choice(self.fetch_panel, -1, size=(80, -1), choices=['__ANY__'] + validVals) validVals = ['__ANY__'] + validVals fieldInp.SetSelection(0) # Create and bind to a text Validator def ValidateGroupField(evt, validVals=validVals): ctrl = evt.GetEventObject() if ctrl.GetValue() in validVals: ctrl.SetForegroundColour('#000001') else: ctrl.SetForegroundColour('#FF0000') self.groupFieldValidators += [ValidateGroupField] fieldInp.Bind(wx.EVT_TEXT, self.groupFieldValidators[-1]) self.groupInputs += [fieldInp] self.fetchFromGroupSizer.Add(label) self.fetchFromGroupSizer.Add(fieldInp) self.fetchFromGroupSizer.Add(10, 20, 0) def ValidateIntegerField(self, evt): ''' Validates an integer-only TextCtrl ''' txtCtrl = evt.GetEventObject() # NOTE: textCtrl.SetBackgroundColor doesn't work on Mac # and foreground color only works when not setting to black. try: int(txtCtrl.GetValue()) txtCtrl.SetForegroundColour('#000001') except(Exception): txtCtrl.SetForegroundColour('#FF0000') def GetGroupKeyFromGroupSizer(self, group=None): ''' Returns the text in the group text inputs as a group key. ''' if group is not None: fieldTypes = dm.GetGroupColumnTypes(group) else: fieldTypes = [int for input in self.groupInputs] groupKey = [] for input, ftype in zip(self.groupInputs, fieldTypes): # GetValue returns unicode from ComboBox, but we need a string val = str(input.GetStringSelection()) # if the value is blank, don't bother typing it, it is a wildcard if val != '__ANY__': val = ftype(val) groupKey += [val] return tuple(groupKey) def OnShowImageControls(self, evt): ''' Shows the image adjustment control panel in a new frame. ''' self.imageControlFrame = wx.Frame(self, size=(470, 155)) ImageControlPanel(self.imageControlFrame, self, brightness=self.brightness, scale=self.scale, contrast=self.contrast) self.imageControlFrame.Show(True) # Saving image thumbnails def OnSaveImgThumbnails(self, evt): saveDialog = wx.DirDialog(self, "Choose input directory", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR) if saveDialog.ShowModal() == wx.ID_OK: directory = saveDialog.GetPath() if not os.path.exists(directory + '/image_gallery'): os.makedirs(directory + '/image_gallery') for tile in self.galleryBin.tiles: imagetools.SaveBitmap(tile.bitmap, directory + '/image_gallery/' + str(tile.obKey) + '.png') # Fetch all object thumbnails from displayed images def OnFetchObjThumbnails(self, evt): self.classBins[0].SelectAll() self.classBins[0].RemoveSelectedTiles() # Need to run this after removing all tiles! def cb(): for tile in self.galleryBin.tiles: pseudo_obKeys = tile.obKey imKey = pseudo_obKeys[:-1] # Get image key obKeys = db.GetObjectsFromImage(imKey) self.classBins[0].AddObjects(obKeys, self.chMap, pos='last', display_whole_image=False) wx.CallAfter(cb) # Fetch all object thumbnails from displayed images def OnFetchAllObjThumbnails(self, evt): self.classBins[0].SelectAll() self.classBins[0].RemoveSelectedTiles() # Need to run this after removing all tiles! def flatten(*args): for x in args: if hasattr(x, '__iter__'): for y in flatten(*x): yield y else: yield x def cb(): imKeys = db.GetAllImageKeys() imKeys = [tuple(list(flatten(x,-1))) for x in imKeys] for imKey in imKeys: pseudo_obKeys = imKey imKey = pseudo_obKeys[:-1] # Get image key obKeys = db.GetObjectsFromImage(imKey) self.classBins[0].AddObjects(obKeys, self.chMap, pos='last', display_whole_image=False) wx.CallAfter(cb) # Saving image thumbnails def OnSaveObjThumbnails(self, evt): saveDialog = wx.DirDialog(self, "Choose input directory", style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT | wx.FD_CHANGE_DIR) if saveDialog.ShowModal() == wx.ID_OK: directory = saveDialog.GetPath() if not os.path.exists(directory + '/object_gallery'): os.makedirs(directory + '/object_gallery') for tile in self.classBins[0].tiles: imagetools.SaveBitmap(tile.bitmap, directory + '/object_gallery/' + str(tile.obKey) + '.png') def SetBrightness(self, brightness): ''' Updates the global image brightness across all tiles. ''' self.brightness = brightness [t.SetBrightness(brightness) for bin in self.all_sort_bins() for t in bin.tiles] def SetScale(self, scale): ''' Updates the global image scaling across all tiles. ''' self.scale = scale [t.SetScale(scale) for bin in self.all_sort_bins() for t in bin.tiles] [bin.UpdateSizer() for bin in self.all_sort_bins()] def SetContrastMode(self, mode): self.contrast = mode [t.SetContrastMode(mode) for bin in self.all_sort_bins() for t in bin.tiles] def PostMessage(self, message): ''' Updates the status bar text and logs to info. ''' self.SetStatusText(message) logging.info(message) def OnClose(self, evt): ''' Prompt to save training set before closing. ''' self.Destroy() def Destroy(self): ''' Kill off all threads before combusting. ''' super(ImageGallery, self).Destroy() import threading t = tilecollection.TileCollection() if self in t.loader.notify_window: t.loader.notify_window.remove(self) # If no other windows are attached to the loader we shut it down and delete the tilecollection. if len(t.loader.notify_window) == 0: for thread in threading.enumerate(): if thread != threading.currentThread() and thread.getName().lower().startswith('tileloader'): logging.debug('Aborting thread %s' % thread.getName()) try: thread.abort() except: pass tilecollection.TileCollection.forget()
class GooeyApplication(wx.Frame): """ Main window for Gooey. """ def __init__(self, buildSpec, *args, **kwargs): super(GooeyApplication, self).__init__(None, *args, **kwargs) self._state = {} self.buildSpec = buildSpec self.applyConfiguration() self.menu = MenuBar(buildSpec) self.SetMenuBar(self.menu) self.header = FrameHeader(self, buildSpec) self.configs = self.buildConfigPanels(self) self.navbar = self.buildNavigation() self.footer = Footer(self, buildSpec) self.console = Console(self, buildSpec) self.props = { 'background_color': self.buildSpec['header_bg_color'], 'title': self.buildSpec['program_name'], 'subtitle': self.buildSpec['program_description'], 'height': self.buildSpec['header_height'], 'image_uri': self.buildSpec['images']['configIcon'], 'image_size': (six.MAXSIZE, self.buildSpec['header_height'] - 10) } state = form_page(initial_state(self.buildSpec)) self.fprops = { 'buttons': state['buttons'], 'progress': state['progress'], 'timing': state['timing'], 'bg_color': self.buildSpec['footer_bg_color'] } # self.hhh = render(create_element(RHeader, self.props), self) # self.fff = render(create_element(RFooter, self.fprops), self) # patch(self.hhh, create_element(RHeader, {**self.props, 'image_uri': self.buildSpec['images']['runningIcon']})) self.layoutComponent() self.timer = Timing(self) self.clientRunner = ProcessController( self.buildSpec.get('progress_regex'), self.buildSpec.get('progress_expr'), self.buildSpec.get('hide_progress_msg'), self.buildSpec.get('encoding'), self.buildSpec.get('requires_shell'), self.buildSpec.get('shutdown_signal', signal.SIGTERM)) pub.subscribe(events.WINDOW_START, self.onStart) pub.subscribe(events.WINDOW_RESTART, self.onStart) pub.subscribe(events.WINDOW_STOP, self.onStopExecution) pub.subscribe(events.WINDOW_CLOSE, self.onClose) pub.subscribe(events.WINDOW_CANCEL, self.onCancel) pub.subscribe(events.WINDOW_EDIT, self.onEdit) pub.subscribe(events.CONSOLE_UPDATE, self.console.logOutput) pub.subscribe(events.EXECUTION_COMPLETE, self.onComplete) pub.subscribe(events.PROGRESS_UPDATE, self.footer.updateProgressBar) pub.subscribe(events.TIME_UPDATE, self.footer.updateTimeRemaining) # Top level wx close event # self.Bind(wx.EVT_CLOSE, self.onClose) # TODO: handle child focus for per-field level validation. # self.Bind(wx.EVT_CHILD_FOCUS, self.handleFocus) if self.buildSpec.get('auto_start', False): self.onStart() def applyConfiguration(self): self.SetTitle(self.buildSpec['program_name']) self.SetBackgroundColour(self.buildSpec.get('body_bg_color')) def onStart(self, *args, **kwarg): """ Verify user input and kick off the client's program if valid """ # navigates away from the button because a # disabled focused button still looks enabled. self.footer.cancel_button.Disable() self.footer.start_button.Disable() self.footer.start_button.Navigate() if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []): # TODO: make this wx thread safe so that it can # actually run asynchronously Thread(target=self.onStartAsync).run() else: Thread(target=self.onStartAsync).run() def onStartAsync(self, *args, **kwargs): with transactUI(self): try: errors = self.validateForm().getOrThrow() if errors: # TODO config = self.navbar.getActiveConfig() config.setErrors(errors) self.Layout() # TODO: account for tabbed layouts # TODO: scroll the first error into view # TODO: rather than just snapping to the top self.configs[0].Scroll(0, 0) else: if self.buildSpec['clear_before_run']: self.console.clear() self.clientRunner.run(self.buildCliString()) self.showConsole() except CalledProcessError as e: self.showError() self.console.appendText(str(e)) self.console.appendText( '\n\nThis failure happens when Gooey tries to invoke your ' 'code for the VALIDATE_FORM event and receives an expected ' 'error code in response.') wx.CallAfter(modals.showFailure) except JSONDecodeError as e: self.showError() self.console.appendText(str(e)) self.console.appendText( '\n\nGooey was unable to parse the response to the VALIDATE_FORM event. ' 'This can happen if you have additional logs to stdout beyond what Gooey ' 'expects.') wx.CallAfter(modals.showFailure) # for some reason, we have to delay the re-enabling of # the buttons by a few ms otherwise they pickup pending # events created while they were disabled. Trial and error # let to this solution. wx.CallLater(20, self.footer.start_button.Enable) wx.CallLater(20, self.footer.cancel_button.Enable) def onEdit(self): """Return the user to the settings screen for further editing""" with transactUI(self): for config in self.configs: config.resetErrors() self.showSettings() def onComplete(self, *args, **kwargs): """ Display the appropriate screen based on the success/fail of the host program """ with transactUI(self): if self.clientRunner.was_success(): if self.buildSpec.get('return_to_config', False): self.showSettings() else: self.showSuccess() if self.buildSpec.get('show_success_modal', True): wx.CallAfter(modals.showSuccess) else: if self.clientRunner.wasForcefullyStopped: self.showForceStopped() else: self.showError() if self.buildSpec.get('show_failure_modal'): wx.CallAfter(modals.showFailure) def onCancel(self): """Close the program after confirming We treat the behavior of the "cancel" button slightly differently than the general window close X button only because this is 'part of' the form. """ if modals.confirmExit(): self.onClose() def onStopExecution(self): """Displays a scary message and then force-quits the executing client code if the user accepts""" if self.shouldStopExecution(): self.clientRunner.stop() def onClose(self, *args, **kwargs): """Stop any actively running client program, cleanup the top level WxFrame and shutdown the current process""" # issue #592 - we need to run the same onStopExecution machinery # when the exit button is clicked to ensure everything is cleaned # up correctly. if self.clientRunner.running(): if self.shouldStopExecution(): self.clientRunner.stop() self.destroyGooey() else: self.destroyGooey() def buildCliString(self) -> str: """ Collect all of the required information from the config screen and build a CLI string which can be used to invoke the client program """ cmd = self.getCommandDetails() return cli.cliCmd( cmd.target, cmd.subcommand, cmd.positionals, cmd.optionals, suppress_gooey_flag=self.buildSpec['suppress_gooey_flag']) def validateForm(self) -> Try[Mapping[str, str]]: config = self.navbar.getActiveConfig() localErrors: Mapping[str, str] = config.getErrors() dynamicResult: Try[Mapping[str, str]] = self.fetchDynamicValidations() combineErrors = lambda m: merge(localErrors, m) return dynamicResult.map(combineErrors) def fetchDynamicValidations(self) -> Try[Mapping[str, str]]: # only run the dynamic validation if the user has # specifically subscribed to that event if Events.VALIDATE_FORM in self.buildSpec.get('use_events', []): cmd = self.getCommandDetails() return seeder.communicate( cli.formValidationCmd(cmd.target, cmd.subcommand, cmd.positionals, cmd.optionals), self.buildSpec['encoding']) else: # shim response if nothing to do. return Success({}) def getCommandDetails(self) -> CommandDetails: """ Temporary helper for getting the state of the current Config. To be deprecated upon (the desperately needed) refactor. """ config = self.navbar.getActiveConfig() group = self.buildSpec['widgets'][self.navbar.getSelectedGroup()] return CommandDetails( self.buildSpec['target'], group['command'], config.getPositionalValues(), config.getOptionalValues(), ) def shouldStopExecution(self): return not self.buildSpec[ 'show_stop_warning'] or modals.confirmForceStop() def destroyGooey(self): self.Destroy() sys.exit() def block(self, **kwargs): pass def layoutComponent(self): sizer = wx.BoxSizer(wx.VERTICAL) # sizer.Add(self.hhh, 0, wx.EXPAND) sizer.Add(self.header, 0, wx.EXPAND) sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND) sizer.Add(self.navbar, 1, wx.EXPAND) sizer.Add(self.console, 1, wx.EXPAND) sizer.Add(wx_util.horizontal_rule(self), 0, wx.EXPAND) # sizer.Add(self.fff, 0, wx.EXPAND) sizer.Add(self.footer, 0, wx.EXPAND) self.SetMinSize((400, 300)) self.SetSize(self.buildSpec['default_size']) self.SetSizer(sizer) self.console.Hide() self.Layout() if self.buildSpec.get('fullscreen', True): self.ShowFullScreen(True) # Program Icon (Windows) icon = wx.Icon(self.buildSpec['images']['programIcon'], wx.BITMAP_TYPE_PNG) self.SetIcon(icon) if sys.platform != 'win32': # OSX needs to have its taskbar icon explicitly set # bizarrely, wx requires the TaskBarIcon to be attached to the Frame # as instance data (self.). Otherwise, it will not render correctly. self.taskbarIcon = TaskBarIcon(iconType=wx.adv.TBI_DOCK) self.taskbarIcon.SetIcon(icon) def buildNavigation(self): """ Chooses the appropriate layout navigation component based on user prefs """ if self.buildSpec['navigation'] == constants.TABBED: navigation = Tabbar(self, self.buildSpec, self.configs) else: navigation = Sidebar(self, self.buildSpec, self.configs) if self.buildSpec['navigation'] == constants.HIDDEN: navigation.Hide() return navigation def buildConfigPanels(self, parent): page_class = TabbedConfigPage if self.buildSpec[ 'tabbed_groups'] else ConfigPage return [ page_class(parent, widgets, self.buildSpec) for widgets in self.buildSpec['widgets'].values() ] def showSettings(self): self.navbar.Show(True) self.console.Show(False) self.header.setImage('settings_img') self.header.setTitle(_("settings_title")) self.header.setSubtitle(self.buildSpec['program_description']) self.footer.showButtons('cancel_button', 'start_button') self.footer.progress_bar.Show(False) self.footer.time_remaining_text.Show(False) def showConsole(self): self.navbar.Show(False) self.console.Show(True) self.header.setImage('running_img') self.header.setTitle(_("running_title")) self.header.setSubtitle(_('running_msg')) self.footer.showButtons('stop_button') if not self.buildSpec.get('disable_progress_bar_animation', False): self.footer.progress_bar.Show(True) self.footer.time_remaining_text.Show(False) if self.buildSpec.get('timing_options')['show_time_remaining']: self.timer.start() self.footer.time_remaining_text.Show(True) if not self.buildSpec['progress_regex']: self.footer.progress_bar.Pulse() def showComplete(self): self.navbar.Show(False) self.console.Show(True) buttons = (['edit_button', 'restart_button', 'close_button'] if self.buildSpec.get('show_restart_button', True) else ['edit_button', 'close_button']) self.footer.showButtons(*buttons) self.footer.progress_bar.Show(False) if self.buildSpec.get('timing_options')['show_time_remaining']: self.timer.stop() self.footer.time_remaining_text.Show(True) if self.buildSpec.get( 'timing_options')['hide_time_remaining_on_complete']: self.footer.time_remaining_text.Show(False) def showSuccess(self): self.showComplete() self.header.setImage('check_mark') self.header.setTitle(_('finished_title')) self.header.setSubtitle(_('finished_msg')) self.Layout() def showError(self): self.showComplete() self.header.setImage('error_symbol') self.header.setTitle(_('finished_title')) self.header.setSubtitle(_('finished_error')) def showForceStopped(self): self.showComplete() if self.buildSpec.get('force_stop_is_error', True): self.showError() else: self.showSuccess() self.header.setSubtitle(_('finished_forced_quit'))