예제 #1
0
파일: editor.py 프로젝트: RochSchanen/edit
 def _refresh(self):
     # get geometry
     X, Y = self.scroll
     cw, ch = self.charSize
     sl, sr, st, sb = self._borders
     cl, rw = self.screenSize
     # create device context
     dc = MemoryDC()               
     # select bitmap
     dc.SelectObject(self.bitmapBuffer)
     # clear buffer
     self._clear(dc)
     # draw _borders
     self.drawBorders(dc)
     # draw each lines
     for y, line in enumerate(self.textBuffer[Y:]):
         # check for last visible line
         if y+st >= rw-sb: break
         # get line number
         lineNumber = f'{y+Y:{sl-1}d}'
         # draw number
         for x, c in enumerate(lineNumber):
             # get character position
             u, v = x*cw, (y+st)*ch
             # draw character
             dc.DrawBitmap(self.CS.get(c, 'emp'), u, v)
         # draw line
         # x = 0 # empty line jumps straight to else case
         for x, c in enumerate(line[X:]+'¶'):
             # check for last visible character
             if x+sl >= cl-sr: break
             # check if character selected
             selected = self.isSelected(x+X, y+Y)
             # adjust display style
             style = 'sel' if selected else 'nrm'
             # get screen position
             u, v = (x+sl)*cw, (y+st)*ch
             # draw character
             dc.DrawBitmap(self.CS.get(c, style), u, v)
     # draw cursor
     self.drawCursor(dc)
     # done
     dc.SelectObject(NullBitmap)
     return
예제 #2
0
    def SaveBackground(self, dc):
        """
        Save the given dc as the new background image.

        @param DC dc : the dc to save
        """
        w, h = self.GetSize()
        bb = self.__backgroundBitmap
        if (bb.GetWidth(), bb.GetHeight()) != (w, h):
            bb = self.__backgroundBitmap = Bitmap(w, h)
        mem = MemoryDC()
        mem.SelectObject(bb)

        if __version__ > "2.3.2":
            x, y = self.CalcUnscrolledPosition(0, 0)
            mem.Blit(0, 0, w, h, dc, x, y)
        else:
            mem.Blit(0, 0, w, h, dc, 0, 0)
        mem.SelectObject(NullBitmap)
예제 #3
0
 def OnFacename(self, facename):
     idx = len(self.font_list)
     self.font_list.append(facename)
     w, h = self.GetTextExtentCached(idx)
     self.bitmaps[idx] = wx.Bitmap(w, h)
     dc = MemoryDC()
     dc.SelectObject(self.bitmaps[idx])
     dc.Clear()
     font = self.MakeFont(facename=facename)
     dc.SetFont(font)
     dc.DrawText(facename, 0, 0)
     wx.CallAfter(self.Append, facename, idx)
     return True
예제 #4
0
파일: editor.py 프로젝트: RochSchanen/edit
 def _createCharBitmap(self, name, font, foreground, background):
     # create device context
     dc = MemoryDC()               
     # set font
     dc.SetFont(self._fonts[font])
     # get font size
     _cw, _ch = dc.GetTextExtent(' ')
     BitmapSet = {}
     for c in self._CharSet:
         # create bitmap
         bitmap = Bitmap(_cw, _ch, BITMAP_SCREEN_DEPTH)
         # select bitmap
         dc.SelectObject(bitmap)
         # set background
         dc.SetBackground(self._brushes[background])
         # clear bitmap
         dc.Clear()
         # set text color
         dc.SetTextForeground(self._colours[foreground])
         # record reference
         BitmapSet[c] = bitmap
         # check alternate drawing 1
         if name == 'nrm' and c == '¶':
             dc.DrawText(' ',0,0)
             continue
         # check alternate drawing 2
         if name == 'sel' and c == ' ':
             dc.DrawText('·',0,0)
             continue
         # draw character
         dc.DrawText(c,0,0) 
     # release bitmap
     dc.SelectObject(NullBitmap)
     # record set
     self._CharBitmaps[name] = BitmapSet
     # done
     return
예제 #5
0
 def createBufferBitmap(self, width, height):
     # get geometry
     cw, ch = self.charSize
     # create bitmap buffer
     self.bmpBuf = Bitmap(
         width *cw,              # bitmap width
         height*ch,              # bitmap heigh
         BITMAP_SCREEN_DEPTH)    # bitmap depth
     # create device context
     dc = MemoryDC()               
     # select buffer
     dc.SelectObject(self.bmpBuf)
     # set background
     dc.SetBackground(self.brushes['bkgd'])
     # clear buffer
     dc.Clear()
     # release bitmap
     dc.SelectObject(NullBitmap)
     # hook BackgroundBitmap to bitmapBuffer handle
     self.Panel.BackgroundBitmap = self.bmpBuf
     # record geometry
     self.screenSize = width, height
     # done        
     return
