class StatusCombo(UberCombo): # number of milliseconds to wait after clicking the status button before the # status is set (if the user hasn't entered any text) set_delay = 3000 def __init__(self, parent, buddylist, statuses, get_status_method=get_profile_status, set_status_method=set_profile_status): ''' StatusCombo constructor. parent - a wx.Window parent window statuses - an observable list of StatusMessage objects ''' self.buddylist = buddylist self.buddylist.Bind(wx.EVT_KEY_DOWN, self.on_buddylist_key) self.searching = False self.searchHintShown = False if not getattr(StatusCombo, 'searchThresholdRegistered', False) and pref('search.buddylist.show_hint', True): def SearchThresholdReached(*a, **k): if pref('search.buddylist.show_hint', True): setpref('search.buddylist.show_hint', False) Hook('digsby.achievements.threshold', 'buddylist.search').register(SearchThresholdReached) StatusCombo.searchThresholdRegistered = True self.offline_item = None self.get_profile_status = get_status_method self.set_profile_status = set_status_method status = self.get_profile_status() UberCombo.__init__(self, parent, skinkey='combobox', typeable=True, valuecallback=self.on_text_lose_focus, empty_text=getattr(status, 'hint', status.title.title()), maxmenuheight=15) self.buttoncallback = self.on_status_button self.cbutton = UberButton(self, -1, skin=self.cbuttonskin) self.cbutton.Bind(wx.EVT_BUTTON, self._on_left_button) self.content.Insert(0, self.cbutton, 0, wx.EXPAND) self.cbutton.BBind(RIGHT_UP=self.on_status_button_right_click, LEFT_DOWN=self.on_status_button_left_click, LEFT_UP=self.on_status_button_left_up) self.display.Bind( wx.EVT_LEFT_DOWN, lambda e: (e.Skip(), setattr(self, 'oldValue', self.Value))) # the on_allow_status_changes method is called when the list of connected # im accounts changes size. if all accounts are offline this control # becomes disabled.. #profile.account_manager.connected_accounts.add_observer(self.on_allow_status_changes) profile.account_manager.connected_accounts.add_observer( self.on_offline_allowed, obj=self) # Listen on status messages (changes, additions, deletes). _obs_link = statuses.add_list_observer(self.on_status_messages_changed, self.on_status_messages_changed) self.Bind( wx.EVT_WINDOW_DESTROY, lambda e: (log.info('status combo removing observers'), e.Skip(), _obs_link.disconnect())) self.on_status_messages_changed(statuses) # when the profile's status changes, update to reflect it profile.add_observer(self.on_profile_status_changed, 'status') # Display the current status. self.show_status(self.get_profile_status()) # Timer for committing status messages after a delay. self.timer = wx.PyTimer(self.SetFocus) self.Bind(wx.EVT_TEXT, self.on_typing) self.button_timer = wx.PyTimer(self.on_status_button_right_click) textbind = self.TextField.Bind textbind(wx.EVT_SET_FOCUS, lambda e: setattr(self, 'skipenter', False)) textbind(wx.EVT_KEY_DOWN, self._on_key_down) textbind(wx.EVT_TEXT_ENTER, self._on_enter) self.DropDownButton.Bind(wx.EVT_LEFT_DOWN, self._dbutton_left) self.OnActivateSearch = Delegate() self.OnDeactivateSearch = Delegate() def UpdateSkin(self): key = 'statuspanel' if not skin.get(key, False) or skin.get(key + '.mode', '') == 'native': s = lambda k, d: None else: s = lambda k, default: skin.get('%s.%s' % (key, k), default) comboskinkey = s('comboboxskin', None) self.cbuttonskin = cbskinkey = s('statusbuttonskin', None) self.SetSkinKey(comboskinkey) UberCombo.UpdateSkin(self) if hasattr(self, 'cbutton'): self.cbutton.SetSkinKey(cbskinkey, True) self.SetButtonIcon(StatusMessage.icon_for(self.status_state)) if hasattr(self, 'menu') and self.menu: self.on_status_messages_changed() def SetButtonIcon(self, icon): """set the icon for the cycle button""" self.cbutton.SetIcon(icon) self._button_icon = icon self.Layout() def SetCallbacks(self, selection=sentinel, value=sentinel, button=sentinel): 'Sets callbacks for this combobox.' UberCombo.SetCallbacks(self, selection, value) if button is not sentinel: self.buttoncallback = button def on_allow_status_changes(self, *a, **k): if self.Show(profile.allow_status_changes): self.Parent.gui_layout() def setandshow(self, statusmsg): 'Immediately sets the status message and shows it.' log.info('setandshow %r', statusmsg) self.oldValue = None self.show_status(statusmsg) self.set_profile_status(statusmsg) def show_status(self, status, force=False): 'Displays the specified status message.' if not force and status is getattr(self, '_shown_status', None): return # make the text area not editable for statuses like "Invisble" and # "Offline", which have the "editable" attribute set to False self.Editable = status.editable self.display.empty_text = getattr(status, 'hint', '') self.ChangeValue(status.message) # change text self.SetButtonIcon(StatusMessage.icon_for(status)) # change icon self.status_state = status.status # store the state self._shown_status = status # # events # def on_typing(self, e): 'Invoked when the user is typing in the textfield.' if self.searching: search.link_prefs(profile.prefs) e.Skip() self.buddylist.search(e.EventObject.Value) else: self.cancel_timer() def on_status_button(self, button): ''' Invoked when the user clicks the state button to the left of the dropdown. ''' # toggle the control's status state isavail = StatusMessage.is_available_state(self.status_state) # do we need to change the shown text? needs_change = self._shown_status in StatusMessage.SpecialStatuses or not self._shown_status.editable self.oldValue = None self.change_state(state='Away' if isavail else 'Available', ) #change_text = needs_change) def change_state(self, state, change_text=False): if not isinstance(state, basestring): raise TypeError('change_state takes a string got a %s' % type(state)) self.status_state = state if change_text: self.ChangeValue(self.status_state, state.title()) else: self.Default = state.title() edit_toggle = getattr(profile.status, 'edit_toggle', True) if getattr(profile.status, 'edit_toggle', True): # update the icon self.SetButtonIcon(StatusMessage.icon_for(self.status_state)) self.cancel_timer() self.timer.StartOneShot(self.set_delay) # select all text in the textfield disp = self.display disp.TypeField() wx.CallAfter(disp.txtfld.SetSelection, -1, -1) else: self.setandshow( profile.status.copy(status=self.status_state, editable=None, edit_toggle=None)) def on_status_button_left_click(self, e=None): if self.searching: return self.TextField.SetFocus() self.skipenter = True self.button_timer.Start(BUTTON_HOLD_TIME, True) if e: e.Skip(True) def on_status_button_left_up(self, e=None): if not self.searching: self.button_timer.Stop() if e: e.Skip(True) def on_status_button_right_click(self, e=None): if not self.searching: self.show_extended_status_menu() def show_extended_status_menu(self): from gui.status import get_state_choices m = SimpleMenu(self, skinkey=skin.get('%s.MenuSkin' % self.skinkey)) for status in get_state_choices(): statusname, statuslabel = status def onclick(item, state=statusname): self.change_state( state ) #, change_text = self.status_state == self.GetValue()) m.AppendItem( SimpleMenuItem( [StatusMessage.icon_for(statusname), statuslabel], method=onclick)) if m.GetCount() > 0: m.Display(self.cbutton) def on_text_lose_focus(self, new_msg): if self.searching: return self.on_search_timer() # Cancel the status button timer if it's running. self.cancel_timer() if getattr(self, 'skipenter', False): wx.CallAfter(lambda: setattr(self, 'skipenter', False)) else: # don't set status if we lost focus because the user is clicking # on the state button if wx.GetMouseState().LeftDown() and wx.FindWindowAtPoint( wx.GetMousePosition()) is self.cbutton: return profile_status = self.get_profile_status() if new_msg == '': self.display.empty_text = profile_status.hint if new_msg != profile_status.message or self.status_state != profile_status.status: # entering a new text value clears all exceptions newmsg = StatusMessage(new_msg, self.status_state, new_msg) self.set_profile_status(newmsg) def on_profile_status_changed(self, *a): "Invoked when the profile's status changes." self.show_status(profile.status) @calllimit(1) def on_offline_allowed(self, *a): if not self: return show_offline = profile.allow_status_changes if not show_offline and self.offline_item: log.info('removing the offline item') self.RemoveItem(self.offline_item) self.offline_item = None elif show_offline and not self.offline_item: log.info('adding the offline item') self.offline_item = self.additem( [skin.get('statusicons.offline'), _('Offline')], self.on_offline) def additem(self, *a, **k): i = SimpleMenuItem(*a, **k) self.AppendItem(i) return i @calllimit(1) def on_status_messages_changed(self, *a): ''' Invoked when a status message changes, or the user status list changes. Rebuilds the status menu items. ''' log.info('on_status_messages_changed, updating menu') self.RemoveAllItems() additem = self.additem def add_status_item(pname, name): additem([skin.get('statusicons.%s' % pname), name], method=getattr(self, 'on_' + pname)) # Available add_status_item('available', _('Available')) # user statuses self.sortedstatuses = msgs = sorted([c for c in profile.statuses], key=lambda msg: msg.away) # Find where to insert the special "Away" status item j = -1 found = False for j, msg in enumerate(msgs): if msg.away: found = True break for i, msg in enumerate(msgs): if found and i == j: add_status_item('away', _('Away')) online_image = skin.get( 'statusicons.away' if msg.away else 'statusicons.available') additem([online_image, msg.title], method=lambda mi, msg=msg: self.setandshow(msg), id=i) if not found or j == -1: add_status_item('away', _('Away')) # Custom... additem(_('Custom...'), method=self.on_custom) self.AppendSeparator() if global_status_enabled(): additem([skin.get('icons.globalstatus'), _('Global Status')], method=self.on_global) log.info('updating status menu with %d extra statuses', len(Hook('digsby.im.statusmessages'))) for msg in Hook('digsby.im.statusmessages'): message = msg() if message is None: continue additem([message.icon, message.title], method=lambda mi, msg=msg: self.setandshow(msg())) if global_status_enabled(): additem([skin.get('statusicons.promote'), _('Promote Digsby!')], method=self.on_promote) # Invisible additem([skin.get('statusicons.invisible'), _('Invisible')], self.on_invisible) # Offline self.offline_item = None self.on_offline_allowed() # # special entries in the status menu. # def on_offline(self, combo_item): self.setandshow(StatusMessage.Offline) def on_available(self, comboitem): self.show_status(StatusMessage.Available) self.display.TypeField() def on_away(self, comboitem): self.show_status(StatusMessage.Away) self.display.TypeField() def on_custom(self, combo_item): edit_custom_status(self) def on_global(self, combo_item): wx.CallAfter(wx.GetApp().SetStatusPrompt) def on_promote(self, combo_item): wx.CallAfter(wx.GetApp().SetStatusPrompt, 'ALL', PROMOTE_STATUS_STRING(), editable=False, edit_toggle=False) def on_nowplaying(self, combo_item): self.setandshow(StatusMessage.NowPlaying) def on_invisible(self, combo_item): sta = self.get_profile_status() cpy = StatusMessage.Invisible.copy(message=sta.message) self.setandshow(cpy) def cancel_timer(self): if self.timer.IsRunning(): self.timer.Stop() # # search functionality # def _on_left_button(self, e): if not self.searching: return self.buttoncallback(self.cbutton) def _on_enter(self, e): if self.searching: self.buddylist.activate_selected_item() self.stop_searching() else: e.Skip() def _on_key_down(self, e): if self.searching: if e.KeyCode == wx.WXK_ESCAPE: self.buddylist.SetFocus() self.stop_searching() elif e.KeyCode in txtcontrol_keys: e.Skip() else: self.buddylist.on_key_down(e) else: e.Skip() def _interpret_char_event(self, e): key = None backspace = False if e is not None: mod = e.Modifiers & ~wx.MOD_SHIFT if e.KeyCode == wx.WXK_BACK: backspace = True elif mod or e.KeyCode <= ord(' ') or e.KeyCode in non_alphanumeric: return key, backspace else: key = unichr(e.UnicodeKey) return key, backspace def ShowSearchHint(self): self.searchHintShown = True def size_like(img, i): img = img.ResizedSmaller(max(i.Width, i.Height)).PIL return img.ResizeCanvas(i.Width, i.Height).WXB self.cbutton.SetIcon( size_like(skin.get('StatusPanel.SearchIcon'), self._button_icon)) self.DropDownButton.SetIcon(skin.get('StatusPanel.CancelSearchIcon')) self.display.DisplayLabel = _("Press 'Ctrl+F' to Search List") def HideSearchHint(self): self.SetButtonIcon(self._button_icon) self.DropDownButton.SetIcon(self.dropdownicon) self.searchHintShown = False self.display.DisplayLabel = None def search(self, e=None): if not pref('search.buddylist.enabled', True): if e is not None: e.Skip() return key, backspace = self._interpret_char_event(e) def size_like(img, i): img = img.ResizedSmaller(max(i.Width, i.Height)).PIL return img.ResizeCanvas(i.Width, i.Height).WXB icon = skin.get('StatusPanel.SearchIcon') self.ForceTextFieldBackground = True self.cbutton.SetIcon(size_like(icon, self._button_icon)) self.DropDownButton.SetIcon(skin.get('StatusPanel.CancelSearchIcon')) self.searching = True if not hasattr(self, 'search_timer'): self.search_timer = wx.PyTimer(self.on_search_timer) self.search_timer.Start(500) self.display.TypeField() # emulate a keypress if one started the search self.TextField.ChangeValue(profile.blist.search_string) if key is not None: self.TextField.AppendText(key) if backspace: # emulate a backspace size = self.TextField.LastPosition self.TextField.Remove(size - 1, size) self.OnActivateSearch() def on_search_timer(self): active = wx.GetActiveWindow() focused = wx.Window.FindFocus() if active is None or not self.searching: self.stop_searching() if not hasattr(self, '_allowed_windows'): # active windows search will stick around for from gui.infobox.infobox import InfoBox from gui.buddylist.buddylistframe import BuddyListFrame from gui.searchgui import SearchEditDialog self._allowed_windows = frozenset( [InfoBox, BuddyListFrame, SearchEditDialog]) self._empty_textfield_cancels = frozenset([BuddyListFrame]) clz = active.__class__ if clz not in self._allowed_windows: self.stop_searching() # if search loses focus to the buddylist and there is no text in the # search field, just cancel the search elif clz in self._empty_textfield_cancels and \ focused is not self.TextField and \ not self.TextField.Value: self.stop_searching() def stop_searching(self): if not self.searching: return log.info('stopping search') self.ForceTextFieldBackground = False self.SetButtonIcon(self._button_icon) self.DropDownButton.SetIcon(self.dropdownicon) self.search_timer.Stop() self.searching = False focused_window = wx.Window.FindFocus() if focused_window is self.TextField: self.buddylist.SetFocus() self.show_status(get_profile_status(), force=True) self.buddylist.clear_search() self.OnDeactivateSearch() hooks.notify('digsby.statistics.buddylist.search') def _dbutton_left(self, e): if self.searching: return self.stop_searching() else: e.Skip() def on_buddylist_key(self, e): if self.searching and e.KeyCode == wx.WXK_ESCAPE: self.stop_searching() else: e.Skip()
class TabBar(SimplePanel, UberWidget): """ Where the tabs live, handles all display and organization functionality """ def __init__(self, parent, skinkey): SimplePanel.__init__(self, parent) self.tabs = [] # a list of all the tabs self.rows = [] # a list of all the visible row, each a list of all the tabs in that row self.rowindex = 0 # the first visible row self.tabindex = 0 # the first tab of the first visible row self.tabendex = 0 # the last tab of the last visible row events = [(wx.EVT_PAINT, self.OnPaint), (wx.EVT_SIZE, self.OnSize), (wx.EVT_BUTTON, self.OnButton), (wx.EVT_MOUSEWHEEL, self.OnWheel), (wx.EVT_MOTION,self.OnMotion)] for event, method in events: self.Bind(event, method) self.flagedrows = set() self.lastsize=self.Size self.rowheight=0#height of a row in pixels self.SetSkinKey(skinkey,True) #buttons for verticle alignment self.cupb = UberButton(self, CUPID, skin=self.scrollbuttonskin, icon=self.upicon) self.cupb.Show(False) self.cdownb = UberButton(self, CDOWNID, skin=self.scrollbuttonskin, icon=self.downicon) self.cdownb.Show(False) #the navigation box self.navi=Navi(self) self.dragorigin = None#when draging the tab that you are dragging self.dragtarget = None#when dragging the mouse is over and at that point released on # the arrow image shown when dragging tabs self.dropmarker=OverlayImage(self, self.dropmarkerimage) # self.dropmarker = Storage(Show = lambda v=True: None) self.dragside=None#was the tab droped on the left or right of the target tab #linking prefs link = profile.prefs.link #@UndefinedVariable link('tabs.rows', self.Generate, False) link('tabs.tabbar_x', self.Generate, False) link('tabs.hide_at_1', self.Generate, False) link('tabs.side_tabs', self.SkinRedirect, False) self.Top.Bind(wx.EVT_MENU, self.OnMenuEvent) side_tabs = prefprop('tabs.side_tabs') tab_rows = prefprop('tabs.rows', 2) def UpdateSkin(self): key = self.tabskin = self.skinkey g = lambda k, default = sentinel: skin.get(key + '.' + k, default) sg = lambda k, default = sentinel: skin.get('side' + key + '.' + k, default) elems = (('spacing', 'spacing', 2), ('bg', 'backgrounds.bar'), ('dropmarkerimage', 'dropmarker.image'), # ('dropmarkeroverlay', 'dropmarker.overlay', 0), ('dropmarkeroffset', 'dropmarker.offset', 0), ('closebuttonskin', 'closebuttonskin', ''), ('closeicon', 'icons.close', None), ('scrollbuttonskin', 'scrollbuttonskin', ''), ('lefticon', 'icons.left', ''), ('righticon', 'icons.right', ''), ('upicon', 'icons.up', ''), ('downicon', 'icons.down', '')) for elem in elems: setattr(self, 'top' + elem[0], g(*elem[1:])) setattr(self, 'side' + elem[0], sg(elem[1],getattr(self,'top' + elem[0]))) setattr(self, elem[0], getattr(self, ('side' if self.side_tabs else 'top') + elem[0])) if hasattr(self,'dropmarker'): self.dropmarker.SetImage(self.dropmarkerimage) self.dropmarker.SetRotation((self.side_tabs and not self.dropmarkerimage)) navi = getattr(self, 'navi', None) if navi is not None: self.cdownb.SetSkinKey(self.scrollbuttonskin) self.cupb.SetSkinKey(self.scrollbuttonskin) self.cdownb.SetIcon(self.downicon) self.cupb.SetIcon(self.upicon) self.navi.closebutton.SetSkinKey(self.closebuttonskin) self.navi.closebutton.SetIcon(self.closeicon) scrollskin = self.scrollbuttonskin navi.prevb.SetSkinKey(scrollskin) navi.nextb.SetSkinKey(scrollskin) navi.upb.SetSkinKey(scrollskin) navi.downb.SetSkinKey(scrollskin) navi.prevb.SetIcon(self.lefticon) navi.nextb.SetIcon(self.righticon) navi.upb.SetIcon(self.upicon) navi.downb.SetIcon(self.downicon) wx.CallAfter(self.Generate) def SkinRedirect(self,val=None): elems = ('spacing', 'bg', 'dropmarkerimage', #'dropmarkeroverlay', 'closebuttonskin', 'closeicon', 'scrollbuttonskin', 'lefticon', 'righticon', 'upicon', 'downicon' ) for elem in elems: setattr(self, elem, getattr(self,('side' if self.side_tabs else 'top') + elem)) self.UpdateChildSkins() def UpdateChildSkins(self): self.cdownb.SetSkinKey(self.scrollbuttonskin,True) self.cupb.SetSkinKey(self.scrollbuttonskin,True) navi, sbs = self.navi, self.scrollbuttonskin navi.closebutton.SetSkinKey(self.closebuttonskin,True) navi.prevb.SetSkinKey(sbs, True) navi.nextb.SetSkinKey(sbs, True) navi.upb.SetSkinKey(sbs, True) navi.downb.SetSkinKey(sbs, True) self.UpdateChildrenIcons() for tab in self.tabs: tab.UpdateMode() self.Generate() def __repr__(self): return '<TabBar %r>' % self.tabs def OnDragStart(self, tab): 'Catches the tab drag event and starts the tab dragging system.' self.NotifyDrag(tab) def OnMotion(self,event): 'Positioning updates during drag and drop' if event.LeftIsDown() and (self.dragorigin or self.Manager.source): self.DragCalc(event.Position) def __getitem__(self, index): return self.tabs[index] def OnPaint(self, event): dc = wx.PaintDC(self) rect = RectS(self.Size) if not self.side_tabs: rcount = min(len(self.rows), pref('tabs.rows', 2)) height = self.tabs[0].Size.height y=0 for unused_i in xrange(rcount): self.bg.Draw(dc, Rect(rect.x, y, rect.width, height)) y += height else: self.bg.Draw(dc,rect) def Add(self, page, focus, resort = True): """ Adds a tab to the bar. Should only be used by parent NoteBook. page - page in PageContainer the tab is to be associated with focus - whether that tab should steal focus from current tab """ tab = Tab(self, page, skinkey = self.tabskin) tab.Bind(wx.EVT_CONTEXT_MENU, self.ShowMenu) tab.Show(False) self.tabs.append(tab) if focus: wx.CallAfter(tab.SetActive, True) elif resort: if self.side_tabs: self.ReVgenerate() else: self.Regenerate(True) return tab def ShowMenu(self, e): self._menutab = e.EventObject try: menu = self._tabmenu except AttributeError: from gui.uberwidgets.umenu import UMenu menu = self._tabmenu = UMenu(self) menu.AddItem('Close &Other Tabs', id = CLOSE_OTHER_TABS) menu.AddSep() menu.AddItem('&Close Tab', id = CLOSE_TAB) menu.PopupMenu() def OnMenuEvent(self, e): '''Invoked when a tab context menu item is clicked.''' if e.Id == CLOSE_TAB: self._menutab.CloseTab() elif e.Id == CLOSE_OTHER_TABS: menutab = self._menutab # switch to that tab first menutab.active = True with self.Frozen(): for tab in self.tabs[:]: if tab is not menutab: tab.CloseTab() else: e.Skip() def Generate(self, val=None): self.navi.closebutton.Show(pref('tabs.tabbar_x', False)) if self.side_tabs: self.ReVgenerate(True) else: self.Regenerate() def ReVgenerate(self,total=False, safe=False, dotoggle=True): """ It's like Doo... err.. Regenerate, only vertical """ # print "Starting: Regenerate",self.Top.Title,'\n'#,'='*80,'\n','\n'.join(format_stack()) # print '='*80 #TODO: Should we be careful about the tab leaving the bar? tabs = self.tabs if not tabs: return do(tab.Show(False) for tab in self.tabs) for tab in self.tabs: tab.row = None del self.rows[:] # Safty precautions prevent list access errors if self.tabindex < 0 or self.tabindex >= len(tabs): self.tabindex = 0 # Preset variables n = self.tabindex # the first tab shown self.rowheight = tabs[0].GetMinHeight() area = self.Notebook.Size.height - 32 # Height in pixels of the tabbar # number of fully visible rows in the given area at the given height i = area//self.rowheight count = len(tabs) #one tab per row for r in xrange(count): tabs[r].row=r rows = self.rows size = self.Size #Sets navimode and position navi = self.navi navi.ShowNav(4) navi.Hide() navi.Position = wx.Point(size.width - navi.Size.width,0) # Totally reconstructs the list if it's told to or there are not tabs in the rows or # if there isn't one more tab than there is room for and there is enough room to fit # them all and number of tabs in the row equals the number of tabs if total or not rows or (i + 1 != len(rows[0])) and not (i > len(rows[0])) and len(rows[0]) == len(tabs): rows.append([]) col = rows[0] #if all tabs fit if i >= count: n=0 self.tabindex=0 do(col.append(tab) for tab in tabs) av=col[0].MinSize.height #calculate and show range else: for t in xrange(n,n+i+1): if t < len(tabs):col.append(tabs[t]) # populate with earlier stuff while len(col) < i and n > 0: n-=1 col.insert(0,tabs[n]) if col: av = col[0].MinSize.height else: #just leave the new values the same as the old col = rows[0] av = col[0].MinSize.height # Show all tabs in the bar count = 16 for t in col: t.Size = (self.Size.width,av) t.Position = (0,count) count += av t.Show() self.tabindex=n endex = self.tabendex=n+len(col) if dotoggle: self.Toggle() cupb, cdownb = self.cupb, self.cdownb cupb.Enable(self.tabindex != 0) cdownb.Enable(endex < len(tabs) or tabs[endex - 1].Position.y + tabs[endex-1].Size.height > size.height - 16) self.UpdateNotify() def Regenerate(self, safe = False, dotoggle=True): ''' Regenerates layout information. safe is a flag to indicate if we should try to keep the currently active tab in view at all times. (This doesn't occur when scrolling, for instance.) ''' # print "Starting: Regenerate",self.Top.Title,'\n','='*80,'\n','\n'.join(format_stack()) # print '='*80 # early exit for when the tabbar isn't visible. if not self.IsShown() and len(self.tabs) == 1: return with self.Frozen(): self._Regenerate(safe = safe, dotoggle = dotoggle) self.Refresh(False) def _Regenerate(self, safe = False, dotoggle = True): self.cupb.Show(False) self.cdownb.Show(False) parentpage = self.Parent.pagecontainer # style is the number of rows (or 0 for single) style = self.tab_rows # Should we be careful about the tab leaving the bar? careful = not safe and parentpage.active # Hide all tabs preparation for refilling for tab in self.tabs: tab.Show(False) tab.row = None del self.rows[:] # navi set up, see if arrows are needed and placement tally = sum(tab.MinSize.width for tab in self.tabs) #total size of tabs navi = self.navi tabs = self.tabs rows = self.rows if not tabs: return # Tab alignment calculations # Saftey precautions prevent list access errors if self.tabindex < 0 or self.tabindex >= len(tabs): self.tabindex = 0 # Preset variables n = self.tabindex # the first tab shown i = n row = 0 self.rowheight = tabs[0].MinHeight my_w, nav_w = self.Size.width, navi.Size.width # Decide what kind of navigation panel, if any, to use... if tally >= my_w - nav_w and not style: navi.ShowNav(1) # arrows left and right elif tally >= (my_w - nav_w): navi.ShowNav(3) # arrows up and down next to the X else: navi.ShowNav(0) #Where to put navigation panel. navi.Freeze() navi.Show(True) navi.Fit() navi.Position = wx.Point(self.Size.width-navi.Size.width,0) navi.Size = wx.Size(-1,self.Size.height) navi.Thaw() #More preparing vars area = self.Notebook.Size.width - navi.Size.width #While more tabs are not in a row while len(tabs) > i: tally = tabs[i].MinSize.width rows.append([]) # Loop through each visible tab, fitting tabs on the right. while i < len(tabs) and tally < area: i += 1 if i < len(tabs): tally += tabs[i].MinSize.width #Be carefull that the active tab doesn't scroll off the bar if careful and not style: activeindex = tabs.index(parentpage.active.tab) change=False #add tabs until the active tab is visible while activeindex>=i and n!=i: i += 1 tally += tabs[i].MinSize.width change = True #Remove tab if more tabs than room if tally >= area and change: tally -= tabs[n].MinSize.width n += 1 self.tabindex=n # If extra space, fit tabs to the right of the row if not style: # if single row, while n > 0 and area - tally > tabs[n-1].MinSize.width: n -= 1 self.tabindex = n tally += tabs[n].MinSize.width # Injects tabs calculated to fit in that row into that row if range(n, i): rows[row] = [tabs[t] for t in xrange(n, i)] for tab in rows[row]: tab.row=row else: rows[row].append(tabs[i]) i += 1 if not style: break # If we're in single row, break now. row += 1 n = i # Row calculation if self.rowindex >= len(rows): self.rowindex = len(rows) - 1 #cycle through visible rows row = self.rowindex visible = self.tab_rows or 1 if careful and style: #print "Being Careful" active = parentpage.active.tab #print active for ir,r in enumerate(rows): #print ir,r if active in r: if ir<row: #print "moving index down" row = self.rowindex = ir elif ir >= row + visible: #print "moving index up" row = ir - (visible - 1) # If we're closing tabs above where is visible, keep the visible # index "where it is" if len(rows) - (row + 1) < visible and len(rows) >= visible: row = len(rows) - visible self.rowindex = row # Place tabs! while row < len(rows) and row < self.rowindex + visible and len( rows[row] ) != 0: # if this is a row that needs to be scrunched... if rows.index(rows[row]) == len(rows)-1 and \ (style or len(rows[row]) == len(tabs)): for t in xrange(0,len(rows[row])): thistab = rows[row][t] thistab.SetSize(thistab.MinSize) if not t: # The first tab is set to it's minimum width at x: 0 thistab.SetPosition((0, self.rowheight*(row-self.rowindex))) else: # Every other tab is placed right next to the tab # before it. thistab.SetPosition((rows[row][t-1].Position.x \ + rows[row][t-1].Size.width, self.rowheight*(row-self.rowindex))) thistab.Show(True) # If there are more rows than the current row... elif len(rows) > row: # Get a list of tab indices, widest to smallest. ordered = [rows[row].index(t) for t in sorted(rows[row], key=lambda o: o.MinSize.width, reverse=True) ] length = len(ordered) reserved=0 o=0 # o_O ? # Average width of tab if all tabs are the same size, and # fill up all the area. av = (area - reserved) / (length - o) mark = 0 while o < length: # Loop from "current" tab to the end for t in xrange(o, length): tab = rows[row][ordered[t]] # If this tab is larger than average... if tab.GetMinSize()[0] > av: # Make it it's minimum, and keep track of it tab.SetSize(tab.MinSize) reserved += tab.MinSize.width o += 1 mark = o # If we're not on the last tab, recalc average if (length - o): av=(area-reserved)/(length-o) else: o += 1 break # For tabs less than the average, set them to average for t in xrange(mark, length): tab = rows[row][ordered[t]] tab.SetSize((av, tab.MinSize.height)) # For every tab in the row for t, tab in enumerate(rows[row]): if not t: # If it's the first tab: if length==1: # If the row is so small it can only fit one tab, # make due. tab.Size = wx.Size(area, tab.MinSize.height) tab.Position = wx.Point(0, self.rowheight * (row - self.rowindex)) else: tab.Position = wx.Point(rows[row][t-1].Position.x + rows[row][t-1].Size.width, self.rowheight * (row - self.rowindex)) tab.Show(True) row += 1 if dotoggle: self.Toggle() # If total rows is less than total rows being shown, shrink the # tab area so that it's only just big enough. if len(rows) < style or not style: rows_shown = len(rows) else: rows_shown = style if self.Parent.SashPosition != rows_shown * self.rowheight:#self.MinSize.height self.MinSize = wx.Size(-1, rows_shown * self.rowheight) self.Parent.SetSashPosition(self.MinSize.height) # self.Size=self.MinSize # Determine if the Navi needs to enable or show arrows navi.Enabler() #self.Parent.Layout() # Relayout self self.tabendex = i-1 # final tab being shown self.UpdateNotify() navi.Size = wx.Size(-1,rows_shown * self.rowheight) def Remove(self, target): 'Removes the tab specified from the bar.' index=self.tabs.index(target) self.tabs.remove(target) #if no more tabs close window if len(self.tabs)==0: self.Notebook.window.Close() else: #if index is between index and endex and bring one tab from the left if index>self.tabindex and index<self.tabendex and self.tabindex>0: self.tabindex-=1 if self.side_tabs: self.ReVgenerate(total=True) else: self.Regenerate(safe = True) def OnSize(self, event): 'ReLayout the tabs if the bar on event of a resize' event.Skip() if self.side_tabs and self.tabs: cupb = self.cupb cdownb = self.cdownb size = self.Size tabs = self.tabs endex = self.tabendex # position and size buttons cupb.Position = (0,0) cupb.Size = (size.width, 16) cupb.Show() cupb.Enable(self.tabindex != 0) cdownb.Position = (0, size.height - 16) cdownb.Size = (size.width, 16) cdownb.Show() cdownb.Enable(endex < len(tabs) or tabs[endex - 1].Position.y + tabs[endex-1].Size.height > size.height - 16) sz = self.Size if ((sz.width != self.lastsize.width and not self.side_tabs) or (sz != self.lastsize and self.side_tabs)) and self.IsShownOnScreen(): self.lastsize = sz if self.side_tabs: self.ReVgenerate(dotoggle = False) else: self.Regenerate(False,dotoggle = False) try: wx.CallAfter(wx.CallAfter,self.Parent.pagecontainer.active.panel.input_area.expandEvent) except AttributeError: pass self.Refresh(False) def GetTabCount(self): """ Returns the number of tabs in the bar """ return len([t for t in self if t]) def NextTab(self): self.SetNextActive(self.ActiveTab, wrap = True) def PrevTab(self): self.SetLastActive(self.ActiveTab, wrap = True) def SetNextActive(self, origin,wrap=False): """ Sets the tab after the curent active -if it does not exist does the previbus -or the first if wrap is true """ if origin in self.tabs: index=self.tabs.index(origin) if not index < len(self.tabs)-1 and wrap: self.tabs[0].SetActive(True) elif index < len(self.tabs)-1: self.tabs[index+1].SetActive(True) elif index>0: self.tabs[index-1].SetActive(True) self.Refresh(False) def SetLastActive(self, origin, wrap = False): """ Sets the tab before the curent active -if it does not exist does the next -or the last if wrap is true """ if origin in self.tabs: index=self.tabs.index(origin) if not index > 0 and wrap: self.tabs[len(self.tabs)-1].SetActive(True) elif index >0: self.tabs[index-1].SetActive(True) elif index<0: self.tabs[index+1].SetActive(True) self.Refresh(False) def SyncActive(self,atab): """ Moves the index and endex so that the active tab is in the bar """ if not atab: return if self.side_tabs: if atab < self.tabindex: self.tabindex=atab self.ReVgenerate(True) else: thetab=self.tabs[atab] while atab >= self.tabendex or thetab.Position.y+thetab.Size.height > self.Size.height-16: self.tabindex+=1 self.ReVgenerate(True) else: style = self.tab_rows if atab < self.rowindex: self.rowindex=atab self.Regenerate() elif atab > self.rowindex+style-1: self.rowindex=atab-style+1 self.Regenerate() def OnWheel(self,event): """ Event that handles mouse wheeling, maps the events to SetNextActive and SetLastActive """ if RectS(self.Size).Contains(event.Position): direction = event.GetWheelRotation() if direction<0: self.SetNextActive(self.ActiveTab, True) elif direction>0: self.SetLastActive(self.ActiveTab, True) @property def Notebook(self): return self.Parent @property def Manager(self): return self.Notebook.manager @property def ActiveTab(self): active = self.Notebook.pagecontainer.active if active is not None: return active.tab def OnButton(self,event): """ The button events for vertical alignment for up and down """ if event.GetId()==CUPID: if self.tabindex > 0: self.tabindex -= 1 self.ReVgenerate(total = True) elif event.GetId()==CDOWNID: if self.tabendex<len(self.tabs) or self.tabs[self.tabendex-1].Position.y+self.tabs[self.tabendex-1].Size.height>self.Size.height-16: self.tabindex+=1 self.ReVgenerate(total=True) self.UpdateNotify() def NotifyDrag(self, origin): """ When a tab is dragged this is called to start the tabbar handling dragging origin - tab being dragged """ # Lets the TabMan know a tab as been dragged self.dragorigin = origin origin.SetCursor(wx.StockCursor(wx.CURSOR_NO_ENTRY)) self.Manager.Notify(self.Notebook) def DragCalc(self,point): """ This does the dragging calculations for the tabs """ sidetabs = self.side_tabs # if here is no local origin tab but there is a remote source identified in TabMan if not self.dragorigin and self.Manager.source: #setting a local drag origin master = self.Manager.source dragorigin=master.tabbar.dragorigin # announcing to TabMan it is expecting a tab self.Manager.Request(self.Notebook) # if there is a local origin use that else: dragorigin=self.dragorigin # if dragtarget is out of date find what you're dragging to if not self.dragtarget or not self.dragtarget.Rect.Contains(point): wap = wx.FindWindowAtPointer() self.dragtarget = wap if isinstance(wap,Tab) else None self.dragside = None # if there is a tab as target if self.dragtarget and self.dragtarget != dragorigin: dtrect=self.dragtarget.Rect # data to decide what side the tab would be dropped on if not sidetabs: x = point[0] - dtrect.x x2 = dtrect.width / 2 else: x = point[1] - dtrect.y x2 = dtrect.height / 2 # make the left/top or right/bottom decision if x <= x2:#left/top if self.dragside!=False: self.dragside=False if not sidetabs: self.DrawDropMarker(dtrect.x, dtrect.y)# + (dtrect.height // 2) else: self.DrawDropMarker(dtrect.x, dtrect.y)# + dtrect.width // 2 elif not self.dragside:#right/bottom self.dragside=True if not sidetabs: self.DrawDropMarker(dtrect.x+dtrect.width,dtrect.y)#+(dtrect.height//2) else: self.DrawDropMarker(dtrect.x,dtrect.y+dtrect.height)#+dtrect.width//2 self.SetFocus() # if being dropped in the whitespace of the TabBar elif (dragorigin and self.dragtarget!=dragorigin) or (dragorigin==None and self.dragtarget==None) and self.Rect.Contains(point): # find what row the tab is being dropped in if not sidetabs: row=self.rows[(point[1]//self.rowheight)+self.rowindex] tab=row[len(row)-1] self.dragside=True # or in vertical if at the beginning or end else: if point.y>self.rowheight: tab=self.rows[0][len(self.rows[0])-1] self.dragside=True else: tab=self.rows[0][0] self.dragside=False dtrect=tab.Rect #Place marker if not sidetabs: self.DrawDropMarker(dtrect.x+dtrect.width,dtrect.y)#+(dtrect.height//2) elif self.dragside==True: self.DrawDropMarker(dtrect.x,dtrect.y+dtrect.height)#+dtrect.width//2 else: self.DrawDropMarker(dtrect.x+dtrect.width/2,dtrect.y) self.SetFocus() #cleanup self.dragtarget=tab else:#if not in tabbar anymore don't show arrow self.dropmarker.Show(False) def DragFinish(self,new=False): """ Ends dragging and does any rearranging if required """ if not wx.IsDestroyed(self.dragorigin): self.dragorigin.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) if self.dragorigin and self.dragorigin.previewtabs: # Destroy the preview tab self.dragorigin.previewtabs.Stop() self.dragorigin.previewtabs = None rect = RectPS(self.Notebook.ClientToScreen(self.Position), self.Size) parentrect = self.Notebook.window.Rect mousepos = wx.GetMousePosition() manager = self.Manager #if released out of the window... if not new and ((manager.destination and not parentrect.Contains(mousepos)) or not rect.Contains(mousepos)): if self.ActiveTab==self.dragorigin: self.SetNextActive(self.dragorigin) self.dragorigin.Show(False) #If no or invalid destination in manager create a new window and sets it destination dest = manager.destination if not dest or not dest.tabbar.Rect.Contains(dest.ScreenToClient(wx.GetMousePosition())): # FIXME: SWIG doesn't like subtracting a wx.Size from a wx.Point, so do it the hard way # until the SIP migration is finished. originsize = self.dragorigin.GetSize() newpoint = wx.Point(mousepos[0] - originsize[0], mousepos[1] - originsize[1]) destination = self.Notebook.winman.NewWindow(newpoint, self.Notebook.window.GetSize()).notebook #else set the destination to the manager's destination else: destination = dest #clear tabman's destination manager.Request() # Grab a reference to the tab's page page = self.dragorigin.page # Make the tab "forget" about the page self.Notebook.did_remove(page.panel) del self.dragorigin.page del page.tab # Remove the tab from the tabs list, and destroy the wxWindow self.tabs.remove(self.dragorigin) self.dragorigin.Close() # remove page from this notebook and insert it into the target notebook #page.Parent.RemoveChild(page) destination.Insert(page, False) # cleanup manager.Notify() self.dragorigin=None # re-sort tabs if self.side_tabs: self.ReVgenerate(True) else: self.Regenerate(safe = True) # if released inside of the window # used both for moving within a window and as the last step of a # interwindow move in case of interwindow tab has already been moved to # this window at the end of the list and all that is left is to move it # to the correct position. elif self.dragtarget and self.dragorigin and self.dragorigin!=self.dragtarget and self.Rect.Contains(self.Notebook.ScreenToClient(mousepos)): #remove the tab from the list self.tabs.remove(self.dragorigin) #decide which side of the target the tab should be dropped pos = self.tabs.index(self.dragtarget) + (1 if self.dragside else 0) # Reinsert the tab in it's new position self.tabs.insert(pos, self.dragorigin) after = self.tabs[pos+1] if pos+1 < len(self.tabs) else None if after is not None: after = after.page.panel # call after so that windows are all in their correct places wx.CallAfter(self.Notebook.did_rearrange, self.dragorigin.page.panel, after) # # Resort if self.side_tabs: self.ReVgenerate(True) else: self.Regenerate() elif new: if self.side_tabs: self.ReVgenerate(True) else: self.Regenerate() #if there is a dragorigin run onMouseLeave on it to reset it's look if self.dragorigin: self.dragorigin.OnMouseLeave() #local cleanup self.dropmarker.Show(False) self.dragorigin=None self.dragtarget=None #destination and manager cleanup dest = manager.destination if dest: dest.tabbar.dragorigin=None dest.tabbar.dragtarget=None manager.Request() manager.Notify() if len(self.tabs)==0: self.Notebook.window.Close() def DrawDropMarker(self,x,y): """ Places the marker for where to drop the tab x and y are center position, saves calculation that way """ # if self.side_tabs: # self.dropmarker.SetSize((self.Size.width - self.dropmarkeroverlay, -1)) # else: # self.dropmarker.SetSize((-1, self.rowheight - self.dropmarkeroverlay)) self.dropmarker.Teleport(self.ClientToScreen((x, y+self.dropmarkeroffset))) if not self.dropmarker.IsShown(): self.Manager.ShowDropMarker(self.dropmarker) def Toggle(self, switch = None): 'Toggle whether the tabbar is hidden or shown.' if pref('tabs.hide_at_1', True) and len(self.tabs) <= 1 and not switch: self.Notebook.Split(False) #make sure the content of the IM win resizes if tabbaar hides self.ProcessEvent(wx.CommandEvent(wx.wxEVT_SIZE)) else: self.Notebook.Split(True) def UpdateNotify(self): frows = self.flagedrows frows.clear() # Resort if self.side_tabs: tabs=self.tabs for i, tab in enumerate(tabs): if tab.notified: frows.add(i) elif i in self.flagedrows: frows.remove(i) self.cupb.SetNotify(len(frows) and min(frows) < self.tabindex) self.cdownb.SetNotify(len(frows) and (max(frows) >= self.tabendex or tabs[max(frows)].Position.y + tabs[max(frows)].Size.height > self.Size.height - 16)) else: for i, row in enumerate(self.rows): flaged = False for tab in row: if tab and tab.notified: flaged = True frows.add(i) self.navi.upb.SetNotify(len(frows) and min(frows)<self.rowindex) self.navi.downb.SetNotify(len(frows) and max(frows)>self.rowindex + self.tab_rows - 1)
class P(wx.Panel): def __init__(self,parent): wx.Panel.__init__(self,parent,-1)#,style = wx.CLIP_CHILDREN|wx.CLIP_SIBLINGS self.Bind(wx.EVT_BUTTON,self.onButton), self.Bind(wx.EVT_LEFT_UP,self.OnMouse), self.Bind(wx.EVT_LEFT_DOWN,self.OnMouse) self.skin = 'button' # print self.skin # self.menu=UMenu(self) # self.menu.Append(wx.NewId(),'item 1') # self.menu.Append(wx.NewId(),'item 2') # self.menu.Append(wx.NewId(),'item 3') content=wx.BoxSizer(wx.VERTICAL) size=None#(200,50)# type=None#'menu'#'toggle'#'toggle'# menu=None#self.menu# icon=wx.Bitmap('../../../../res/skins/default/statusicons/mobile.png',wx.BITMAP_TYPE_PNG)#wx.Bitmap('../../res/skins/default/tinydigsby.png',wx.BITMAP_TYPE_PNG) #label= "button"#"Super wide Button Name of Impending doooooooooooom!!!"#"button"# skin=self.skin#None# self.b1=UberButton(self,wx.NewId(),'&One',skin,icon=icon,style=wx.HORIZONTAL,size=size,type=type,menu=menu) # self.b1.SetStaticWidth(60) self.b2=UberButton(self,wx.NewId(),'&Two',icon=icon,style=wx.HORIZONTAL,size=size,type=type,menu=menu) self.b3=UberButton(self,wx.NewId(),'T&hree',skin,icon=icon,style=wx.VERTICAL,size=size,type=type,menu=menu) self.b4=UberButton(self,wx.NewId(),'&Four',icon=icon,style=wx.VERTICAL,size=size,type=type,menu=menu) self.b5=UberButton(self,wx.NewId(),"",skin,icon=icon,size=size,type=type,menu=menu) self.b6=UberButton(self,wx.NewId(),"",icon=icon,size=size,type=type,menu=menu) self.b7=UberButton(self,wx.NewId(),'Fi&ve',skin,size=size,type=type,menu=menu) self.b8=UberButton(self,wx.NewId(),'&Six',size=size,type=type,menu=menu) self.b9=UberButton(self,wx.NewId(),"",skin,size=size,type=type,menu=menu) self.b10=UberButton(self,wx.NewId(),"",size=size,type=type,menu=menu) self.b11 = wx.Button(self, wx.NewId(), 'Native') self.b12= wx.Button(self, wx.NewId(), ' ') self.b12.Bind(wx.EVT_BUTTON,self.ToggleAlignment) self.b13 = UberButton(self, wx.NewId(), 'active?', self.skin) self.b13.Bind(wx.EVT_BUTTON,self.ToggleSkin) wexp = 0#wx.EXPAND#wx.ALL| hrat = 0#1# pad = 0 content.Add(self.b1,hrat,wexp,pad) content.Add(self.b2,hrat,wexp,pad) content.Add(self.b3,hrat,wexp,pad) content.Add(self.b4,hrat,wexp,pad) content.Add(self.b5,hrat,wexp,pad) content.Add(self.b6,hrat,wexp,pad) content.Add(self.b7,hrat,wexp,pad) content.Add(self.b8,hrat,wexp,pad) content.Add(self.b9,hrat,wexp,pad) content.Add(self.b10,hrat,wexp,pad) content.Add(self.b11,hrat,wexp,pad) content.Add(self.b12,hrat,wexp,pad) content.Add(self.b13, hrat, wexp, pad) self.SetSizer(content) def onButton(self,event): print event.GetEventObject().Label # print "..." # if type(event.EventObject)==wx.Button: # print event.EventObject.Label # else: # print event.EventObject.label # switch= not self.b1.IsEnabled() # self.b1.Enable(switch) # self.b2.Enable(switch) # self.b3.Enable(switch) # self.b4.Enable(switch) # self.b5.Enable(switch) # self.b6.Enable(switch) # self.b7.Enable(switch) # self.b8.Enable(switch) # self.b9.Enable(switch) # self.b10.Enable(switch) # self.b11.Enable(switch) def ToggleSkin(self,event = None): key = None if self.b1.skinkey else 'button' print 'toggleskin to',key self.b1.SetSkinKey(key,1) self.b3.SetSkinKey(key,1) self.b5.SetSkinKey(key,1) self.b7.SetSkinKey(key,1) self.b9.SetSkinKey(key,1) def ToggleAlignment(self,event = None): print "ToggleAlignment DISABLED!!! It can not be!!!" # align = wx.VERTICAL if self.b1.Alignment == wx.HORIZONTAL else wx.HORIZONTAL # self.b1.Alignment = align # self.b3.Alignment = align # self.b5.Alignment = align # self.b7.Alignment = align # self.b9.Alignment = align # self.Layout() def OnMouse(self,event): print "window caught mouse"