Beispiel #1
0
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()
Beispiel #2
0
class SearchSideBar(wx.Panel):
    
    INDENT = 7
    HEADER_FONT_WEIGHT = wx.FONTWEIGHT_NORMAL
    
    def __init__(self, parent, parent_list, size):
        wx.Panel.__init__(self, parent, size = size)
        self.SetForegroundColour(parent.GetForegroundColour())

        self.guiutility =  GUIUtility.getInstance()
        self.torrentsearch_manager = self.guiutility.torrentsearch_manager
        self.parent = parent
        self.parent_list = parent_list
        
        self.nrfiltered = 0
        self.family_filter = True
        self.bundlestates = [Bundler.ALG_MAGIC, Bundler.ALG_NAME, Bundler.ALG_NUMBERS, Bundler.ALG_SIZE, Bundler.ALG_OFF]
        self.bundlestates_str = {Bundler.ALG_NAME: 'Name',
                                 Bundler.ALG_NUMBERS: 'Numbers',
                                 Bundler.ALG_SIZE: 'Size',
                                 Bundler.ALG_MAGIC: 'Magic',
                                 Bundler.ALG_OFF: 'Off'}
        self.bundletexts = []
        self.bundle_db = BundlerPreferenceDBHandler.getInstance()
        self.uelog = UserEventLogDBHandler.getInstance()
        
        self.vSizer = wx.BoxSizer(wx.VERTICAL)
        
        hSizer = wx.BoxSizer(wx.HORIZONTAL)
        header = StaticText(self, -1, 'Search')
        if SearchSideBar.HEADER_FONT_WEIGHT != wx.FONTWEIGHT_NORMAL:
            font = header.GetFont()
            font.SetWeight(SearchSideBar.HEADER_FONT_WEIGHT)
            header.SetFont(font)
        hSizer.Add(header, 0, wx.ALIGN_CENTER_VERTICAL)
        
        self.searchState = StaticText(self)
        hSizer.Add(self.searchState, 1, wx.ALIGN_CENTER_VERTICAL)
        
        ag_fname = os.path.join(self.guiutility.utility.getPath(), LIBRARYNAME, 'Main', 'vwxGUI', 'images', 'search_new.gif')
        self.ag = wx.animate.GIFAnimationCtrl(self, -1, ag_fname)
        self.ag.UseBackgroundColour(True)
        self.ag.Hide()
        hSizer.Add(self.ag, 0, wx.RESERVE_SPACE_EVEN_IF_HIDDEN)
        
        self.vSizer.Add(hSizer, 0, wx.EXPAND|wx.BOTTOM, 3)
        self.vSizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.BOTTOM, 3)

        self.searchGauge = wx.Gauge(self, size = (-1, 7))
        self.vSizer.Add(self.searchGauge, 0, wx.EXPAND|wx.RESERVE_SPACE_EVEN_IF_HIDDEN)
        
        self.vSizer.AddSpacer((-1,15))
        
        hSizer = wx.BoxSizer(wx.HORIZONTAL)
        header = StaticText(self, -1, 'Family Filter')
        if SearchSideBar.HEADER_FONT_WEIGHT != wx.FONTWEIGHT_NORMAL:
            font = header.GetFont()
            font.SetWeight(SearchSideBar.HEADER_FONT_WEIGHT)
            header.SetFont(font)
        hSizer.Add(header)
        
        self.ffstate = StaticText(self)
        hSizer.Add(self.ffstate)
        self.vSizer.Add(hSizer, 0, wx.EXPAND|wx.BOTTOM, 3)
        self.vSizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.BOTTOM, 3)
        
        self.ffblocked = StaticText(self)
        self.vSizer.Add(self.ffblocked, 0, wx.EXPAND|wx.LEFT, SearchSideBar.INDENT)
        
        self.ffbutton = LinkStaticText(self, '', None)
        self.ffbutton.Bind(wx.EVT_LEFT_UP, self.toggleFamilyFilter)
        self.vSizer.Add(self.ffbutton, 0, wx.EXPAND|wx.LEFT, SearchSideBar.INDENT)
        
        self.vSizer.AddSpacer((-1,15))
        
        hSizer = wx.BoxSizer(wx.HORIZONTAL)
        header = StaticText(self, -1, 'Bundling')
        if SearchSideBar.HEADER_FONT_WEIGHT != wx.FONTWEIGHT_NORMAL:
            font = header.GetFont()
            font.SetWeight(SearchSideBar.HEADER_FONT_WEIGHT)
            header.SetFont(font)
        
        hSizer.Add(header)
        
        #keep longest text in bundlestatetext, to define bestsize (width) for sidepanel
        self.bundlestatetext = StaticText(self, -1, ' by Numbers')
        hSizer.Add(self.bundlestatetext)
        self.vSizer.Add(hSizer, 0, wx.EXPAND|wx.BOTTOM, 3)
        self.vSizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.BOTTOM, 3)
            
        self.bundleSizer = wx.FlexGridSizer(0, 2, 0, 0)
        self.SetBundleState(None, reset = True)
        self.vSizer.Add(self.bundleSizer, 0, wx.EXPAND|wx.LEFT, SearchSideBar.INDENT)
        
        self.vSizer.AddSpacer((-1,15))
        
        header = StaticText(self, -1, 'Associated Channels')
        if SearchSideBar.HEADER_FONT_WEIGHT != wx.FONTWEIGHT_NORMAL:
            font = header.GetFont()
            font.SetWeight(SearchSideBar.HEADER_FONT_WEIGHT)
            header.SetFont(font)
        self.vSizer.Add(header, 0, wx.EXPAND|wx.BOTTOM, 3)
        self.vSizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND|wx.BOTTOM, 3)
        
        self.nochannels = StaticText(self, -1, 'None')
        self.vSizer.Add(self.nochannels, 0, wx.EXPAND|wx.LEFT, SearchSideBar.INDENT)
        
        self.channels = [LinkStaticText(self, '', icon = None) for _ in range(3)]
        for channel in self.channels:
            self.vSizer.Add(channel, 0, wx.EXPAND|wx.LEFT, SearchSideBar.INDENT)
            channel.Bind(wx.EVT_LEFT_UP, self.OnChannel)
        
        borderSizer = wx.BoxSizer(wx.VERTICAL)
        borderSizer.AddSpacer((-1, 3))
        borderSizer.Add(self.vSizer, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 7)

        self.SetSizer(borderSizer)
        self.SetMinSize((self.GetBestSize()[0], -1))
        
        self.Reset()
        
    def SetFF(self, family_filter, nrfiltered):
        self.family_filter = family_filter
        self.nrfiltered = nrfiltered
        self._SetLabels()
    
    @forceWxThread
    def SetMaxResults(self, max, remotekeywords):
        self.Freeze()
        
        self.searchGauge.SetRange(max)
        self.searchGauge.SetValue(0)
        self.searchGauge.Show()
        self.searchState.SetLabel(' in progress')
        
        wx.CallLater(10000, self.SetFinished, remotekeywords)
        
        self.ag.Play()
        self.ag.Show()
        
        self.Thaw()
    
    @forceWxThread
    def NewResult(self):
        maxValue = self.searchGauge.GetRange()
        newValue = min(self.searchGauge.GetValue() + 1, maxValue)
        if newValue == maxValue:
            self.SetFinished(None)
        else:
            self.searchGauge.SetValue(newValue)
        
    def SetFinished(self, keywords):
        curkeywords, hits, filtered = self.guiutility.torrentsearch_manager.getSearchKeywords()
        if not keywords or curkeywords == keywords:
            self.Freeze()
            
            self.ag.Stop()
            self.ag.Hide()
            self.searchGauge.Hide()
            self.searchState.SetLabel(' completed')
            self.Layout()
            
            self.Thaw()
            self.guiutility.frame.searchlist.SetFinished()
    
    @forceWxThread
    def SetAssociatedChannels(self, channels):
        #channels should be a list, of occurrences, name, permid
        self.Freeze()

        self.nochannels.Show(len(channels) == 0)
        for i in range(len(self.channels)):
            if i < len(channels):
                tooltip = "Click to go to %s's Channel."%channels[i][-1].name
                
                self.channels[i].SetLabel(channels[i][-1].name)
                self.channels[i].SetToolTipString(tooltip)
                self.channels[i].channel = channels[i][-1]
                
            else:
                self.channels[i].SetLabel('')
                self.channels[i].SetToolTipString('')

        self.Layout()
        self.Thaw()
    
    def toggleFamilyFilter(self, event):
        self.parent_list.toggleFamilyFilter()
        
    def _SetLabels(self):
        self.Freeze()
        if self.family_filter:
            if self.nrfiltered > 0:
                if self.nrfiltered > 1:
                    self.ffblocked.SetLabel('%d results blocked'%self.nrfiltered)
                else:
                    self.ffblocked.SetLabel('1 result blocked')
                
                self.vSizer.Detach(self.ffblocked)
                self.vSizer.Insert(6, self.ffblocked, 0, wx.EXPAND|wx.LEFT, 7)
            else:
                self.ffblocked.SetLabel('')
                self.vSizer.Detach(self.ffblocked)
                self.vSizer.Insert(7, self.ffblocked)
                
            self.ffstate.SetLabel(' is On')
            self.ffbutton.SetLabel('turn off')
        else:
            self.ffstate.SetLabel(' is Off')
            self.ffbutton.SetLabel('turn on')
            self.ffblocked.SetLabel('')
        self.Layout()
        self.Thaw()
    
    def Reset(self):
        self.SetBundleState(None,refresh=False,reset=True)
        self.nochannels.Show()
        self.searchState.SetLabel(' in progress')
        
        for channel in self.channels:
            channel.SetLabel('')
            channel.SetToolTipString('')
    
    def OnRebundle(self, event):
        curstate = self.bundlestate
        selectedByMagic = -1
        for i, text in enumerate(self.bundletexts):
            if isinstance(text, LinkStaticText) and text.IsIconShown():
                selectedByMagic = self.bundlestates[i]
                break
        
        newstate = event.GetEventObject().action
        self.SetBundleState(newstate)
        
        def db_callback():
            keywords = self.torrentsearch_manager.getSearchKeywords()[0]
            self.bundle_db.storePreference(keywords, newstate)
            query = ' '.join(keywords)
            
            selectedByMagicStr = ''
            if selectedByMagic != -1:
                selectedByMagicStr = self.bundlestates_str[selectedByMagic]
            
            self.uelog.addEvent(message="Bundler GUI: %s -> %s; %s -> %s; selectedByMagic %s (%s); q=%s" 
                                % (curstate, newstate, self.bundlestates_str[curstate], 
                                   self.bundlestates_str[newstate],
                                   selectedByMagic, selectedByMagicStr, query), type = 3)
        
        self.guiutility.frame.guiserver.add_task(db_callback)
        
    def SetBundleState(self, newstate, refresh=True, reset=False):
        if newstate is None:
            auto_guess = self.guiutility.utility.config.Read('use_bundle_magic', "boolean")
            
            newstate = Bundler.ALG_OFF # default
            stored_state = None
            
            if not reset:
                keywords = self.torrentsearch_manager.getSearchKeywords()[0]
                if keywords != '':
                    try:
                        stored_state = self.bundle_db.getPreference(keywords)
                    except:
                        pass
                        #if db interaction fails, ignore
                    
            local_override = stored_state is not None
            if local_override:
                newstate = stored_state
                
            elif auto_guess:
                newstate = Bundler.ALG_MAGIC
        
        self.bundlestate = newstate
        self.selected_bundle_mode = None
        self.Freeze()
        
        if newstate != Bundler.ALG_OFF:
            self.bundlestatetext.SetLabel(' by %s' % self.bundlestates_str[newstate])
        else:
            self.bundlestatetext.SetLabel(' is %s' % self.bundlestates_str[newstate])
        self.torrentsearch_manager.setBundleMode(newstate, refresh)
        
        self.bundleSizer.ShowItems(False)
        self.bundleSizer.Clear(deleteWindows = True)
        self.bundletexts = []
        self.bundleSizer.Add(StaticText(self, -1, 'Bundle by '))
        for i, state in enumerate(self.bundlestates):
            if newstate == state:
                text = StaticText(self, -1, self.bundlestates_str[state])
                self.bundleSizer.Add(text)
                self.bundletexts.append(text)
            else:
                link = LinkStaticText(self, self.bundlestates_str[state], "wand.png")
                link.ShowIcon(False)
                link.SetIconToolTipString('Selected by Magic')
                link.Bind(wx.EVT_LEFT_UP, self.OnRebundle)
                link.action = state
                self.bundleSizer.Add(link)
                self.bundletexts.append(link)
                
            if i+1 < len(self.bundlestates):
                self.bundleSizer.AddSpacer((1, -1))
        
        self.Layout()
        self.Thaw()
    
    def SetSelectedBundleMode(self, selected_bundle_mode):
        if self.bundlestate == Bundler.ALG_MAGIC:
            self.Freeze()
            
            self.selected_bundle_mode = selected_bundle_mode
            index = self.bundlestates.index(selected_bundle_mode)
            for i in range(len(self.bundletexts)):
                linkStaticText = self.bundletexts[i]
                if isinstance(linkStaticText, LinkStaticText):
                    if i == index: 
                        if not linkStaticText.IsIconShown():
                            linkStaticText.ShowIcon(True)
                            wx.CallAfter(linkStaticText.Blink)
                    else:
                        linkStaticText.ShowIcon(False)
            self.Thaw()
    
    def OnChannel(self, event):
        label = event.GetEventObject()
        channel_name = label.GetLabel()
        
        if channel_name != '':
            channel = label.channel
            self.guiutility.showChannel(channel)
    
    def SetBackgroundColour(self, colour):
        wx.Panel.SetBackgroundColour(self, colour)
        
        self.ffbutton.SetBackgroundColour(colour)
        self.ag.SetBackgroundColour(colour)
        
        for channel in self.channels:
            channel.SetBackgroundColour(colour)
        
        for sizeritem in self.bundleSizer.GetChildren():
            if sizeritem.IsWindow():
                child = sizeritem.GetWindow()
                if isinstance(child, wx.Panel):
                    child.SetBackgroundColour(colour)