예제 #6
0
    def refreshBuffer(self):
        # get geometry
        X, Y = self.scroll
        cw, ch = self.charSize
        sl, sr, st, sb = 5, 15, 5, 5
        cl, rw = self.screenSize
        # create device context
        dc = MemoryDC()               
        # select
        dc.SelectObject(self.bmpBuf)
        # set background
        dc.SetBackground(self.brushes['bkgd'])
        # and clear buffer
        dc.Clear()
        # draw margins
        x3, x4 = 0*cw, cl*cw
        x1, y1 = (sl-0.5)*cw, (st-0.5)*ch
        x2, y2 = (cl-sr+0.5)*cw, (rw-sb+0.5)*ch
        dc.SetPen(self.pens['margin'])
        dc.DrawLine(x1, y1, x1, y2)
        dc.DrawLine(x2, y1, x2, y2)
        dc.DrawLine(x3, y1, x4, y1)
        dc.DrawLine(x3, y2, x4, y2)
        # get block geometry
        c1, r1, c2, r2 = self.getBlockGeometry()
        # set brush
        dc.SetBrush(Brush(Colour(100,100,100)))
        dc.SetPen(self.pens['transparent'])
        # draw text
        x, y = 0, -1
        for line in self.chrBuf[Y:]:
            y += 1
            if y+st >= rw-sb: break
            # draw line number
            self.drawText(dc, f'{y+Y:4d}', 0, y+st, self.numberChars)
            # the line is outside of the block
            if y+Y < r1 or y+Y > r2:
                self.drawText(dc, line[X:], x+sl, y+st, self.normalChars)
                continue
            # block is inside if the line
            if r1 == r2:
                self.drawText(dc, line[X:],   x+sl, y+st, self.normalChars)
                self.drawText(dc, line[X:c2], x+sl, y+st, self.selectedChars)
                self.drawText(dc, line[X:c1], x+sl, y+st, self.normalChars)
                continue
            # line at the start of the block
            if y+Y == r1:
                self.drawText(dc, line[X:]+'¶',   x+sl, y+st, self.selectedChars)
                self.drawText(dc, line[X:c1], x+sl, y+st, self.normalChars)
                continue
            # line at the end of the block
            if y+Y == r2:
                self.drawText(dc, line[X:],   x+sl, y+st, self.normalChars)
                self.drawText(dc, line[X:c2], x+sl, y+st, self.selectedChars)
                continue
            # line is inside the block
            self.drawText(dc, line[X:]+'¶', x+sl, y+st, self.selectedChars)

        # draw cursor
        x, y = self.cursor
        x1, y1, y2 = (x+sl)*cw, (y+st)*ch, (y+st+1)*ch-1
        dc.SetPen(self.pens['cursor'])
        dc.DrawLine(x1, y1, x1, y2)
        # done
        dc.SelectObject(NullBitmap)
        return
예제 #7
0
 def createCharBitmaps(self):
     # create device context
     dc = MemoryDC()               
     # set font
     dc.SetFont(self.fonts['monospace'])
     # get font size (monospace)
     cw, ch = dc.GetTextExtent(' ')
     # record geometry
     self.charSize = cw, ch
     # NORMAL CHARACTERS
     self.normalChars = {}
     # create bitmaps
     for c in self.charSet:
         # create black and white bitmap
         bitmap = Bitmap(cw, ch, BITMAP_SCREEN_DEPTH)
         # select bitmap
         dc.SelectObject(bitmap)
         # set background
         dc.SetBackground(self.brushes['bkgd'])
         # clear bitmap
         dc.Clear()
         # set text color
         dc.SetTextForeground(Colour(230,230,230))
         # write character
         dc.DrawText(c,0,0)
         # create reference to bitmap
         self.normalChars[c] = bitmap
     # SELECTED CHARACTERS
     self.selectedChars = {}
     # create bitmaps
     for c in self.charSet:
         # create black and white bitmap
         bitmap = Bitmap(cw, ch, BITMAP_SCREEN_DEPTH)
         # select bitmap
         dc.SelectObject(bitmap)
         # set background
         dc.SetBackground(Brush(Colour(70, 80, 90)))
         # clear bitmap
         dc.Clear()
         # set text color
         dc.SetTextForeground(Colour(160,160,180))
         # write character
         dc.DrawText(c,0,0)
         # create reference to bitmap
         self.selectedChars[c] = bitmap
     # NUMBER CHARACTERS
     self.numberChars = {}
     # create bitmaps
     for c in ' 0123456789':
         # create black and white bitmap
         bitmap = Bitmap(cw, ch, BITMAP_SCREEN_DEPTH)
         # select bitmap
         dc.SelectObject(bitmap)
         # set background
         dc.SetBackground(self.brushes['bkgd'])
         # clear bitmap
         dc.Clear()
         # set text color
         dc.SetTextForeground(Colour(150,150,200))
         # write character
         dc.DrawText(c,0,0)
         # create reference to bitmap
         self.numberChars[c] = bitmap
     # release bitmap
     dc.SelectObject(NullBitmap)
     # done
     return
