def add_block(self, block, work, server): """ Adds a new block. server must be the server the work is coming from """ with self.lock: hook_start = plugins.Hook('plugins.lp.add_block.start') hook_start.notify(self, block, work, server) self.blocks[block] = {} self.blocks[block]['_time'] = time.localtime() self.bitHopper.lp_callback.new_block(work, server) self.blocks[block]["_owner"] = None self.lastBlock = block hook_end = plugins.Hook('plugins.lp.add_block.end') hook_end.notify(self, block, work, server)
def set_owner(self, server, block=None): with self.lock: hook_start = plugins.Hook('plugins.lp.set_owner.start') hook_start.notify(self, server, block) if block == None: if self.lastBlock == None: return block = self.lastBlock old_owner = self.blocks[block]["_owner"] if old_owner and self.pool.servers[server][ 'coin'] != self.pool.servers[old_owner]['coin']: return self.blocks[block]["_owner"] = server if server not in self.blocks[block]: self.blocks[block][server] = 0 if '_defer' in self.blocks[block]: old_defer = self.blocks[block]['_defer'] else: old_defer = None new_defer = threading.Lock() new_defer.acquire() self.blocks[block]['_defer'] = new_defer if old_defer: old_defer.release() logging.info('Setting Block Owner ' + server + ':' + str(block)) if server in self.bitHopper.pool.servers and self.bitHopper.pool.servers[ server]['role'] == 'mine_lp' and old_owner != server: old_shares = self.bitHopper.pool.servers[server]['shares'] self.bitHopper.pool.servers[server]['shares'] = 0 self.bitHopper.scheduler.reset() self.bitHopper.select_best_server() eventlet.spawn_n(self.api_check, server, block, old_shares) #If We change servers trigger a LP. if old_owner != server: #Update list of valid server self.bitHopper.server_update() #Figure out which server to source work from source_server = self.bitHopper.pool.get_work_server() work, _, source_server = self.bitHopper.work.jsonrpc_getwork( source_server, []) #Trigger the LP Callback with the new work. self.bitHopper.lp_callback.new_block(work, source_server) hook_end = plugins.Hook('plugins.lp.set_owner.end') hook_end.notify(self, server, block)
def __init__(self, bitHopper): hook_start = plugins.Hook('plugins.lp.init.start') hook_start.notify(self, bitHopper) self.bitHopper = bitHopper logging.info('LP Module Load') self.pool = self.bitHopper.pool self.blocks = {} self.lastBlock = None self.errors = {} self.polled = {} self.lock = threading.RLock() hook_end = plugins.Hook('plugins.lp.init.end') hook_end.notify(self, bitHopper) eventlet.spawn_n(self.start_lp)
def __init__(self, bitHopper): """ Hook the lp module to print out blocks to a file """ self.bitHopper = bitHopper self.file_name = self.bitHopper.config.get('plugins.lpdump', 'file', None) if not self.file_name: return hook_ann = plugins.Hook('plugins.lp.add_block.start') hook_ann.register(self.on_block)
def main(): parser = optparse.OptionParser() parser.add_option("-H", "--headless", action="store_true", dest="headless") parser.add_option("-s", "--save", action="store_true", dest="save", help="Save data on quit to %s" % EIM_PERSISTENCE_FILE) options, arguments = parser.parse_args() if options.save: plugins.Hook('chandler.shutdown.app').register(save_all) if options.headless: ChandlerApplication.run(after=_headless) else: ChandlerApplication.run(after=runtime.use_wx_twisted)
def __init__(self, bitHopper): self.bitHopper = bitHopper self.blocks = {} self.reportInterval = 600 #self.announce_threshold = 20 self.log_dbg('Registering hooks') # register plugin hooks hook = plugins.Hook('plugins.poolblocks.verified') hook.register(self.block_verified) self.parseConfig() self.log_msg("Init") self.log_msg(" - reportInterval: " + str(self.reportInterval)) self.lastreport = time.time()
def handle_store(self, work, server, data, username, password, rpc_request): #some reject callbacks and merkle root stores if str(work).lower() == 'false': self.bitHopper.reject_callback(server, data, username, password) elif str(work).lower() != 'true': merkle_root = work["data"][72:136] self.bitHopper.getwork_store.add(server, merkle_root) #Fancy display methods hook = plugins.Hook('work.rpc.request') hook.notify(rpc_request, data, server) if data != []: self.bitHopper.data_callback(server, data, username, password) #request.remote_password)
def __init__(self, bitHopper): self.bitHopper = bitHopper if self.bitHopper.options.threshold: self.difficultyThreshold = self.bitHopper.options.threshold else: self.difficultyThreshold = 0.435 self.valid_roles = [ 'mine', 'mine_lp', 'mine_c', 'mine_charity', 'mine_force', 'mine_lp_force' ] self.valid_roles.extend([ 'mine_' + coin["short_name"] for coin in self.bitHopper.altercoins.itervalues() if coin["short_name"] != 'btc' ]) hook_announce = plugins.Hook('plugins.lp.announce') hook_announce.register(self.mine_lp_force) self.loadConfig() eventlet.spawn_n(self.bitHopper_server_update)
"""Start up an application/event loop""" __all__ = [ 'use_wx_twisted', 'use_twisted', 'Application', 'APP_START', 'REACTOR_INIT', 'WXUI_START', ] from peak.events import trellis, activity from peak.util import plugins import peak.context as context APP_START = plugins.Hook('chandler.launch.app') REACTOR_INIT = plugins.Hook('chandler.launch.reactor') WXUI_START = plugins.Hook('chandler.launch.wxui') APP_SHUTDOWN = plugins.Hook('chandler.shutdown.app') class Application(plugins.Extensible, trellis.Component, context.Service): """Base class for applications""" extend_with = APP_START def run(self, before=(), after=()): """Run app, loading `before` and `after` hooks around ``APP_START``""" self.extend_with = before, self.extend_with, after self.load_extensions() try: activity.EventLoop.run() finally: APP_SHUTDOWN.notify() def use_wx_twisted(app): """Run `app` with a wx+Twisted event loop"""
import peak.events.trellis as trellis from peak.util import plugins import peak.events.activity as activity from chandler.time_services import nowTimestamp, is_past_timestamp from chandler.core import ConstraintError, ItemAddOn ### Constants ### NOW = 100.0 LATER = 200.0 DONE = 300.0 TRIAGE_HOOK = plugins.Hook('chandler.domain.triage') ### Domain model ### def triage_status_timeline(triage): """Yield all (timestamp, status) pairs for the given item.""" yield (0, NOW) # default manual_pair = triage.manual_timestamp, triage.manual if None not in manual_pair: yield manual_pair for iterable in TRIAGE_HOOK.query(triage._item): for pair in iterable: yield pair def filter_on_time(triage, future=True): """Yield all past or future (timestamp, status) pairs for the given item.""" for timestamp, status in triage_status_timeline(triage):
def report(self): self.log_trace('report()') pools = {} for pool in self.bitHopper.pool.get_servers(): pools[pool] = {} pools[pool]['hit'] = 0 pools[pool]['incorrect'] = 0 pools[pool]['total'] = 0 # for each block see if we have verification try: for block in self.bitHopper.lp.blocks: self.log_trace('block: ' + str(block)) lp_owner = str(self.bitHopper.lp.blocks[block]['_owner']) self.log_trace(" - lp_owner: " + str(lp_owner)) #Catch in case lp_owner isn't in our pool list. if lp_owner not in pools: pools[lp_owner] = {} pools[lp_owner]['hit'] = 0 pools[lp_owner]['incorrect'] = 0 pools[lp_owner]['total'] = 0 verified_owner = None if block in self.blocks: verified_owner = str(self.blocks[block]['verified']) self.log_trace('verified owner: ' + str(verified_owner) + ' for ' + str(block)) if lp_owner == verified_owner: self.log_trace('hit ' + str(lp_owner) + ' for block ' + str(block)) pools[lp_owner]['hit'] += 1 pools[lp_owner]['total'] += 1 elif verified_owner is not None: self.log_trace('mispredict ' + str(lp_owner) + ' was ' + str(verified_owner) + ' for block ' + str(block)) pools[lp_owner]['incorrect'] += 1 pools[lp_owner]['total'] += 1 if verified_owner not in pools: pools[verified_owner] = {} pools[verified_owner]['hit'] = 0 pools[verified_owner]['incorrect'] = 0 pools[verified_owner]['total'] = 0 pools[verified_owner]['total'] += 1 else: self.log_trace('no verified owner for ' + str(block)) pass hook = plugins.Hook('plugins.blockaccuracy.report') hook.notify(pools) for pool in pools: self.log_trace('/pool/' + str(pool)) total = pools[pool]['total'] hit = pools[pool]['hit'] incorrect = pools[pool]['incorrect'] missed = total - hit # skip slush (realtime stats) if str(pool) == 'slush': continue # skip anything without hit or miss if hit == 0 and incorrect == 0 and total == 0: continue if total == 0: pct = float(0) else: pct = (float(hit) / total) * 100 msg = '[report] %(pool)16s %(hit)4d hits / %(incorrect)4d incorrect / %(total)6d total / %(hit_percent)5.1f%% hit %(missed)4d missed blocks' % \ {"pool": pool, "hit":hit, "incorrect": incorrect, "missed":missed, "total":total, "hit_percent":pct} self.log_msg(msg) except Exception, e: if self.bitHopper.options.debug: traceback.print_exc()
plugins = wx.GetApp().plugins trees = [] for plugin in plugins: skin = plugin.get_component('skin') or None if skin is not None: trees.append(skin) return trees def plugins_skinpaths(): 'Returns paths images for plugins can be loaded from.' return list( pkg_dirs ) # todo: limit skin lookups for plugins to their own res/ directory Plugins.Hook('digsby.skin.load.trees', 'plugins_skin').register(plugins_skintrees) Plugins.Hook('digsby.skin.load.skinpaths', 'plugins_skin').register(plugins_skinpaths) if __name__ == '__main__': import wx a = wx.App() logging.basicConfig() log.setLevel(1) scan('c:\\workspace\\digsby\\src\\plugins')
def receive(self, body, server): hook_start = plugins.Hook('plugins.lp.receive.start') hook_start.notify(self, body, server) if server in self.polled: self.polled[server].release() logging.debug('received lp from: ' + server) logging.log(0, 'LP: ' + str(body)) info = self.bitHopper.pool.servers[server] if info['role'] in ['disable', 'info']: return if body == None: logging.debug('error in long poll from: ' + server) with self.lock: if server not in self.errors: self.errors[server] = 0 self.errors[server] += 1 #timeout? Something bizarre? if self.errors[server] < 3 or info['role'] == 'mine_lp': eventlet.sleep(1) eventlet.spawn_after(0, self.pull_lp, self.pool.servers[server]['lp_address'], server, False) return try: output = True response = json.loads(body) work = response['result'] data = work['data'] block = data.decode('hex')[0:64] block = wordreverse(block) block = block.encode('hex')[56:120] #block = int(block, 16) with self.lock: if block not in self.blocks: logging.info('New Block: ' + str(block)) logging.info('Block Owner ' + server) self.add_block(block, work, server) #Add the lp_penalty if it exists. with self.lock: offset = self.pool.servers[server].get('lp_penalty', '0') self.blocks[block][server] = time.time() + float(offset) logging.debug('EXACT ' + str(server) + ': ' + str(self.blocks[block][server])) if self.blocks[block]['_owner'] == None or self.blocks[block][ server] < self.blocks[block][self.blocks[block] ['_owner']]: self.set_owner(server, block) hook_announce = plugins.Hook('plugins.lp.announce') logging.debug('LP Notify') hook_announce.notify(self, body, server, block) hook_start = plugins.Hook('plugins.lp.receive.end') hook_start.notify(self, body, server, block) except Exception, e: output = False logging.debug('Error in Long Poll ' + str(server) + str(body)) if self.bitHopper.options.debug: traceback.print_exc() if server not in self.errors: self.errors[server] = 0 with self.lock: self.errors[server] += 1 #timeout? Something bizarre? if self.errors[server] > 3 and info['role'] != 'mine_lp': return
self.__item = delegate._item self.__key = type(delegate) self.__is_occurrence = isinstance(self.__item, Occurrence) self.__default = default def __getattr__(self, name): if not self.__is_occurrence: return getattr(self.__delegate, name) return self.__item.modification_recipe.changes.get((self.__key, name), self.__default) def occurrence_triage(item): """Hook for triage of an occurrence.""" if not isinstance(item, Occurrence): return () else: master = Recurrence(item.master) done_before = master.triaged_done_before start = master.start_for(item) if item.hashable_recurrence_id in master.triaged_recurrence_ids: return ( master.triaged_recurrence_ids[item.hashable_recurrence_id], ) elif not done_before or done_before < start: return () else: return ((timestamp(start), DONE), ) plugins.Hook('chandler.domain.triage').register(occurrence_triage)
def __init__(self, bitHopper): self.bitHopper = bitHopper hook = plugins.Hook('work.rpc.request') hook.register(self.rpcWorkReport)
from peak.events import collections, trellis from peak.util import addons, plugins from chandler.event import Event from chandler.starred import Starred from chandler.reminder import ReminderList import chandler.triage as triage from chandler.time_services import (TimeZone, is_past, timestamp, fromtimestamp, getNow) import chandler.core as core import chandler.wxui.image as image from chandler.i18n import _ from datetime import datetime, timedelta TRIAGE_HOOK = plugins.Hook('chandler.dashboard.triage') class AppDashboardEntry(addons.AddOn, trellis.Component): @trellis.make def subject(self): return None def __init__(self, subject, **kw): kw.update(subject=subject) trellis.Component.__init__(self, **kw) @trellis.compute def _item(self): return self.subject.subject_item @trellis.compute def triage_status(self):
def __init__(self, bitHopper): self.bitHopper = bitHopper self.log_dbg('Registering hooks') # register plugin hooks hook_ann = plugins.Hook('plugins.lp.announce') hook_ann.register(self.lp_announce)
def main(): parser = optparse.OptionParser(description='bitHopper') parser.add_option('--debug', action= 'store_true', default = False, help='Extra error output. Basically print all caught errors') parser.add_option('--trace', action= 'store_true', default = False, help='Extra debugging output') parser.add_option('--listschedulers', action='store_true', default = False, help='List alternate schedulers available') parser.add_option('--port', type = int, default=8337, help='Port to listen on') parser.add_option('--scheduler', type=str, default='DefaultScheduler', help='Select an alternate scheduler') parser.add_option('--threshold', type=float, default=None, help='Override difficulty threshold (default 0.43)') parser.add_option('--config', type=str, default='bh.cfg', help='Select an alternate main config file from bh.cfg') parser.add_option('--ip', type = str, default='', help='IP to listen on') parser.add_option('--auth', type = str, default=None, help='User,Password') parser.add_option('--logconnections', default = False, action='store_true', help='show connection log') # parser.add_option('--simple_logging', default = False, action='store_true', help='remove RCP logging from output') options = parser.parse_args()[0] if options.debug: logging.getLogger().setLevel(logging.DEBUG) elif options.trace: logging.getLogger().setLevel(0) else: logging.getLogger().setLevel(logging.INFO) if options.listschedulers: schedulers = "" for s in scheduler.Scheduler.__subclasses__(): schedulers += ", " + s.__name__ print "Available Schedulers: " + schedulers[2:] return config = ConfigParser.ConfigParser() try: # determine if application is a script file or frozen exe if hasattr(sys, 'frozen'): application_path = os.path.dirname(sys.executable) elif __file__: application_path = os.path.dirname(__file__) if not os.path.exists(os.path.join(application_path, options.config)): print "Missing " + options.config + " may need to rename bh.cfg.default" os._exit(1) config.read(os.path.join(application_path, options.config)) except: if not os.path.exists(options.config): print "Missing " + options.config + " may need to rename bh.cfg.default" os._exit(1) config.read(options.config) bithopper_instance = BitHopper(options, config) if options.auth: auth = options.auth.split(',') bithopper_instance.auth = auth if len(auth) != 2: print 'User,Password. Not whatever you just entered' return # auth from config try: c = config.get('auth', 'username'), config.get('auth', 'password') bithopper_instance.auth = c except: pass override_scheduler = False if options.scheduler != None: scheduler_name = options.scheduler override_scheduler = True try: sched = config.get('main', 'scheduler') if sched != None: override_scheduler = True scheduler_name = sched except: pass if override_scheduler: logging.info("Selecting scheduler: " + scheduler_name) foundScheduler = False for s in scheduler.Scheduler.__subclasses__(): if s.__name__ == scheduler_name: bithopper_instance.scheduler = s(bithopper_instance) foundScheduler = True break if not foundScheduler: logging.info("Error couldn't find: " + scheduler_name + ". Using default scheduler.") bithopper_instance.scheduler = scheduler.DefaultScheduler(bithopper_instance) else: logging.info("Using default scheduler.") bithopper_instance.scheduler = scheduler.DefaultScheduler(bithopper_instance) bithopper_instance.select_best_server() lastDefaultTimeout = socket.getdefaulttimeout() if options.logconnections: log = None else: log = open(os.devnull, 'wb') hook = plugins.Hook('plugins.bithopper.startup') hook.notify(bithopper_instance, config, options) while True: try: listen_port = options.port try: listen_port = config.getint('main', 'port') except ConfigParser.Error: logging.debug("Unable to load main listening port from config file") pass #This ugly wrapper is required so wsgi server doesn't die socket.setdefaulttimeout(None) wsgi.server(eventlet.listen((options.ip,listen_port), backlog=500),bithopper_instance.website.handle_start, log=log, max_size = 8000) socket.setdefaulttimeout(lastDefaultTimeout) break except Exception, e: logging.info("Exception in wsgi server loop, restarting wsgi in 60 seconds\n%s" % (str(e))) eventlet.sleep(60)
class Table(wxGrid.Grid): overRowCol = None clickRowCol = None if wx.Platform == "__WXMAC__": defaultStyle = wx.BORDER_SIMPLE else: defaultStyle = wx.BORDER_STATIC TABLE_EXTENSIONS = plugins.Hook('chandler.wxui.table.extensions') def __init__(self, parent, tableData, *arguments, **keywords): super(Table, self).__init__(parent, style=self.defaultStyle, *arguments, **keywords) # Register extensions self.RegisterDataType('String', StringRenderer(), self.GetDefaultEditorForType('String')) self.TABLE_EXTENSIONS.notify(self) # Generic table setup self.SetColLabelAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE) self.SetRowLabelSize(0) self.SetColLabelSize(0) self.AutoSizeRows() self.DisableDragRowSize() self.SetDefaultCellBackgroundColour(wx.WHITE) if EXTENDED_WX: self.ScaleWidthToFit(True) self.EnableCursor(False) # The following disables drawing a black box around the # last-clicked cell. self.SetCellHighlightPenWidth(0) self.SetLightSelectionBackground() self.SetUseVisibleColHeaderSelection(True) self.SetScrollLineY(self.GetDefaultRowSize()) self.EnableGridLines(False) # should customize based on hints # wxSidebar is subclassed from wxTable and depends on the binding of # OnLoseFocus so it can override OnLoseFocus in wxTable self.Bind(wx.EVT_KILL_FOCUS, self.OnLoseFocus) self.Bind(wx.EVT_SET_FOCUS, self.OnGainFocus) # @@@ [grant] Not sure we need to support [Enter] to edit, which # is what the following binding is for self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.Bind(wxGrid.EVT_GRID_CELL_BEGIN_DRAG, self.OnItemDrag) self.GetGridWindow().Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy) gridWindow = self.GetGridWindow() # wxSidebar is subclassed from wxTable and depends on the binding of # OnMouseEvents so it can override OnMouseEvents in wxTable gridWindow.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvents) gridWindow.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnMouseCaptureLost) # It appears that wxGrid gobbles all the mouse events so we never get # context menu events. So bind right down to the context menu handler #gridWindow.Bind(wx.EVT_RIGHT_DOWN, wx.GetApp().OnContextMenu) if not EXTENDED_WX: # Use of GetGridColLabelWindow() and triangle drawing code below # from: # <http://wiki.wxpython.org/index.cgi/DrawingOnGridColumnLabel> # trap the column label's paint event: columnLabelWindow = self.GetGridColLabelWindow() columnLabelWindow.Bind(wx.EVT_PAINT, self.OnColumnHeaderPaint) if not EXTENDED_WX: _use_visible_col_header = False _selected_col = -1 _ascending_arrows = False def GetUseVisibleColHeaderSelection(self, use): return self._use_visible_col_header def SetUseVisibleColHeaderSelection(self, use): self._use_visible_col_header = use self.GetGridColLabelWindow().Refresh() def GetSelectedCol(self): return self._selected_col def SetSelectedCol(self, colnum): self._selected_col = colnum self.GetGridColLabelWindow().Refresh() def GetColSortDescending(self): return self._ascending_arrows def SetColSortAscending(self, ascending): self._ascending_arrows = ascending self.GetGridColLabelWindow().Refresh() def GetUseColSortArrows(self): return self._use_sort_arrows def SetUseColSortArrows(self, useArrows): self._use_sort_arrows = useArrows self.GetGridColLabelWindow().Refresh() def OnColumnHeaderPaint(self, event): event.Skip() # Let the regular paint handler do its thing if self._use_visible_col_header or self._use_sort_arrows: w = event.EventObject dc = wx.PaintDC(w) clientRect = w.GetClientRect() font = dc.GetFont() # For each column, draw its rectangle, its column name, # and its sort indicator, if appropriate: totColSize = -self.GetViewStart( )[0] * self.GetScrollPixelsPerUnit()[0] # Thanks Roger Binns for col in range(self.GetNumberCols()): colSize = self.GetColSize(col) rect = (totColSize, 0, colSize, w.GetSize().height) totColSize += colSize if col == self._selected_col: # draw a triangle, pointed up or down, at the # top left of the column. if self._use_sort_arrows: left = rect[0] + 3 top = rect[1] + 3 dc.SetBrush(wx.Brush(wx.BLACK, wx.SOLID)) if self._ascending_arrows: dc.DrawPolygon([(left, top), (left + 6, top), (left + 3, top + 4)]) else: dc.DrawPolygon([(left + 3, top), (left + 6, top + 4), (left, top + 4)]) if self._use_visible_col_header: dc.SetBrush(wx.Brush(wx.BLACK, wx.SOLID)) dc.DrawRectangle(rect[0] + 4, rect[3] - 4, colSize - 8, 2) def OnDestroy(self, event): try: # Release the mouse capture, if we had it if getattr(self, 'mouseCaptured', False): delattr(self, 'mouseCaptured') gridWindow = self.GetGridWindow() if gridWindow.HasCapture(): #logger.debug("wxDashboard.Destroy: ReleaseMouse") gridWindow.ReleaseMouse() #else: #logger.debug("wxDashboard.Destroy: would ReleaseMouse, but not HasCapture.") finally: event.Skip() def OnGainFocus(self, event): self.SetSelectionBackground( wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)) self.InvalidateSelection() event.Skip() def OnLoseFocus(self, event): if not self.IsCellEditControlEnabled(): self.SetLightSelectionBackground() self.InvalidateSelection() event.Skip() def EditCell(self, row, col): self.MakeCellVisible(row, col) self.SetGridCursor(row, col) self.EnableCellEditControl() # @@@ AIIIEEE: WTF. Sometimes Grid seems to have # an editor, but not actually Show() it. cellAttr = self.Table.GetAttr(row, col, wxGrid.GridCellAttr.Any) control = cellAttr.GetEditor(self, row, col).Control if not control.IsShown(): control.Show() control.SetFocus() control.SelectAll() def OnKeyDown(self, event): # default grid behavior is to move to the "next" cell, # whatever that may be. We want to edit instead. if event.GetKeyCode() == wx.WXK_RETURN: if self.IsCellEditControlEnabled(): self.SaveEditControlValue() self.EnableCellEditControl(False) return selected = self.Table.table.selection if len(selected) == 1: for index, item in enumerate(self.Table.table.items): if item in selected: row = self.Table.IndexToRow(index) for col in xrange(self.Table.GetNumberCols()): if not self.Table.ReadOnly(row, col): self.EditCell(row, col) return # other keys should just get propagated up event.Skip() def SetLightSelectionBackground(self): background = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) background.Set((background.Red() + 255) / 2, (background.Green() + 255) / 2, (background.Blue() + 255) / 2) self.SetSelectionBackground(background) def InvalidateSelection(self): numColumns = self.GetNumberCols() for rowStart, rowEnd in self.Table.SelectedRowRanges(): dirtyRect = self.GetRectForCellRange(rowStart, 0, rowEnd - rowStart + 1, numColumns) self.RefreshRect(dirtyRect) def GetRectForCellRange(self, startRow, startCol, numRows=1, numCols=1): resultRect = self.CellToRect(startRow, startCol) if numRows > 1 or numCols > 1: endRect = self.CellToRect(startRow + numRows - 1, startCol + numCols - 1) resultRect.SetTopRight(endRect.GetTopRight()) resultRect.OffsetXY(self.GetRowLabelSize(), self.GetColLabelSize()) left, top = self.CalcScrolledPosition(resultRect.GetLeft(), resultRect.GetTop()) resultRect.SetLeft(left) resultRect.SetTop(top) return resultRect def __eventToRowCol(self, event): """ return the cell coordinates for the X & Y in this event """ x = event.GetX() y = event.GetY() unscrolledX, unscrolledY = self.CalcUnscrolledPosition(x, y) row = self.YToRow(unscrolledY) col = self.XToCol(unscrolledX) return (row, col) def RebuildSections(self): # If sections change, forget that we were over a cell. self.overRowCol = None def OnMouseEvents(self, event): """ This code is tricky, tread with care -- DJA """ if not event.LeftDClick(): event.Skip() #Let the grid also handle the event by default gridWindow = self.GetGridWindow() x = event.GetX() y = event.GetY() unscrolledX, unscrolledY = self.CalcUnscrolledPosition(x, y) row = self.YToRow(unscrolledY) col = self.XToCol(unscrolledX) toolTipString = None oldOverRowCol = self.overRowCol outsideGrid = (-1 in (row, col)) refreshRowCols = set() if outsideGrid: self.overRowCol = None if event.LeftUp(): refreshRowCols.add(self.clickRowCol) refreshRowCols.add(oldOverRowCol) if self.clickRowCol == (row, col): self.Table.OnClick(row, col) self.overRowCol = None self.clickRowCol = None elif event.LeftDClick(): # Stop hover if we're going to edit self.overRowCol = None if not self.Table.ReadOnly(row, col): self.EditCell(row, col) elif event.LeftDown() and not outsideGrid: refreshRowCols.add(oldOverRowCol) if self.Table.CanClick(row, col): self.clickRowCol = self.overRowCol = row, col event.Skip(False) # Gobble the event refreshRowCols.add(self.clickRowCol) if not gridWindow.HasCapture(): gridWindow.CaptureMouse() self.SetFocus() else: self.overRowCol = None elif self.clickRowCol is not None: if not outsideGrid: self.overRowCol = row, col event.Skip(False) # Gobble the event elif not event.LeftIsDown() and (self.overRowCol != row, col): toolTipString = self.Table.GetToolTipString(row, col) if self.GetTable().TrackMouse(row, col): self.overRowCol = row, col else: self.overRowCol = None if toolTipString: gridWindow.SetToolTipString(toolTipString) gridWindow.GetToolTip().Enable(True) else: toolTip = gridWindow.GetToolTip() if toolTip: toolTip.Enable(False) gridWindow.SetToolTip(None) if self.overRowCol != oldOverRowCol: refreshRowCols.add(oldOverRowCol) refreshRowCols.add(self.overRowCol) if (self.overRowCol, self.clickRowCol) == (None, None): if (gridWindow.HasCapture()): gridWindow.ReleaseMouse() for cell in refreshRowCols: if cell is not None: self.RefreshRect(self.GetRectForCellRange(*cell)) def OnMouseCaptureLost(self, event): if hasattr(self, 'mouseCaptured'): #logger.debug("OnMouseCaptureLost: forgetting captured.") del self.mouseCaptured def OnItemDrag(self, event): # To fix bug 2159, tell the grid to release the mouse now because the # grid object may get destroyed before it has a chance to later on: gridWindow = self.GetGridWindow() if gridWindow.HasCapture(): gridWindow.ReleaseMouse() event.Skip() def IndexRangeToRowRange(self, indexRanges): """ Given a list of index ranges, [(a,b), (c,d), ...], generate corresponding row ranges[(w,x), (y, z),..] Eventually this will need to get more complex when IndexToRow() returns multiple rows """ for (indexStart, indexEnd) in indexRanges: topRow = self.IndexToRow(indexStart) bottomRow = self.IndexToRow(indexEnd) # not sure when the -1 case would happen? if -1 not in (topRow, bottomRow): yield (topRow, bottomRow)