class SubTitleHeader(TitleHeader): @warnWxThread def GetSubTitlePanel(self, parent): self.subtitle = StaticText(parent) return self.subtitle @warnWxThread def SetSubTitle(self, subtitle): if subtitle != self.subtitle.GetLabel(): self.Freeze() self.subtitle.SetLabel(subtitle) self.subtitle.Refresh() self.Thaw()
class ChannelResultFooter(ListFooter): def GetMidPanel(self, hSizer): self.message = StaticText(self) font = self.message.GetFont() font.SetPointSize(font.GetPointSize()+2) font.SetWeight(wx.FONTWEIGHT_BOLD) self.message.SetFont(font) hSizer.Add(self.message, 0, wx.TOP|wx.BOTTOM|wx.ALIGN_BOTTOM, 3) hSizer.AddStretchSpacer() self.channelResults = wx.Button(self, -1, "Channel Results") hSizer.Add(self.channelResults, 0, wx.TOP|wx.BOTTOM, 3) def SetLabel(self, label, nr_channels): haveResults = True if nr_channels and nr_channels >= 1 else False if label != self.message.GetLabel(): self.message.SetLabel(label) if haveResults: self.HighLight() self.Layout() self.EnableResults(haveResults) def SetEvents(self, channel): #removing old, binding new eventhandler self.channelResults.Unbind(wx.EVT_BUTTON) self.channelResults.Bind(wx.EVT_BUTTON, channel) def EnableResults(self, state): self.channelResults.Enable(state) def Reset(self): self.EnableResults(False) self.message.SetLabel('')
class AbstractListBody(): @warnWxThread def __init__(self, parent_list, columns, leftSpacer=0, rightSpacer=0, singleExpanded=False, showChange=False, list_item_max=None, hasFilter=True, listRateLimit=LIST_RATE_LIMIT): self.columns = columns self.leftSpacer = leftSpacer self.rightSpacer = rightSpacer self.parent_list = parent_list self.singleExpanded = singleExpanded self.showChange = showChange self.list_selected = LIST_SELECTED self.listRateLimit = listRateLimit if not list_item_max: list_item_max = LIST_ITEM_MAX_SIZE self.list_item_max = list_item_max self.list_cur_max = self.list_item_max self.hasFilter = hasFilter hSizer = wx.BoxSizer(wx.HORIZONTAL) self.listpanel = wx.Panel(self, name="LIST") #vertical sizer containing all items self.vSizer = wx.BoxSizer(wx.VERTICAL) self.listpanel.SetSizer(self.vSizer) hSizer.Add(self.listpanel, 1) self.SetSizer(hSizer) #messagePanel text self.messagePanel = wx.Panel(self.listpanel) self.messagePanel.SetBackgroundColour(DEFAULT_BACKGROUND) self.messagePanel.Show(False) messageVSizer = wx.BoxSizer(wx.VERTICAL) self.headerText = StaticText(self.messagePanel) _set_font(self.headerText, fontweight=wx.FONTWEIGHT_BOLD) self.messageText = StaticText(self.messagePanel) self.loadNext = wx.Button(self.messagePanel) self.loadNext.Bind(wx.EVT_BUTTON, self.OnLoadMore) self.loadNext.Hide() messageVSizer.Add(self.headerText, 0, wx.EXPAND) messageVSizer.Add(self.messageText, 0, wx.EXPAND) messageVSizer.Add(self.loadNext, 0, wx.ALIGN_CENTER) self.messageText.sizer = messageVSizer self.messageText.altControl = None messageSizer = wx.BoxSizer(wx.HORIZONTAL) messageSizer.AddStretchSpacer() messageSizer.Add(messageVSizer, 0, wx.TOP | wx.BOTTOM, 7) messageSizer.AddStretchSpacer() self.messagePanel.SetSizer(messageSizer) #vertical scrollrate self.rate = None #states self.cur_expanded = None #quick filter self.filter = None self.filterMessage = None #sorting self.sortcolumn = None #queue lists self.done = True self.lastData = 0 self.dataTimer = None self.data = None self.raw_data = None self.items = {} # Allow list-items to store the most recent mouse left-down events: self.lastMouseLeftDownEvent = None self.curWidth = -1 self.Bind(wx.EVT_SIZE, self.OnEventSize) @warnWxThread def SetBackgroundColour(self, colour): wx.Panel.SetBackgroundColour(self, DEFAULT_BACKGROUND) self.listpanel.SetBackgroundColour(colour) @warnWxThread def SetStyle(self, font=None, foregroundcolour=None, list_selected=LIST_SELECTED): if font: self.SetFont(font) if foregroundcolour: self.SetForegroundColour(foregroundcolour) self.list_selected = list_selected @warnWxThread def OnSort(self, column, reverse): self.Scroll(-1, 0) #Niels: translating between -1 and None conventions if column == -1: column = None self.sortcolumn = column self.sortreverse = reverse self.SetData(highlight=False, force=True) def DoSort(self): def sortby(b, a): if a[0] in self.items: a = self.items[a[0]].data[self.sortcolumn] else: a = a[1][self.sortcolumn] if b[0] in self.items: b = self.items[b[0]].data[self.sortcolumn] else: b = b[1][self.sortcolumn] if isinstance(a, basestring): a = a.lower() if isinstance(b, basestring): b = b.lower() return cmp(a, b) if self.sortcolumn != None: self.data = sorted(self.data, cmp=sortby, reverse=self.sortreverse) def SetFilter(self, filter, filterMessage, highlight): self.filterMessage = filterMessage if self.filter is not None or filter is not None: self.filter = filter self.Scroll(-1, 0) self.SetData(highlight=highlight) @warnWxThread def OnExpand(self, item, raise_event=False): self.Freeze() if self.singleExpanded: if self.cur_expanded: self.OnCollapse(self.cur_expanded, False) panel = self.parent_list.OnExpand(item) if panel and not isinstance(panel, bool): item.Expand(panel) self.OnChange() #Niels: Windows 7 needs this refresh otherwise it will show some paint errors self.Refresh() self.cur_expanded = item self.Thaw() return panel @warnWxThread def OnCollapse(self, item=None, onchange=True): self.Freeze() if not item: item = self.cur_expanded if item: panel = item.Collapse() self.parent_list.OnCollapse(item, panel) self.cur_expanded = None if onchange: self.OnChange() #Niels: Windows 7 needs this refresh otherwise it will show some paint errors self.Refresh() self.Thaw() @warnWxThread def OnChange(self, scrollToTop=False): self.Freeze() self.vSizer.Layout() self.listpanel.Layout() self.Layout() #Determine scrollrate if self.rate is None: nritems = len(self.vSizer.GetChildren()) if nritems > 0: height = self.vSizer.GetSize()[1] self.rate = height / nritems if DEBUG: print >> sys.stderr, "ListBody: setting scrollrate to", self.rate self.SetupScrolling(scrollToTop=scrollToTop, rate_y=self.rate) else: if DEBUG: print >> sys.stderr, "ListBody: setting scrollrate to default" self.SetupScrolling(scrollToTop=scrollToTop) else: if DEBUG: print >> sys.stderr, "ListBody: using scrollrate", self.rate self.SetupScrolling(scrollToTop=scrollToTop, rate_y=self.rate) self.Thaw() @warnWxThread def Reset(self): if DEBUG: print >> sys.stderr, "ListBody: reset" self.Freeze() self.filter = None self.filterMessage = None self.sortcolumn = None self.rate = None self.vSizer.ShowItems(False) self.vSizer.Clear() for key in self.items.keys(): self.items[key].Destroy() self.list_cur_max = self.list_item_max self.items = {} self.data = None self.lastData = 0 self.raw_data = None self.ShowLoading() self.OnChange() self.Thaw() def IsEmpty(self): return len(self.items) == 0 def InList(self, key, onlyCreated=True): if onlyCreated or not self.data: return key in self.items if key in self.items: return True return any(curdata[0] == key for curdata in self.data) @warnWxThread def ScrollToEnd(self, scroll_to_end): if scroll_to_end: self.Scroll(-1, self.vSizer.GetSize()[1]) else: self.Scroll(-1, 0) @warnWxThread def ScrollToId(self, id): if id in self.items: sy = self.items[id].GetPosition()[1] / self.GetScrollPixelsPerUnit( )[1] self.Scroll(-1, sy) @warnWxThread def ShowMessage(self, message, header=None, altControl=None): if DEBUG: print >> sys.stderr, "ListBody: ShowMessage", message self.Freeze() if header: self.headerText.SetLabel(header) self.headerText.Show() else: self.headerText.Hide() self.messageText.SetLabel(message) if self.messageText.altControl: self.messageText.sizer.Detach(self.messageText.altControl) if getattr(self.messageText.altControl, 'ShowItems', False): self.messageText.altControl.ShowItems(False) self.messageText.altControl.Clear(True) self.messageText.altControl = None if altControl: self.messageText.altControl = altControl self.messageText.sizer.Insert(2, altControl, 0, wx.EXPAND) self.loadNext.Hide() self.vSizer.ShowItems(False) self.vSizer.Clear() self.vSizer.Add(self.messagePanel, 0, wx.EXPAND | wx.BOTTOM, 1) self.messagePanel.Layout() if not self.messagePanel.IsShown(): self.messagePanel.Show() self.OnChange() self.Thaw() def GetMessage(self): header = message = None if self.headerText.IsShown(): header = self.headerText.GetLabel() if self.messageText.IsShown(): message = self.messageText.GetLabel() return header, message @warnWxThread def ShowLoading(self): self.ShowMessage('Loading, please wait.') @warnWxThread def RefreshData(self, key, data): if key in self.items: if DEBUG: print >> sys.stderr, "ListBody: refresh item", self.items[key] self.items[key].RefreshData(data) #forward update to expandedPanel panel = self.items[key].GetExpandedPanel() if panel and getattr(panel, 'RefreshData', False): if DEBUG: print >> sys.stderr, "ListBody: refresh item (Calling expandedPanel refreshdata)", self.items[ key] panel.RefreshData(data) @warnWxThread def SetData(self, data=None, highlight=None, force=False): if DEBUG: nr_items = 0 if data: nr_items = len(data) print >> sys.stderr, "ListBody: new data", time(), nr_items if data == None: data = self.raw_data else: self.raw_data = data assert len(data[0][1]) == len( self.columns ), 'Data does not have equal amount of columns %d/%d %s' % (len( data[0][1]), len(self.columns), type(self.parent_list)) if highlight is None: highlight = not self.IsEmpty() def doSetData(): self.lastData = time() self.dataTimer = None self.__SetData(highlight) if force: if self.dataTimer: self.dataTimer.Stop() doSetData() else: diff = time() - (self.listRateLimit + self.lastData) call_in = -diff * 1000 if call_in <= 0: doSetData() else: if self.dataTimer == None: self.dataTimer = wx.CallLater(call_in, doSetData) else: self.dataTimer.Restart(call_in) def __SetData(self, highlight=True): if DEBUG: print >> sys.stderr, "ListBody: __SetData", time() if __debug__ and currentThread().getName() != "MainThread": print >> sys.stderr, "ListBody: __SetData thread", currentThread( ).getName(), "is NOT MAIN THREAD" print_stack() self.Freeze() #apply quickfilter if self.filter: data = filter(self.filter, self.raw_data) if len(data) != len(self.raw_data): self.parent_list.SetFilteredResults(len(data)) else: self.parent_list.SetFilteredResults(None) else: self.parent_list.SetFilteredResults(None) data = self.raw_data if not data: data = [] self.highlightSet = set() cur_keys = set(self.items.keys()) for curdata in data[:self.list_cur_max]: key = curdata[0] if key not in cur_keys: if highlight: self.highlightSet.add(key) else: cur_keys.discard(key) #cur_keys now contains all removed items for key in cur_keys: self.items[key].Show(False) self.items[key].Destroy() del self.items[key] self.data = data self.DoSort() self.done = False if len(data) > 0: self.vSizer.ShowItems(False) self.vSizer.Clear() self.CreateItems(nr_items_to_create=3 * LIST_ITEM_BATCH_SIZE) #Try to yield try: wx.Yield() except: pass elif self.filter: self.ShowMessage(self.filterMessage(empty=True) + '.') if self.done: self.Unbind( wx.EVT_IDLE ) #unbinding unnecessary event handler seems to improve visual performance else: self.Bind(wx.EVT_IDLE, self.OnIdle) self.Thaw() def OnIdle(self, event): if not self.done: if self.data and len(self.data) > 0: self.CreateItems() else: self.done = True #idle event also paints search animation, use request more to show this update event.RequestMore(not self.done) if self.done: self.Unbind(wx.EVT_IDLE) def OnLoadMore(self, event): self.loadNext.Disable() self.list_cur_max += LIST_ITEM_MAX_SIZE wx.CallAfter(self.CreateItems) self.Bind(wx.EVT_IDLE, self.OnIdle) def OnLoadAll(self): self.loadNext.Disable() self.list_cur_max = sys.maxint wx.CallAfter(self.CreateItems) self.Bind(wx.EVT_IDLE, self.OnIdle) @warnWxThread def CreateItem(self, key): if not key in self.items and self.data: for curdata in self.data: if len(curdata) > 3: thiskey, item_data, original_data, create_method = curdata else: thiskey, item_data, original_data = curdata create_method = ListItem if key == thiskey: self.items[key] = create_method( self.listpanel, self, self.columns, item_data, original_data, self.leftSpacer, self.rightSpacer, showChange=self.showChange, list_selected=self.list_selected) if self.messagePanel.IsShown(): before = len(self.vSizer.GetChildren()) - 1 self.vSizer.Insert(before, self.items[key], 0, wx.EXPAND | wx.BOTTOM, 1) else: self.vSizer.Add(self.items[key], 0, wx.EXPAND | wx.BOTTOM, 1) self.OnChange() break @warnWxThread def CreateItems(self, nr_items_to_create=LIST_ITEM_BATCH_SIZE, nr_items_to_add=None): if not nr_items_to_add: nr_items_to_add = self.list_cur_max if DEBUG: print >> sys.stderr, "ListBody: Creating items", time() initial_nr_items_to_add = nr_items_to_add done = True if len(self.data) > 0: t1 = time() self.Freeze() #Check if we need to clear vSizer self.messagePanel.Show(False) self.loadNext.Show(False) self.vSizer.Remove(self.messagePanel) if self.filter: message = self.filterMessage() + '.' else: message = '' revertList = [] #Add created/cached items for curdata in self.data: if len(curdata) > 3: key, item_data, original_data, create_method = curdata else: key, item_data, original_data = curdata create_method = ListItem if nr_items_to_add > 0 and nr_items_to_create > 0: if key not in self.items: self.items[key] = create_method( self.listpanel, self, self.columns, item_data, original_data, self.leftSpacer, self.rightSpacer, showChange=self.showChange, list_selected=self.list_selected) nr_items_to_create -= 1 elif getattr(self.items[key], 'should_update', False): self.items[key].RefreshData(curdata) item = self.items[key] sizer = self.vSizer.GetItem(item) if not sizer: self.vSizer.Add(item, 0, wx.EXPAND | wx.BOTTOM, 1) item.Show() if key in self.highlightSet: self.highlightSet.remove(key) if item.Highlight(revert=False): revertList.append(key) nr_items_to_add -= 1 else: done = nr_items_to_add == 0 or initial_nr_items_to_add == sys.maxint if done: if message != '': message = 'Only showing the first %d of %d' % (len( self.vSizer.GetChildren() ), len(self.data)) + message[ 12:] + '\nFurther specify keywords to reduce the number of items, or click the button below.' else: message = 'Only showing the first %d of %d items in this list.' % ( len(self.vSizer.GetChildren()), len(self.data)) if self.hasFilter: message += '\nSearch within results to reduce the number of items, or click the button below.' remainingItems = min( LIST_ITEM_MAX_SIZE, len(self.data) - len(self.vSizer.GetChildren())) self.loadNext.SetLabel("Show next %d items" % remainingItems) self.loadNext.Enable() self.loadNext.Show() break if len(message) > 12: self.messageText.SetLabel(message) self.vSizer.Add(self.messagePanel, 0, wx.EXPAND | wx.BOTTOM, 1) self.messagePanel.Layout() self.messagePanel.Show() self.OnChange() self.Thaw() if len(revertList) > 0: wx.CallLater(1000, self.Revert, revertList) if len(revertList) > 0: wx.CallLater(1000, self.Revert, revertList) self.done = done if DEBUG: print >> sys.stderr, "List created", len( self.vSizer.GetChildren()), "rows of", len( self.data), "took", time() - t1, "done:", self.done def GetItem(self, key): return self.items[key] def GetItemPos(self, key): # Returns the index of the ListItem belonging to this key for i, data in enumerate(self.data): if key == data[0]: return i @warnWxThread def RemoveItem(self, remove): for key, item in self.items.iteritems(): if item == remove: self.RemoveKey(key) break @warnWxThread def RemoveKey(self, key): item = self.items.get(key, None) if item: self.items.pop(key) self.vSizer.Detach(item) item.Destroy() self.OnChange() for i, curdata in enumerate(self.raw_data): if curdata[0] == key: self.raw_data.pop(i) break def GetExpandedItem(self): return self.cur_expanded def GetExpandedItems(self): return [(key, item) for key, item in self.items.iteritems() if item.expanded] @warnWxThread def Select(self, key, raise_event=True): self.DeselectAll() #check if we need to create this item on the spot if not key in self.items: self.CreateItem(key) if key in self.items: if raise_event: self.items[key].OnClick(None) else: self.items[key].expanded = True self.cur_expanded = self.items[key] self.items[key].ShowSelected() @warnWxThread def DeselectAll(self): for _, item in self.items.iteritems(): item.Deselect() def Revert(self, revertList): for key in revertList: if key in self.items: self.items[key].Revert() def OnEventSize(self, event): width = self.GetSize()[0] if width != self.curWidth: doOnChange = False self.Freeze() self.curWidth = width for item in self.items.itervalues(): if item.OnEventSize(width): doOnChange = True if doOnChange: self.OnChange() self.Thaw() event.Skip()
class TitleHeader(ListHeader): def __init__(self, parent, parent_list, columns, font_increment=2, fontweight=wx.FONTWEIGHT_BOLD, radius=LIST_RADIUS, spacers=[0, 0]): self.font_increment = font_increment self.fontweight = fontweight ListHeader.__init__(self, parent, parent_list, columns, radius=radius, spacers=spacers) @warnWxThread def AddComponents(self, columns, spacers): vSizer = wx.BoxSizer(wx.VERTICAL) vSizer.AddSpacer((-1, 3)) self.title = StaticText(self) _set_font(self.title, self.font_increment, self.fontweight) titlePanel = self.GetTitlePanel(self) subtitlePanel = self.GetSubTitlePanel(self) righttitlePanel = self.GetRightTitlePanel(self) belowPanel = self.GetBelowPanel(self) if titlePanel: subSizer = wx.BoxSizer(wx.HORIZONTAL) subSizer.Add(self.title) subSizer.Add(titlePanel, 0, wx.LEFT | wx.ALIGN_CENTER_VERTICAL, 3) titlePanel = subSizer else: titlePanel = self.title if subtitlePanel: subSizer = wx.BoxSizer(wx.VERTICAL) subSizer.Add(titlePanel, 0, wx.BOTTOM, 3) subSizer.Add(subtitlePanel) subtitlePanel = subSizer else: subtitlePanel = titlePanel subSizer = wx.BoxSizer(wx.HORIZONTAL) subSizer.Add(subtitlePanel) if righttitlePanel: subSizer.Add(righttitlePanel, 1, wx.LEFT, 3) righttitlePanel = subSizer vSizer.Add(righttitlePanel, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, self.radius + spacers[0]) if belowPanel: vSizer.Add(belowPanel, 1, wx.EXPAND | wx.TOP, 3) vSizer.AddSpacer((-1, 3)) if len(columns) > 0: hSizer = wx.BoxSizer(wx.HORIZONTAL) self.AddColumns(hSizer, self, columns) vSizer.Add(hSizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT, self.radius + spacers[0]) self.SetSizer(vSizer) def GetTitlePanel(self, parent): pass def GetSubTitlePanel(self, parent): pass def GetRightTitlePanel(self, parent): pass def GetBelowPanel(self, parent): pass @warnWxThread def SetTitle(self, title): if title != self.title.GetLabel(): self.Freeze() self.title.SetLabel(title) self.title.Refresh() self.Layout() self.Thaw() @warnWxThread def SetToolTip(self, tooltip): self.title.SetToolTipString(tooltip)