예제 #8
0
    def OnRefreshPanel(self, event):
        """
        Refresh dialog box

        """
        import time
        # constants
        backRed: int = 230
        backGreen: int = 255
        backBlue: int = 230  # Background color

        frontRed: int = 64
        frontGreen: int = 0
        frontBlue: int = 64  # Foreground color

        FADE_IN_LENGTH: int = 63
        self.logger.debug(f'Enter OnRefreshPanel')
        # Init memory buffer
        tdc: MemoryDC = MemoryDC()
        tdc.SelectObject(Bitmap(FrameWidth, FrameHeight))
        while not tdc.IsOk():
            time.sleep(0.05)
            tdc = MemoryDC()
            tdc.SelectObject(Bitmap(FrameWidth, FrameHeight))

        tdc.SetTextForeground(Colour(frontRed, frontGreen, frontBlue))
        tdc.SetBackground(
            Brush(Colour(backRed, backGreen, backBlue), BRUSHSTYLE_SOLID))
        tdc.Clear()
        font = tdc.GetFont()
        font.SetFamily(FONTFAMILY_MODERN)
        font.SetPointSize(12)
        tdc.SetFont(font)

        # Fade-in
        position = self.textPosition
        if position < FADE_IN_LENGTH:
            n = float(position) / float(FADE_IN_LENGTH)
            r = backRed - n * (backRed - frontRed)
            g = backGreen - n * (backGreen - frontGreen)
            b = backBlue - n * (backBlue - frontBlue)
            r = int(r)
            g = int(g)
            b = int(b)
            tdc.SetTextForeground(Colour(r, g, b))

        # Display text
        for j in range(1, len(self._textToShow)):
            # Draw text ?
            if position > FADE_IN_LENGTH:
                y = y0 + j * dy - (position - FADE_IN_LENGTH)
                if -dy < y < FrameHeight:
                    tdc.DrawText(self._textToShow[j], x0, y)
            else:  # Draw initial screen with fade in
                y = y0 + j * dy
                if -dy < y < FrameHeight:
                    tdc.DrawText(self._textToShow[j], x0, y)

        # Show memory dc to current dc (blit)
        dc = PaintDC(self._panel)
        dc.Blit(0, 0, FrameWidth, FrameHeight, tdc, 0, 0)
        tdc.SelectObject(NullBitmap)
        self.logger.debug(f'Exit OnRefreshPanel')
예제 #9
0
    def refreshBuffer(self):
        # get geometry
        X, Y = self.scroll
        cw, ch = self.charSize
        sl, sr, st, sb = 5, 15, 5, 5
        cl, rw = self.screenSize
        # create device context
        dc = MemoryDC()
        # select
        dc.SelectObject(self.bitmapBuffer)
        # set background
        dc.SetBackground(self.brushes['bkgd'])
        # and clear buffer
        dc.Clear()
        # draw margins
        x3, x4 = 0 * cw, cl * cw
        x1, y1 = (sl - 0.5) * cw, (st - 0.5) * ch
        x2, y2 = (cl - sr + 0.5) * cw, (rw - sb + 0.5) * ch
        dc.SetPen(self.pens['margin'])
        dc.DrawLine(x1, y1, x1, y2)
        dc.DrawLine(x2, y1, x2, y2)
        dc.DrawLine(x3, y1, x4, y1)
        dc.DrawLine(x3, y2, x4, y2)

        # get block geometry
        r1, c1 = self.blockBegin
        r2, c2 = self.blockEnd
        block = False

        # draw text
        x, y = 0, 0
        for line in self.characterBuffer[Y:]:
            # draw line number
            self.drawText(dc, f'{y+Y:4d}', 0, y + st, self.numberChars)
            # the line is outside of the block
            if y + Y < r1 or y + Y > r2:
                self.drawText(dc, line[X:], x + sl, y + st, self.normalChars)
                # done
                y += 1
                if y + st >= rw - sb: break
                continue
            # block is inside if the line
            if r1 == r2:
                # draw normal text
                # self.drawText(dc, line[X:], x+sl, y+st, self.normalChars)
                # # block has size zero
                # if c1 == c2:
                #     # done
                #     y += 1
                #     if y+st >= rw-sb: break
                #     continue
                # block has finite size
                self.drawText(dc, line[X:], x + sl, y + st, self.normalChars)
                self.drawText(dc, line[X:c2], x + sl, y + st,
                              self.selectedChars)
                self.drawText(dc, line[X:c1], x + sl, y + st, self.normalChars)
                # done
                y += 1
                if y + st >= rw - sb: break
                continue
            # line at the start of the block
            if y + Y == r1:
                self.drawText(dc, line[X:], x + sl, y + st, self.selectedChars)
                self.drawText(dc, line[X:c1], x + sl, y + st, self.normalChars)
                # done
                y += 1
                if y + st >= rw - sb: break
                continue
            # line at the end of the block
            if y + Y == r2:
                self.drawText(dc, line[X:], x + sl, y + st, self.normalChars)
                self.drawText(dc, line[X:c2], x + sl, y + st,
                              self.selectedChars)
                # done
                y += 1
                if y + st >= rw - sb: break
                continue
            # line in the middle of the block
            self.drawText(dc, line[X:], x + sl, y + st, self.selectedChars)
            y += 1
            if y + st >= rw - sb: break

        # draw cursor
        x, y = self.cursor
        x1, y1, y2 = (x + sl) * cw, (y + st) * ch, (y + st + 1) * ch - 1
        dc.SetPen(self.pens['cursor'])
        dc.DrawLine(x1, y1, x1, y2)

        # done
        dc.SelectObject(NullBitmap)
        return
