class BundlePanel(wx.BoxSizer): COLLAPSED, PARTIAL, FULL = range(3) icons = None @classmethod def load_icons(cls): if not cls.icons: icons = cls.icons = {} guiUtility = GUIUtility.getInstance() utility = guiUtility.utility base_path = os.path.join(utility.getPath(), LIBRARYNAME, "Main", "vwxGUI", "images") icons['info'] = wx.Bitmap(os.path.join(base_path, "info.png"), wx.BITMAP_TYPE_ANY) def __init__(self, parent, parent_list, hits, general_description = None, description = None, font_increment=0): wx.BoxSizer.__init__(self, wx.HORIZONTAL) # preload icons self.load_icons() self.parent = parent self.parent_listitem = parent self.parent_list = parent_list listbody_width = parent_list.GetSize()[0] if listbody_width < BUNDLE_GRID_COLLAPSE: self.num_cols = 1 else: self.num_cols = BUNDLE_NUM_COLS # logging self.guiutility = GUIUtility.getInstance() self.uelog = UserEventLogDBHandler.getInstance() self.state = BundlePanel.COLLAPSED self.nrhits = -1 self.bundlelist = None self.font_increment = font_increment self.vsizer = wx.BoxSizer(wx.VERTICAL) self.SetBackgroundColour(DEFAULT_BACKGROUND) self.indent = parent.controls[0].icon.GetSize()[0] + 3 + 3 + self.parent_list.leftSpacer #width of icon + 3px left spacer + 3px right spacer self.AddHeader() self.AddGrid() self.SetHits(hits, noChange = True) self.UpdateHeader(general_description, description) self.AddSpacer((self.indent, -1)) self.Add(self.vsizer, 1, wx.EXPAND|wx.BOTTOM, 7) def AddHeader(self): sizer = wx.BoxSizer(wx.HORIZONTAL) self.header = StaticText(self.parent, -1, ' ') self.info_icon = wx.StaticBitmap(self.parent, -1, self.icons['info']) sizer.Add(self.header, 0, wx.RIGHT, 7) sizer.Add(self.info_icon, 0, wx.ALIGN_CENTER_VERTICAL) self.vsizer.Add(sizer, 0, wx.BOTTOM, 3) def UpdateHeader(self, general_description, description): self.SetGeneralDescription(general_description) self.SetDescription(description) def AddGrid(self): self.grid = wx.FlexGridSizer(0, self.num_cols, 3, 7) self.grid.SetFlexibleDirection(wx.HORIZONTAL) self.grid.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_NONE) self.grid.SetMinSize((1,-1)) for i in xrange(BUNDLE_NUM_ROWS): self.grid.AddGrowableRow(i, 1) for j in xrange(self.num_cols): self.grid.AddGrowableCol(j, 1) self.vsizer.Add(self.grid, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 14 + self.indent) def RebuildGrid(self, new_cols): if self.num_cols != new_cols: self.num_cols = new_cols children = self.grid.GetChildren() children_controls = [] for child in children: children_controls.append(child.GetWindow() or child.GetSizer()) for child in children_controls: self.grid.Detach(child) self.vsizer.Detach(self.grid) self.grid.Destroy() self.grid = wx.FlexGridSizer(0, self.num_cols, 3, 7) for child in children_controls: self.grid.Add(child, 0, wx.EXPAND) for j in xrange(self.num_cols): self.grid.AddGrowableCol(j, 1) self.vsizer.Add(self.grid, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 14 + self.indent) self.Layout() self.parent_listitem.Layout() return True return False def UpdateGrid(self, hits, noChange=False): N = BUNDLE_NUM_ROWS * BUNDLE_NUM_COLS items_to_add = min(N, self.nrhits) if self.nrhits > N: items_to_add -= 1 self.parent.Freeze() children = self.grid.GetChildren() didChange = len(children) < min(N, self.nrhits) if not didChange: if DEBUG: print >> sys.stderr, "*** BundlePanel.UpdateGrid: total nr items did not change, updating labels only" #total nr items did not change for i in range(items_to_add): link_static_text = children[i].GetWindow() or children[i].GetSizer() if link_static_text and getattr(link_static_text, 'SetLabel', False): link_static_text.SetLabel(hits[i].name) link_static_text.action = hits[i] else: didChange = True break if self.nrhits > N: more_caption = '(%s more...)' % (self.nrhits - N + 1) link_static_text = children[i+1].GetWindow() or children[i+1].GetSizer() if link_static_text and getattr(link_static_text, 'SetLabel', False): link_static_text.SetLabel(more_caption) link_static_text.Unbind(wx.EVT_LEFT_UP) link_static_text.Bind(wx.EVT_LEFT_UP, self.OnMoreClick) else: didChange = True if didChange: if DEBUG: print >> sys.stderr, "*** BundlePanel.UpdateGrid: something did change rebuilding grid", len(children),min(N, self.nrhits) curRows = len(children) / BUNDLE_NUM_COLS newRows = min(self.nrhits / BUNDLE_NUM_COLS, BUNDLE_NUM_ROWS) rowsChanged = curRows != newRows self.grid.ShowItems(False) self.grid.Clear(deleteWindows = True) for i in range(items_to_add): hit = hits[i] new_text = LinkStaticText(self.parent, hit.name, icon = False, icon_type = 'tree', icon_align = wx.ALIGN_LEFT, font_increment = self.font_increment, font_colour = BUNDLE_FONT_COLOR) new_text.Bind(wx.EVT_LEFT_UP, self.OnBundleLinkClick) new_text.SetMinSize((1,-1)) new_text.action = hit self.grid.Add(new_text, 0, wx.EXPAND) if self.nrhits > N: caption = '(%s more...)' % (self.nrhits - N + 1) more_label = LinkStaticText(self.parent, caption, icon = False, icon_align = wx.ALIGN_LEFT, font_increment = self.font_increment, font_colour = BUNDLE_FONT_COLOR) more_label.Bind(wx.EVT_LEFT_UP, self.OnMoreClick) self.grid.Add(more_label, 0, wx.EXPAND) self.parent_listitem.AddEvents(self.grid) if self.state != self.COLLAPSED: self.ShowGrid(False) if rowsChanged and not noChange: self.parent_listitem.OnChange() self.parent.Thaw() return didChange def OnEventSize(self, width): if width < BUNDLE_GRID_COLLAPSE: return self.RebuildGrid(1) return self.RebuildGrid(BUNDLE_NUM_COLS) def ShowGrid(self, show): if show: self.grid.ShowItems(True) else: self.grid.ShowItems(False) def UpdateList(self, hits): self.hits = hits if self.bundlelist: self.bundlelist.SetData(hits) if self.state == BundlePanel.FULL: self.bundlelist.OnLoadAll() def ShowList(self, show): if self.bundlelist is None and show: max_list = BUNDLE_NUM_ROWS * BUNDLE_NUM_COLS if len(self.hits) != BUNDLE_NUM_ROWS * BUNDLE_NUM_COLS: max_list -= 1 self.bundlelist = BundleListView(parent = self.parent, list_item_max = max_list) self.vsizer.Add(self.bundlelist, 0, wx.EXPAND|wx.BOTTOM, self.indent - 7) #a 7px spacer is already present # SetData does wx.Yield, which could cause a collapse event to be processed within the setdata # method. Thus we have to do this after the add to the sizer self.bundlelist.SetData(self.hits) elif self.bundlelist is not None and not show: self.vsizer.Detach(self.bundlelist) self.bundlelist.Show(False) self.bundlelist.Destroy() self.bundlelist = None def CollapseExpandedItem(self): if self.state != BundlePanel.COLLAPSED: self.bundlelist.list.OnCollapse() def RefreshDataBundleList(self, key, data): if self.bundlelist is not None: self.bundlelist.RefreshData(key, data) def SetDescription(self, description): self.header.SetToolTipString(description) self.info_icon.SetToolTipString(description) def SetGeneralDescription(self, general_description): if general_description: general_description = unicode(general_description) else: general_description = u'Similar' self.header.SetLabel(u'%s items (%s):' % (general_description, self.nrhits)) def SetHits(self, hits, noChange=False): self.nrhits = len(hits) gridChanged = self.UpdateGrid(hits, noChange) self.UpdateList(hits) self.Layout() return gridChanged def ChangeState(self, new_state, doLayout=True): if self.state != new_state: old_state = self.state self.state = new_state if new_state == BundlePanel.COLLAPSED: self.ShowList(False) self.ShowGrid(True) else: if new_state == BundlePanel.PARTIAL or new_state == BundlePanel.FULL: self.ShowGrid(False) if old_state == BundlePanel.COLLAPSED: self.ShowList(True) if new_state == BundlePanel.FULL and self.bundlelist: self.bundlelist.OnLoadAll() if DEBUG: statestr = lambda st: ['COLLAPSED', 'PARTIAL', 'FULL'][st] print >>sys.stderr, '*** BundlePanel.ChangeState: %s --> %s' % (statestr(old_state), statestr(new_state)) def ExpandHit(self, hit): id = hit.infohash self.bundlelist.ExpandItem(id) self.parent_listitem.ShowSelected() def OnBundleLinkClick(self, event): #do expand self.ExpandAndHideParent() staticText = event.GetEventObject() action = getattr(staticText, 'action', None) if action is not None: # Reason for non-persistence (for now) is least-surprise. # If the user collapses a bundled listitem, the previously # clicked item is still at the same location. if action in self.hits: self.hits.remove(action) self.hits.insert(0, action) self.ChangeState(BundlePanel.PARTIAL) self.ExpandHit(action) def db_callback(): self.uelog.addEvent(message="Bundler GUI: BundleLink click; %s; %s;" % (self.nrhits, self.parent_listitem.general_description), type = 3) self.guiutility.frame.guiserver.add_task(db_callback) def OnMoreClick(self, event): #do expand self.ExpandAndHideParent() self.ChangeState(BundlePanel.FULL) def db_callback(): self.uelog.addEvent(message="Bundler GUI: More click; %s; %s;" % (self.nrhits, self.parent_listitem.general_description), type = 3) self.guiutility.frame.guiserver.add_task(db_callback) def ExpandAndHideParent(self): self.parent.Freeze() if not self.parent_listitem.expanded: # Make sure the listitem is marked as expanded self.parent_listitem.OnClick() # but hide the panel self.parent_listitem.ShowExpandedPanel(False) self.parent.Thaw() #Called from GUI to get expanded torrentdetails panel def GetExpandedPanel(self): if self.bundlelist: item = self.bundlelist.GetExpandedItem() if item: return item.GetExpandedPanel() def SetBackgroundColour(self, colour): self.parent.Freeze() if getattr(self, 'grid', False): for sizeritem in self.grid.GetChildren(): child = sizeritem.GetWindow() or sizeritem.GetSizer() if child and getattr(child, 'SetBackgroundColour', False): child.SetBackgroundColour(colour) self.parent.Thaw()
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)