Exemple #1
0
 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)
Exemple #2
0
    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)
Exemple #3
0
 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)
Exemple #4
0
    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)
Exemple #5
0
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)
Exemple #6
0
    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()
Exemple #7
0
    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)
Exemple #8
0
    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)
Exemple #9
0
"""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"""
Exemple #10
0
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):
Exemple #11
0
    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()
Exemple #12
0
    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')
Exemple #13
0
    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
Exemple #14
0
            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)
Exemple #15
0
 def __init__(self, bitHopper):
     self.bitHopper = bitHopper
     hook = plugins.Hook('work.rpc.request')
     hook.register(self.rpcWorkReport)
Exemple #16
0
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):
Exemple #17
0
 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)
Exemple #18
0
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)
Exemple #19
0
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)