예제 #10
0
    def Start(self):

        # define colors
        self.colours = {}
        self.colours['bkgd'] = Colour(52, 61, 70)
        self.colours['text'] = Colour(255, 255, 255)
        self.colours['numb'] = Colour(160, 160, 160)
        self.colours['sepr'] = Colour(200, 120, 120)
        self.colours['selbgd'] = Colour(92, 101, 110)
        self.colours['seltxt'] = Colour(255, 255, 255)

        # brushes
        self.brushes = {}
        # normal background
        b = Brush()
        b.SetStyle(BRUSHSTYLE_SOLID)
        b.SetColour(self.colours['bkgd'])
        self.brushes['bkgd'] = b

        # selected background
        b = Brush()
        b.SetStyle(BRUSHSTYLE_SOLID)
        b.SetColour(self.colours['selbgd'])
        self.brushes['selbgd'] = b

        # pens
        self.pens = {}
        p = Pen()
        p.SetColour(self.colours['sepr'])
        p.SetWidth(1)
        p.SetStyle(PENSTYLE_SOLID)
        self.pens['sepr'] = p

        # create device context to work on bitmaps
        dc = MemoryDC()

        # define font
        self.fonts = {}
        f = Font()
        f.SetFamily(FONTFAMILY_ROMAN)
        f.SetFaceName("Monospace")
        f.SetEncoding(FONTENCODING_DEFAULT)
        f.SetStyle(FONTSTYLE_NORMAL)
        f.SetWeight(FONTWEIGHT_NORMAL)
        f.SetUnderlined(False)
        f.SetPointSize(10)
        self.fonts['monospace'] = f

        # select font
        f = 'monospace'

        # get font size
        dc.SetFont(self.fonts[f])
        w, h = dc.GetTextExtent(' ')

        # build character dictionary
        self.rawChars = {}
        # standard printable ascii table
        for c in range(32, 126):
            self.rawChars[chr(c)] = None
        # extra characters (UK)
        self.rawChars['£'] = None
        # save character size
        self.rawChars['Size'] = w, h

        # set background color
        dc.SetBackground(self.brushes['bkgd'])

        # build characters bitmaps
        for C in self.rawChars.keys():
            if C == 'Size': continue
            # instanciate character bitmap
            self.rawChars[C] = Bitmap(w, h, BITMAP_SCREEN_DEPTH)
            # select bitmap
            dc.SelectObject(self.rawChars[C])
            # clear bitmap
            dc.Clear()
            # set text color
            dc.SetTextForeground(self.colours['text'])
            # draw character shape and color
            dc.DrawText(C, 0, 0)
        # done
        dc.SelectObject(NullBitmap)

        # build selected dictionary
        self.selChars = {}
        # standard printable ascii table
        for c in range(32, 126):
            self.selChars[chr(c)] = None
        # extra characters (UK)
        self.selChars['£'] = None
        # save character size
        self.selChars['Size'] = w, h

        # set background color
        dc.SetBackground(self.brushes['selbgd'])

        # build selected bitmaps
        for C in self.selChars.keys():
            if C == 'Size': continue
            # instanciate character bitmap
            self.selChars[C] = Bitmap(w, h, BITMAP_SCREEN_DEPTH)
            # select bitmap
            dc.SelectObject(self.selChars[C])
            # clear bitmap
            dc.Clear()
            # set text color
            dc.SetTextForeground(self.colours['seltxt'])
            # draw character shape and color
            dc.DrawText(C, 0, 0)
        # done
        dc.SelectObject(NullBitmap)

        # create bitmap buffer display and clear buffer
        self.bitmapBuffer = Bitmap(
            128 * w,  # bitmap width
            10 * h,  # bitmap height
            BITMAP_SCREEN_DEPTH)  # bitmap depth
        dc.SelectObject(self.bitmapBuffer)
        dc.SetBackground(self.brushes['bkgd'])
        dc.Clear()
        dc.SelectObject(NullBitmap)

        # reference BackgroundBitmap to bitmapBuffer
        self.Panel.BackgroundBitmap = self.bitmapBuffer

        # adjust frame position
        scrw, scrh = 3840, 1200  # screen size
        w, h = self.Panel.BackgroundBitmap.GetSize()
        self.Frame.SetPosition(Point(scrw / 4 - 128 * 8 / 2, 800))

        # draw text string
        dc.SelectObject(self.bitmapBuffer)

        t = list("hello")
        # select buffer
        # draw text
        X = 0
        w, h = self.rawChars['Size']
        for c in t:
            dc.DrawBitmap(self.rawChars[c], X, 0)
            X += w

        t = list("World!")
        # select buffer
        # draw text
        X = 0
        w, h = self.selChars['Size']
        for c in t:
            dc.DrawBitmap(self.selChars[c], X, h)
            X += w

        # release bitmap
        dc.SelectObject(NullBitmap)

        # done
        return
예제 #11
0
 def OnDrawItem(self, dc, rect, item, flags):
     idx = self.GetClientData(item)
     dc_img = MemoryDC()
     dc_img.SelectObject(self.bitmaps[idx])
     w, h = self.bitmaps[idx].GetWidth(), self.bitmaps[idx].GetHeight()
     dc.Blit(rect.left, rect.top, w, h, dc_img, 0, 0, logicalFunc=wx.AND)
예제 #12
0
class RowScroller(wx.Window):
    """ A VScrolledWindow that allows for pixel size scrolling
    """
    def __init__(self,
                 datamodel,
                 parent,
                 id=wx.ID_ANY,
                 label="",
                 pos=wx.DefaultPosition,
                 size=wx.DefaultSize,
                 style=wx.NO_BORDER):
        super().__init__(parent)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_CHAR, self.__OnChar)
        self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
        self.line_size = 16  # Scroll this many pixels each time

        self.pixels_hidden_first_row = 0
        self.pixels_hidden_last_row = 0
        self.client_height = 0
        self.fixed_row = None
        self.SetBackgroundStyle(wx.BG_STYLE_PAINT)
        self.current_pos = 0
        self.margin = 8
        #above this, use scroll directly without retrieving all intermediary rows
        self.scroll_row_threshold = 500
        self.datamodel = datamodel
        self.datamodel.INSERTED.subscribe(self.OnInserted)
        self.datamodel.DELETED.subscribe(self.OnRemoved)
        self.datamodel.MODIFIED.subscribe(self.OnModified)

        # New
        self.displayed_rows = deque()
        self.heights = RowHeigths()

    def SetFixedRow(self, rowpos):
        """ The current row is the row that stays fixed when rows are inserted, or removed"""
        self.fixed_row = rowpos
        self.PaintRect(self.GetClientRect())

    def GetFixedRow(self):
        return first(self.displayed_rows, lambda e: e.rowpos == self.fixed_row)

    def GetRowCached(self, row_id):
        result = self.datamodel.Get(row_id)
        self.heights.add(row_id, result.height)
        return result

    def ReindexDisplayedRows(self, reindex_func):
        if self.fixed_row is not None:
            self.fixed_row = reindex_func(self.fixed_row)
        for d in self.displayed_rows:
            d.rowpos = reindex_func(d.rowpos)
        self.heights.reindex(reindex_func)

    def OnInserted(self, insert_pos, reindex_func):
        self.ReindexDisplayedRows(reindex_func)
        fixed_row = self.GetFixedRow()
        if self.fixed_row:
            self.ScrollToLayout(self.fixed_row, fixed_row.y)
        else:
            self.ScrollToLayout(self.displayed_rows[0].rowpos,
                                self.displayed_rows[0].y)
        self.PaintRect(self.GetClientRect())
        wx.PostEvent(self, RowScrollerDisplayChanged())

    def OnRemoved(self, pos, reindex_func):
        if self.fixed_row == pos:
            self.fixed_row = None
        fixed_row = self.GetFixedRow()
        self.ReindexDisplayedRows(reindex_func)
        if self.fixed_row:
            self.ScrollToLayout(self.fixed_row, fixed_row.y)
        else:
            self.ScrollToLayout(self.displayed_rows[0].rowpos,
                                self.displayed_rows[0].y)
        self.PaintRect(self.GetClientRect())

    def OnModified(self, rowpos):
        disprow = first(self.displayed_rows, lambda e: e.rowpos == rowpos)
        if not disprow:
            return
        row = self.GetRowCached(rowpos)
        if row.height != disprow.height():
            fixed_row = self.GetFixedRow()
            if self.fixed_row:
                self.ScrollToLayout(self.fixed_row, fixed_row.y)
            else:
                self.ScrollToLayout(self.displayed_rows[0].rowpos,
                                    self.displayed_rows[0].y)
            self.PaintRect(self.GetClientRect())
        else:
            self.RepaintRow(rowpos)
            self.BlitToScreen(self.GetLayoutRect(rowpos))

    def GetLayoutRect(self, rowpos):
        disprow = first(self.displayed_rows, lambda e: e.rowpos == rowpos)
        result = wx.Rect(0, disprow.y, self.client_width, disprow.end_y)

    def PaintRow(self, dc, row, start_y, end_y):
        dc.SetClippingRegion(
            wx.Rect(
                (self.margin, start_y, self.client_width, end_y - start_y)))
        dc.Clear()
        row.Paint(dc, self.margin, start_y)
        dc.DestroyClippingRegion()

    def RepaintRow(self, rowpos):
        d = first(self.displayed_rows, lambda e: e.rowpos == rowpos)
        #rowpos, row, start_y, next_y = self.displayed_rows(rowpos)
        self.dc_back.SetClippingRegion(
            wx.Rect(self.margin, d.y, self.client_width, d.height()))
        self.dc_back.Clear()
        d.row.Paint(self.dc_back, self.margin, d.y)
        self.dc_back.DestroyClippingRegion()

    def BlitToScreen(self, rect):
        # TODO: Remove this..
        rect = self.GetClientRect()
        dc = wx.ClientDC(self)
        dc.Blit(rect.x, rect.y, rect.width, rect.height, self.dc_back, rect.x,
                rect.y)

    def PaintRect(self, rect, refresh=True):
        self.dc_back.SetClippingRegion(rect)
        self.dc_back.Clear()
        for displayed_row in self.displayed_rows:
            if displayed_row.y + displayed_row.row.height >= rect.top:
                self.PaintRow(self.dc_back, displayed_row.row, displayed_row.y,
                              displayed_row.y + displayed_row.row.height)
            if displayed_row.y >= rect.bottom:
                break
            if self.fixed_row is not None:
                self.dc_back.SetBrush(wx.TRANSPARENT_BRUSH)
                self.dc_back.SetPen(wx.BLACK_PEN)
                fixed_row = first(self.displayed_rows,
                                  lambda e: e.rowpos == self.fixed_row)
                if fixed_row:
                    self.dc_back.DrawRectangle(
                        1, fixed_row.y + 1, self.client_width - 2,
                        fixed_row.end_y - fixed_row.y - 2)
        self.BlitToScreen(rect)
        self.dc_back.DestroyClippingRegion()

    def ScrollBackBufferRow(self, start_y, end_y, distance):
        self.dc_back.Blit(0, start_y + distance, self.client_width,
                          end_y - start_y, self.dc_back, 0, start_y)

    def ScrollBackBuffer(self, distance):
        # do we need another image (e.g. test Blit for overlapping regions)
        self.dc_back.Blit(0, distance, self.client_width, self.inner_height,
                          self.dc_back, 0, 0)

    def HitTest(self, x, y):
        x -= self.margin
        for displayed_row in self.displayed_rows:
            if displayed_row.y <= y <= displayed_row.end_y:
                return (displayed_row.rowpos, ) + displayed_row.row.HitTest(
                    x, y - displayed_row.y)

    def OnSize(self, event):
        #print ("OnSize", event)
        self.client_width, self.client_height = self.GetClientSize()
        self.inner_height = self.client_height
        self.BackBuffer = wx.Bitmap(self.client_width, self.client_height)
        self.dc_back = MemoryDC()
        self.dc_back.SelectObject(self.BackBuffer)
        #self.ResetHeightEstimate()
        # Estimate Height
        max_idx = self.datamodel.GetApproximateCount()
        if max_idx < 50:
            rowsample = range(max_idx)
        else:
            rowsample = set([
                self.datamodel.GetApproximatePos(random.randrange(0, max_idx))
                for _ in range(50)
            ])
        for pos in rowsample:
            self.heights.add(pos, self.datamodel.Get(pos).height)

        if self.displayed_rows:
            rowpos, y = self.displayed_rows[0].rowpos, self.displayed_rows[0].y
        else:
            rowpos, y = self.datamodel.GetFirstPos(), 0
        self.ScrollToLayout(rowpos, y)
        self.PaintRect(self.GetClientRect(), refresh=True)
        event.Skip()

    def GetScrollPosition(self):
        return self.current_pos

    def GetInnerHeight(self):
        return self.inner_height

    def RefreshScrollBar(self):
        # idx = self.GetApproximateIndex(self.layout_rows[0][0])
        # This line was removed as it was unused. Would we need this?
        self.SetScrollbar(wx.VERTICAL,
                          self.current_pos,
                          self.inner_height,
                          self.heights.estimate() *
                          self.datamodel.GetApproximateCount(),
                          refresh=True)

    def Display(self, displayed_row, position):
        #print ("Displaying", displayed_row)
        self.displayed_rows.insert(position, displayed_row)

    def Hide(self, top=True):
        if top:
            result = self.displayed_rows.popleft()
        else:
            result = self.displayed_rows.pop()
        #print ("Hiding", result)

    def FillRemaingRows(self):
        if self.displayed_rows:
            # Expand bottom
            displayed_row = self.displayed_rows[-1]
            y = displayed_row.y
            rowpos = self.datamodel.GetNextPos(displayed_row.rowpos)
            while y + displayed_row.row.height < self.inner_height and rowpos is not None:
                row = self.GetRowCached(rowpos)
                y += displayed_row.row.height
                displayed_row = DisplayedRow(y, y + row.height, rowpos, row)
                self.Display(displayed_row, len(self.displayed_rows))
                rowpos = self.datamodel.GetNextPos(displayed_row.rowpos)
            # Expand top
            displayed_row = self.displayed_rows[0]
            y = displayed_row.y
            rowpos = self.datamodel.GetPrevPos(displayed_row.rowpos)
            while y > 0 and rowpos is not None:

                row = self.GetRowCached(rowpos)
                y -= row.height
                self.Display(DisplayedRow(y, y + row.height, rowpos, row), 0)
                rowpos = self.datamodel.GetPrevPos(rowpos)

    def ClearExtraRows(self):
        # Remove Top
        while self.displayed_rows and self.displayed_rows[
                0].y + self.displayed_rows[0].row.height <= 0:
            self.Hide(True)
        # Remove Bottom
        while self.displayed_rows and self.displayed_rows[
                -1].y >= self.inner_height:
            self.Hide(False)

    def ScrollToLayout(self, rowpos, start_px):
        """  Absolute scroll rows such that the row 'rowpos' is now at 'start_px' pixels
        from the top of the window (start_px can be negative).
        """
        y = start_px
        self.displayed_rows = deque()
        if rowpos is not None:
            row = self.GetRowCached(rowpos)
            self.displayed_rows.append(
                DisplayedRow(y, y + row.height, rowpos, row))
        self.FillRemaingRows()
        self.RefreshScrollBar()

    def _ScrollNoRefresh(self, distance):
        ''' Relative Scroll distance in pixels (negative is up) '''
        if distance == 0:
            return 0
        if distance > 0:
            distance_moved = self.MoveDownBottom(distance)
            self.MoveDownTop(distance_moved)
        else:
            distance_moved = self.MoveUpTop(-distance)
            self.MoveUpBottom(distance_moved)
        distance_moved = math.copysign(distance_moved, distance)
        self.current_pos = max(
            min(self.current_pos + distance_moved, self.estimated_height), 0)
        return distance_moved

    def _RefreshAfterScrolling(self, distance):
        ''' Refresh after scrolling distance pixels (negative is up) '''
        if abs(distance) < self.inner_height:
            if distance > 0:
                paint_rect = wx.Rect(0, self.inner_height - distance,
                                     self.client_width, self.client_height)
            else:
                paint_rect = wx.Rect(0, 0, self.client_width, -distance)
            self.ScrollBackBuffer(-distance)
            self.ScrollWindow(0, -distance)
        else:
            paint_rect = wx.Rect(0, 0, self.client_width, self.inner_height)
        self.PaintRect(paint_rect, refresh=True)
        self.RefreshScrollBar()

    def _ScrollUntil(self, distance_increment, fct):
        ''' Relative Scroll distance_increment in pixels until fct returns True.
            Then, refresh the screen.
            This can be used for "Scroll until cursor is in view"
        '''
        distance_moved = 0
        while not fct:
            distance_moved += self._ScrollNoRefresh(distance_increment)

    def ScrollIntoViewXY(self, row_id, x, y):
        return self.ScrollIntoRectView(row_id, wx.Rect(x, y, 0, 0))

    def ScrollIntoRectView(self, row_id, rect):
        # FIXME
        print("ScrollIntoRectView")
        first, last = self.layout_row_ids[0], self.layout_row_ids[-1]
        if row_id > last:
            #self.ScrollIntoViewXY2(row_id, rect.left, rect.bottom)
            self.ScrollToLayout(row_id, self.inner_height - 1 - rect.bottom)
            self.PaintRect(self.GetClientRect(), refresh=True)
            self.RefreshScrollBar()

        elif row_id < first:
            self.ScrollToLayout(row_id, -rect.top)
            self.PaintRect(self.GetClientRect(), refresh=True)
            self.RefreshScrollBar()
            #self.ScrollIntoViewXY2(row_id, rect.left, rect.top)
        elif row_id == last:
            missing = max(rect.bottom - self.PixelsVisibleLastRow(), 0)
            distance_moved = self.Scroll(missing)
        elif row_id == first:
            missing = max(self.PixelsHiddenFirstRow() - rect.top, 0)
            distance_moved = self.Scroll(-missing)

    def _ScrollRows(self, distance):
        for displayed_row in self.displayed_rows:
            displayed_row.y -= distance
            displayed_row.end_y -= distance

    def Scroll(self, distance, bounded_scolling=True):
        if not self.displayed_rows:
            return
        self._ScrollRows(distance)
        self.FillRemaingRows()
        scrolled_pixels = distance
        # Now we might have scrolled too far
        if bounded_scolling:
            if distance < 0:
                if self.displayed_rows[0].y > 0:
                    dist = self.displayed_rows[0].y
                    self._ScrollRows(dist)
                    scrolled_pixels += dist
            elif distance > 0:
                if self.displayed_rows[
                        -1].y < self.inner_height - self.displayed_rows[
                            -1].row.height:
                    dist = self.displayed_rows[-1].y - (
                        self.inner_height - self.displayed_rows[-1].row.height)
                    self._ScrollRows(dist)
                    scrolled_pixels += dist
            self.FillRemaingRows()
        self.ClearExtraRows()
        self.current_pos += scrolled_pixels
        paint_rect = wx.Rect(0, 0, self.client_width, self.inner_height)
        self.PaintRect(paint_rect, refresh=True)
        self.RefreshScrollBar()
        wx.PostEvent(self, RowScrollerScrolledEvent())

    def ScrollTo(self, pos):
        '''Absolute scroll pos in pixels'''
        # pos varies between 0 and self.estimated_height - self.inner_height
        if abs(pos - self.current_pos) < 1000:
            # Avoid flickering, when scrolling small distances by doing a relative scroll
            return self.Scroll(pos - self.current_pos)
        else:
            estimated_row_height = self.heights.estimate()
            rowpos = self.datamodel.GetApproximatePos(
                pos // int(estimated_row_height))
            hidden_first_row = pos % int(estimated_row_height)
            self.ScrollToLayout(rowpos, hidden_first_row)
        self.PaintRect(self.GetClientRect(), refresh=True)
        self.current_pos = pos
        self.RefreshScrollBar()
        wx.PostEvent(self, RowScrollerScrolledEvent())

    def OnScroll(self, event):
        event_type = event.GetEventType()
        if event_type == wx.EVT_SCROLLWIN_PAGEDOWN.typeId:
            pos = event.GetPosition()
            self.Scroll(self.inner_height)
        elif event_type == wx.EVT_SCROLLWIN_PAGEUP.typeId:
            self.Scroll(-self.inner_height)
        elif event_type == wx.EVT_SCROLLWIN_THUMBTRACK.typeId:
            pos = event.GetPosition()
            self.ScrollTo(pos)
        elif event_type == wx.EVT_SCROLLWIN_THUMBRELEASE.typeId:
            pos = event.GetPosition()
            self.ScrollTo(pos)

    def OnMouseWheel(self, event):
        rotation = event.GetWheelRotation()
        delta = event.GetWheelDelta()
        self.Scroll(-self.line_size * (rotation / delta))

    def __OnChar(self, event):
        key_code = event.GetKeyCode()
        if key_code == WXK_PAGEDOWN:
            self.Scroll(self.inner_height)
        elif key_code == WXK_PAGEUP:
            self.Scroll(-self.inner_height)
        elif key_code == WXK_UP:
            self.Scroll(-self.line_size)
        elif key_code == WXK_DOWN:
            self.Scroll(self.line_size)
        event.Skip()