from ViewMode import *
from cemu import *
import TextSelection
from TextDecorators import *
import string
import PyQt5
from PyQt5 import QtGui, QtCore, QtWidgets

class HexViewMode(ViewMode):
    def __init__(self, width, height, data, cursor, widget=None, plugin=None):
        super(HexViewMode, self).__init__()

        self.dataModel = data
        self.width = width
        self.height = height

        self.refresh = True
        self.selector = TextSelection.HexSelection(self)
        self.widget = widget

        self.addHandler(self.dataModel)

        # background brush
        self.backgroundBrush = QtGui.QBrush(QtGui.QColor(0, 0, 128))

        # text font
        self.font = QtGui.QFont('Terminus', 11, QtGui.QFont.Light)

        # font metrics. assume font is monospaced
        self.font.setKerning(False)
        self.font.setFixedPitch(True)
        fm = QtGui.QFontMetrics(self.font)
        self._fontWidth  = fm.width('a')
        self._fontHeight = fm.height()

        self.Special = string.ascii_letters + string.digits + ' .;\':;=\"?-!()/\\_'

        self.textPen = QtGui.QPen(QtGui.QColor(192, 192, 192), 1, QtCore.Qt.SolidLine)

        self.cursor = cursor

        self.HexColumns = [1, 4, 8, 16, 32, 36, 40]
        self.idxHexColumns = 3 # 32 columns

        self.newPix = None
        self.Ops = []
        self.gap = 5
        self.plugin = plugin

        self.highpart = True
        self.resize(width, height)

        self.ann_w = Annotation(self.widget, self)

    @property
    def fontWidth(self):
        return self._fontWidth

    @property
    def fontHeight(self):
        return self._fontHeight


    def setTransformationEngine(self, engine):
        self.transformationEngine = engine
        self.original_textdecorator = engine

    def _getNewPixmap(self, width, height):
        return QtGui.QPixmap(width, height)

    def getPixmap(self):
        #return self.qpix

        for t in self.Ops:
            if len(t) == 1:
                t[0]()

            else:
                t[0](*t[1:])

        self.Ops = []

        if not self.newPix:
            self.draw()

        return self.newPix

    def getGeometry(self):
        return self.COLUMNS, self.ROWS

    def getColumnsbyRow(self, row):
        return self.COLUMNS

    def getDataModel(self):
        return self.dataModel

    def startSelection(self):
        self.selector.startSelection()

    def stopSelection(self):
        self.selector.stopSelection()

    def getPageOffset(self):
        return self.dataModel.getOffset()

    def getCursorAbsolutePosition(self):
        x, y = self.cursor.getPosition()
        return self.dataModel.getOffset() + y*self.COLUMNS + x

    def computeTextArea(self):
        self.COLUMNS = self.HexColumns[self.idxHexColumns]
        self.CON_COLUMNS = self.width//self.fontWidth
        self.ROWS = self.height//self.fontHeight
        self.notify(self.ROWS, self.COLUMNS)

    def resize(self, width, height):
        self.width = width - width%self.fontWidth
        self.height = height - height%self.fontHeight
        self.computeTextArea()
        self.qpix = self._getNewPixmap(self.width, self.height + self.SPACER)
        self.refresh = True

    def changeHexColumns(self):
        if self.idxHexColumns == len(self.HexColumns) - 1:
            self.idxHexColumns = 0
        else:
            self.idxHexColumns += 1

        # if screen is ont big enough, retry
        if self.HexColumns[self.idxHexColumns]*(3+1) + self.gap >= self.CON_COLUMNS:
            self.changeHexColumns()
            return

        self.resize(self.width, self.height)


    def scroll(self, dx, dy):
        if dx != 0:
            if self.dataModel.inLimits((self.dataModel.getOffset() - dx)):
                self.dataModel.slide(-dx)
                self.scroll_h(dx)

        if dy != 0:
            if self.dataModel.inLimits((self.dataModel.getOffset() - dy*self.COLUMNS)):
                self.dataModel.slide(-dy*self.COLUMNS)
                self.scroll_v(dy)
            else:
                if dy <= 0:
                    pass
                    #self.dataModel.slideToLastPage()
                else:
                    self.dataModel.slideToFirstPage()
                self.draw(refresh=True)

        self.draw()

    def scrollPages(self, number):
        self.scroll(0, -number*self.ROWS)

    def drawAdditionals(self):
        self.newPix = self._getNewPixmap(self.width, self.height + self.SPACER)
        qp = QtGui.QPainter()
        qp.begin(self.newPix)
        qp.drawPixmap(0, 0, self.qpix)

        #self.transformationEngine.decorateText()

        # highlight selected text
        self.selector.highlightText()

        # draw other selections
        self.selector.drawSelections(qp)

        # draw our cursor
        self.drawCursor(qp)

        # draw dword lines
        for i in range(self.COLUMNS//4)[1:]:
            xw = i*4*3*self.fontWidth - 4
            qp.setPen(QtGui.QColor(0, 255, 0))
            qp.drawLine(xw, 0, xw, self.ROWS*self.fontHeight)


        qp.end()


    def scroll_h(self, dx):
        gap = self.gap

        # hex part
        self.qpix.scroll(dx*3*self.fontWidth, 0, QtCore.QRect(0, 0, self.COLUMNS*3*self.fontWidth, self.ROWS*self.fontHeight + self.SPACER))
        # text part
        self.qpix.scroll(dx*self.fontWidth, 0, QtCore.QRect((self.COLUMNS*3 + gap)*self.fontWidth , 0, self.COLUMNS*self.fontWidth, self.ROWS*self.fontHeight + self.SPACER))

        qp = QtGui.QPainter()

        qp.begin(self.qpix)
        qp.setFont(self.font)
        qp.setPen(self.textPen)

        factor = abs(dx)

        # There are some trails from the characters, when scrolling. trail == number of pixel to erase near the character
        trail = 5

        textBegining = self.COLUMNS*3 + gap
        if dx < 0:
            # hex
            qp.fillRect((self.COLUMNS - 1*factor)*3*self.fontWidth, 0, factor * self.fontWidth * 3, self.ROWS*self.fontHeight + self.SPACER, self.backgroundBrush)
            # text
            qp.fillRect((textBegining + self.COLUMNS - 1*factor)*self.fontWidth, 0, factor * self.fontWidth+trail, self.ROWS*self.fontHeight + self.SPACER, self.backgroundBrush)
        if dx > 0:
            # hex
            qp.fillRect(0, 0, factor * 3 * self.fontWidth, self.ROWS*self.fontHeight + self.SPACER, self.backgroundBrush)
            # text
            qp.fillRect(textBegining*self.fontWidth - trail, 0, factor * self.fontWidth + trail, self.ROWS*self.fontHeight + self.SPACER, self.backgroundBrush)

        cemu = ConsoleEmulator(qp, self.ROWS, self.CON_COLUMNS)

        page = self.transformationEngine.decorate()
        # scriem pe fiecare coloana in parte
        for column in range(factor):
            # fiecare caracter de pe coloana
            for i in range(self.ROWS):

                if dx < 0:
                    # cu (column) selectam coloana
                    idx = (i+1)*(self.COLUMNS) - (column + 1)
                if dx > 0:
                    idx = (i)*(self.COLUMNS) + (column)

                if len(self.getDisplayablePage()) > idx:
                    qp.setPen(self.transformationEngine.choosePen(idx))
                else:
                    break

                if self.transformationEngine.chooseBrush(idx) != None:
                    qp.setBackgroundMode(1)
                    qp.setBackground(self.transformationEngine.chooseBrush(idx))

                c = self.getDisplayablePage()[idx]

                hex_s = str(hex(c)[2:]).zfill(2) + ' '

                if dx < 0:
                    cemu.writeAt((self.COLUMNS - (column + 1))*3, i, hex_s, noBackgroudOnSpaces=True)
                    cemu.writeAt(textBegining + self.COLUMNS - (column + 1), i, self.cp437(c))

                if dx > 0:
                    cemu.writeAt((column)*3, i, hex_s, noBackgroudOnSpaces=True)
                    cemu.writeAt(textBegining + column, i, self.cp437(c))

                qp.setBackgroundMode(0)

        qp.end()

    def scroll_v(self, dy):
        self.qpix.scroll(0, dy*self.fontHeight, self.qpix.rect())

        qp = QtGui.QPainter()

        qp.begin(self.qpix)
        qp.setFont(self.font)
        qp.setPen(self.textPen)

        factor = abs(dy)

        cemu = ConsoleEmulator(qp, self.ROWS, self.CON_COLUMNS)

        if dy < 0:
            cemu.gotoXY(0, self.ROWS - factor)
            qp.fillRect(0, (self.ROWS-factor)*self.fontHeight, self.fontWidth*self.CON_COLUMNS, factor * self.fontHeight + self.SPACER, self.backgroundBrush)

        if dy > 0:
            cemu.gotoXY(0, 0)
            qp.fillRect(0, 0, self.fontWidth*self.CON_COLUMNS, factor * self.fontHeight, self.backgroundBrush)


        page = self.transformationEngine.decorate()

        # how many rows
        for row in range(factor):
            # for every column
            for i in range(self.COLUMNS):

                if dy < 0:
                    # we write from top-down, so get index of the first row that will be displayed
                    # this is why we have factor - row
                    idx = i + (self.ROWS - (factor - row))*self.COLUMNS
                if dy > 0:
                    idx = i + (self.COLUMNS*row)

                qp.setPen(self.transformationEngine.choosePen(idx))

                if self.transformationEngine.chooseBrush(idx) != None:
                    qp.setBackgroundMode(1)
                    qp.setBackground(self.transformationEngine.chooseBrush(idx))

                if len(self.getDisplayablePage()) > idx:
                    c = self.getDisplayablePage()[idx]
                else:
                    break

                if i == self.COLUMNS - 1:
                    hex_s = str(hex(c)[2:]).zfill(2)
                else:
                    hex_s = str(hex(c)[2:]).zfill(2) + ' '

                # write hex representation
                cemu.write(hex_s, noBackgroudOnSpaces=True)

                # save hex position
                x, y = cemu.getXY()
                # write text
                cemu.writeAt(self.COLUMNS*3 + self.gap + (i%self.COLUMNS), y, self.cp437(c))

                # go back to hex chars
                cemu.gotoXY(x, y)

                qp.setBackgroundMode(0)


            cemu.writeLn()
        qp.end()

    def draw(self, refresh=False, row=0, howMany=0):
        if self.refresh or refresh:
            qp = QtGui.QPainter()
            qp.begin(self.qpix)

            if not howMany:
                howMany = self.ROWS

            self.drawTextMode(qp, row=row, howMany=howMany)
            self.refresh = False
            qp.end()

        self.drawAdditionals()

    def drawTextMode(self, qp, row=0, howMany=1):

        # draw background
        qp.fillRect(0, row * self.fontHeight, self.CON_COLUMNS * self.fontWidth,  howMany * self.fontHeight + self.SPACER, self.backgroundBrush)

        # set text pen&font
        qp.setFont(self.font)
        qp.setPen(self.textPen)

        cemu = ConsoleEmulator(qp, self.ROWS, self.CON_COLUMNS)

        page = self.transformationEngine.decorate()

        cemu.gotoXY(0, row)

        for i, c in enumerate(self.getDisplayablePage()[row*self.COLUMNS:(row + howMany)*self.COLUMNS]):     #TODO: does not apply all decorators

            w = i + row*self.COLUMNS

            if (w+1)%self.COLUMNS == 0:
                hex_s = str(hex(c)[2:]).zfill(2)
            else:
                hex_s = str(hex(c)[2:]).zfill(2) + ' '

            qp.setPen(self.transformationEngine.choosePen(w))

            if self.transformationEngine.chooseBrush(w) != None:
                qp.setBackgroundMode(1)
                qp.setBackground(self.transformationEngine.chooseBrush(w))

            # write hex representation
            cemu.write(hex_s, noBackgroudOnSpaces=True)
            # save hex position
            x, y = cemu.getXY()
            # write text
            cemu.writeAt(self.COLUMNS*3 + self.gap + (w%self.COLUMNS), y, self.cp437(c))
            # go back to hex chars
            cemu.gotoXY(x, y)
            if (w+1)%self.COLUMNS == 0:
                cemu.writeLn()

            qp.setBackgroundMode(0)

    def moveCursor(self, direction):
        #TODO: have to move this, don't like it
        if self.isInEditMode():
            if self.highpart == False:
                self.highpart = True

        cursorX, cursorY = self.cursor.getPosition()

        if direction == Directions.Left:
            if cursorX == 0:
                if cursorY == 0:
                    self.scroll(1, 0)
                else:
                    self.cursor.moveAbsolute(self.COLUMNS-1, cursorY - 1)
            else:
                self.cursor.move(-1, 0)


        if direction == Directions.Right:
            if self.getCursorAbsolutePosition() + 1 >= self.dataModel.getDataSize():
                return

            if cursorX == self.COLUMNS-1:
                if cursorY == self.ROWS-1:
                    self.scroll(-1, 0)
                else:
                    self.cursor.moveAbsolute(0, cursorY + 1)
            else:
                self.cursor.move(1, 0)


        if direction == Directions.Down:
            if self.getCursorAbsolutePosition() + self.COLUMNS >= self.dataModel.getDataSize():
                y, x = self.dataModel.getXYInPage(self.dataModel.getDataSize()-1)
                self.cursor.moveAbsolute(x, y)
                return

            if cursorY == self.ROWS-1:
                self.scroll(0, -1)
            else:
                self.cursor.move(0, 1)

        if direction == Directions.Up:
            if cursorY == 0:
                self.scroll(0, 1)
            else:
                self.cursor.move(0, -1)

        if direction == Directions.End:
            if self.dataModel.getDataSize() < self.getCursorAbsolutePosition() + self.ROWS * self.COLUMNS:
                y, x = self.dataModel.getXYInPage(self.dataModel.getDataSize()-1)
                self.cursor.moveAbsolute(x, y)

            else:
                self.cursor.moveAbsolute(self.COLUMNS-1, self.ROWS-1)

        if direction == Directions.Home:
            self.cursor.moveAbsolute(0, 0)

        if direction == Directions.CtrlHome:
            self.dataModel.slideToFirstPage()
            self.draw(refresh=True)
            self.cursor.moveAbsolute(0, 0)

        if direction == Directions.CtrlEnd:
            self.dataModel.slideToLastPage()
            self.draw(refresh=True)
            self.moveCursor(Directions.End)



    def drawCursor(self, qp):
        qp.setBrush(QtGui.QColor(255, 255, 0))
        if self.isInEditMode():
            qp.setBrush(QtGui.QColor(255, 102, 179))

        cursorX, cursorY = self.cursor.getPosition()

        columns = self.HexColumns[self.idxHexColumns]
        if cursorX > columns:
            self.cursor.moveAbsolute(columns-1, cursorY)

        # get cursor position again, maybe it was moved
        cursorX, cursorY = self.cursor.getPosition()

        qp.setOpacity(0.8)
        if self.isInEditMode():
            qp.setOpacity(0.5)
        # cursor on text
        qp.drawRect((self.COLUMNS*3 + self.gap + cursorX)*self.fontWidth, cursorY*self.fontHeight+2, self.fontWidth, self.fontHeight)

        # cursor on hex
        if not self.isInEditMode():
            qp.drawRect(cursorX*3*self.fontWidth, cursorY*self.fontHeight+2, 2*self.fontWidth, self.fontHeight)
        else:
            if self.highpart:
                qp.drawRect(cursorX*3*self.fontWidth, cursorY*self.fontHeight+2, 1*self.fontWidth, self.fontHeight)
            else:
                qp.drawRect(cursorX*3*self.fontWidth + self.fontWidth, cursorY*self.fontHeight+2, 1*self.fontWidth, self.fontHeight)

        qp.setOpacity(1)

    def keyFilter(self):
        return [
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Right),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Left),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Up),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Down),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_End),
                (QtCore.Qt.ControlModifier, QtCore.Qt.Key_Home),


                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Right),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Left),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Up),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Down),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_End),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_Home),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_PageDown),
                (QtCore.Qt.NoModifier, QtCore.Qt.Key_PageUp)


                ]


    def anon(self, dx, dy):
        self.scroll(dx, dy)

        # scroll modifies datamodel offset, so we must do scroll and cursor
        # operations toghether

        y, x = self.dataModel.getXYInPage(self.dataModel.getDataSize() - 1)
        if self.getCursorAbsolutePosition() >= self.dataModel.getDataSize():
            y, x = self.dataModel.getXYInPage(self.dataModel.getDataSize() - 1)
            self.cursor.moveAbsolute(x, y)

        # we call draw() again because it was called before by scroll()
        # and the cursor is already painted but it's not in correct position
        # kinda hack, don't really like it
        self.draw()

    def handleEditMode(self, modifiers, key, event):

        if str(event.text()).lower() in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']:

            offs = self.getCursorOffsetInPage()

            b = self.dataModel.getBYTE(self.dataModel.getOffset() + offs)
            if b is None:
                return

            z = int(str(event.text()), 16)

            # compute nibble
            if self.highpart:
                b =  ((z << 4) | (b & 0x0F)) & 0xFF
            else:
                b =  ((b & 0xF0) | (z & 0x0F)) & 0xFF


            block = modifiers == QtCore.Qt.AltModifier and self.selector.getCurrentSelection()

            # change block or single byte
            if block:
                # multiple, with ALT key
                if self.selector.getCurrentSelection():
                    u, v = self.selector.getCurrentSelection()

                    for x in range(u, v):
                        b = self.dataModel.getBYTE(x)
                        if self.highpart:
                            b =  ((z << 4) | (b & 0x0F)) & 0xFF
                        else:
                            b =  ((b & 0xF0) | (z & 0x0F)) & 0xFF

                        self.dataModel.setData_b(x, b)
            else:
                self.dataModel.setData_b(self.dataModel.getOffset() + offs, b)

            if block:
                self.transformationEngine = RangePen(self.original_textdecorator, u, v, QtGui.QPen(QtGui.QColor(218, 94, 242), 0, QtCore.Qt.SolidLine), ignoreHighlights=True)
            else:
                z = self.dataModel.getOffset() + offs
                #TODO: sa nu se repete, tre original_transformengine
                self.transformationEngine = RangePen(self.original_textdecorator, z, z + 0, QtGui.QPen(QtGui.QColor(218, 94, 242), 0, QtCore.Qt.SolidLine), ignoreHighlights=True)

            # se if we are at end of row, we must also redraw previous line
            highpart = self.highpart

            # for block mode, move cursor
            if not block:
                x, old_y = self.cursor.getPosition()

                if self.highpart == False:
                    self.moveCursor(Directions.Right)

                x, y = self.cursor.getPosition()

            if highpart:
                self.highpart = False
            else:
                self.highpart = True

            if block:
                self.draw(refresh=True)
            else:
                self.draw(refresh=True, row=y, howMany=1)
                if y > old_y:
                    self.draw(refresh=True, row=y-1, howMany=1)


    def handleKeyEvent(self, modifiers, key, event=None):
        if event.type() == QtCore.QEvent.KeyRelease:
            if key == QtCore.Qt.Key_Shift:
                self.stopSelection()
                return True

        if event.type() == QtCore.QEvent.KeyPress:

            if modifiers == QtCore.Qt.ShiftModifier:
                keys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Down, QtCore.Qt.Key_Up, QtCore.Qt.Key_End, QtCore.Qt.Key_Home]
                if key in keys:
                    self.startSelection()

                if key == QtCore.Qt.Key_Question:
                    self.annotationWindow()

            if modifiers == QtCore.Qt.AltModifier:
                if key == QtCore.Qt.Key_A:
                    self.add_annotation(1)
                    return True


            if modifiers == QtCore.Qt.ControlModifier:
                if key == QtCore.Qt.Key_A:
                    self.add_annotation(2)

                if key == QtCore.Qt.Key_Right:
                    self.addop((self.anon, -1, 0))

                if key == QtCore.Qt.Key_Left:
                    self.addop((self.scroll, 1, 0))


                if key == QtCore.Qt.Key_Down:
                    self.addop((self.anon, 0, -1))

                if key == QtCore.Qt.Key_Up:
                    self.addop((self.scroll, 0, 1))

                if key == QtCore.Qt.Key_End:
                    self.moveCursor(Directions.CtrlEnd)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Home:
                    self.moveCursor(Directions.CtrlHome)
                    self.addop((self.draw,))

                return True

            else:#selif modifiers == QtCore.Qt.NoModifier:

                if key == QtCore.Qt.Key_Escape:
                    self.selector.resetSelections()
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Left:
                    self.moveCursor(Directions.Left)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Right:
                    self.moveCursor(Directions.Right)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Down:
                    self.moveCursor(Directions.Down)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_End:
                    self.moveCursor(Directions.End)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Home:
                    self.moveCursor(Directions.Home)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_Up:
                    self.moveCursor(Directions.Up)
                    self.addop((self.draw,))

                if key == QtCore.Qt.Key_PageDown:
                    self.addop((self.scrollPages, 1))

                if key == QtCore.Qt.Key_PageUp:
                    self.addop((self.scrollPages, -1))

                if key == QtCore.Qt.Key_F6:
                    self.changeHexColumns()
                    x, y = self.cursor.getPosition()

                    columns = self.HexColumns[self.idxHexColumns]
                    if x > columns:
                        self.cursor.moveAbsolute(columns-1, y)
                    self.addop((self.draw,))

                if self.isInEditMode():
                    self.handleEditMode(modifiers, key, event)

                return True

        return False

    def isEditable(self):
        return True

    def setEditMode(self, mode):
        super(HexViewMode, self).setEditMode(mode)
        if mode == False:
            self.highpart = True
            self.transformationEngine = self.original_textdecorator
            self.transformationEngine.reset()
            self.draw(refresh=True)

            for shortcut in self.plugin.getShortcuts():
                if shortcut.key().toString() in list('0123456789abcdefABCDEF') + ['Alt+A', 'Alt+B', 'Alt+C', 'Alt+D', 'Alt+E', 'Alt+F']:
                    shortcut.setEnabled(True)


        if mode == True:
            for shortcut in self.plugin.getShortcuts():
                if shortcut.key().toString() in list('0123456789abcdefABCDEF') + ['Alt+A', 'Alt+B', 'Alt+C', 'Alt+D', 'Alt+E', 'Alt+F']:
                    shortcut.setEnabled(False)

    def addop(self, t):
        self.Ops.append(t)

    def getHeaderInfo(self):
        s = ''
        for i in range(self.HexColumns[self.idxHexColumns]):
            s += '{0} '.format('{0:x}'.format(i).zfill(2))

        s += self.gap*' ' + 'Text'
        return s


    def annotationWindow(self):
        w = self.ann_w.treeWidget

        w.setDragEnabled(True)
        w.viewport().setAcceptDrops(True)
        w.setDropIndicatorShown(True)

        self.ann_w.show()


    @QtCore.pyqtSlot("QItemSelection, QItemSelection")
    def selectionChanged(self, selected, deselected):

         item = self.ann_w.treeWidget.currentItem()

         if item:
            offset = item.getOffset()
            size = item.getSize()
            u = offset
            v = offset + size
            self.selector.addSelection((u, v, QtGui.QBrush(QtGui.QColor(125, 255, 0)), 0.2), type=TextSelection.SelectionType.NORMAL)
            self.goTo(u)

    @QtCore.pyqtSlot("QTreeWidgetItem*, int")
    def itemChanged(self, item, column):

        ID_NAME = 0
        ID_DESCRIPTION = 4


        s = str(item.text(column))

        if column == ID_NAME:
            item.setName(s)

        if column == ID_DESCRIPTION:
            item.setDescription(s)


    def add_annotation(self, mode):
        #FIXME: check if this connect call is the correct way in PyQt5
        # QtCore.QObject.connect(self.ann_w.treeWidget.selectionModel(), QtCore.SIGNAL('selectionChanged(QItemSelection, QItemSelection)'), self.selectionChanged)
        self.ann_w.treeWidget.selectionModel().selectionChanged.connect(lambda selected, deselected: self.selectionChanged(selected, deselected))
        # QtCore.QObject.connect(self.ann_w.treeWidget, QtCore.SIGNAL('itemChanged(QTreeWidgetItem*, int)'), self.itemChanged)
        self.ann_w.treeWidget.itemChanged.connect(lambda item, column: self.itemChanged(item, column))


        ID_NAME = 0
        ID_OFFSET = 1
        ID_SIZE = 2
        ID_VALUE = 3
        ID_DESCRIPTION = 4
        ID_COLOR = 5

        if self.selector.getCurrentSelection():
            u, v = self.selector.getCurrentSelection()
        else:
            return

        import random
        r = random.randint(0, 255)
        g = random.randint(0, 255)
        b = random.randint(0, 255)

        opacity = 0.4
        if mode == 2:
            opacity = 0.25

        qcolor = QtGui.QColor(r, g, b)
        added = self.selector.addSelection((u, v, QtGui.QBrush(qcolor), opacity), type=TextSelection.SelectionType.PERMANENT)

#        if not added:
#            return

        t = self.ann_w.treeWidget

        row = AnnonItem(None, self.ann_w.treeWidget, qcolor.name())
        row.setFlags(QtCore.Qt.ItemIsSelectable |
                  QtCore.Qt.ItemIsEnabled |
                  QtCore.Qt.ItemIsEditable |
                  QtCore.Qt.ItemIsDropEnabled |
                  QtCore.Qt.ItemIsDragEnabled)

        t.setAcceptDrops(True)
        t.setDragEnabled(True)
        t.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove)

        delegate = NoEditDelegate()
        t.setItemDelegateForColumn(1, delegate)
        t.setItemDelegateForColumn(2, delegate)
        t.setItemDelegateForColumn(3, delegate)
        t.setItemDelegateForColumn(5, delegate)


        row.setName(self.ann_w.newFieldName())
        row.setOffset(u)
        #row.setText(ID_NAME, 'field_0')
        #row.setText(ID_OFFSET, hex(u))

        size = v - u
        #row.setText(ID_SIZE, hex(size))
        row.setSize(size)

        value = ''
        if size == 1:
            value = self.dataModel.getBYTE(u, asString=True)
        elif size == 2:
            value = self.dataModel.getWORD(u, asString=True)
        elif size == 4:
            value = self.dataModel.getDWORD(u, asString=True)
        else:
            value = repr(str(self.dataModel.getStream(u, v)))


        #row.setText(ID_VALUE, value)
        row.setValue(value)

        #cmb.setCurrentIndex(cmb.findData(w))

        if mode == 2:
            self.ann_w.treeWidget.addTopLevelItem(row)

        if mode == 1:
            selected = t.selectedItems()
            if len(selected) == 1:
                selected = selected[0]
            else:
                selected = t.topLevelItem(0)

            if selected:
                selected.addChild(row)

        t.expandItem(row)

        #cmb = QColorButton()
        #cmb.setColor(qcolor.name())
        #self.ann_w.treeWidget.setItemWidget(row, ID_COLOR, cmb)

        self.ann_w.treeWidget.setItemWidget(row, ID_COLOR, row.cmb)

        #self.ann_w.treeWidget.openPersistentEditor(row, 0)
        #self.ann_w.treeWidget.editItem(row, 0)
        #self.ann_w.treeWidget.editItem(row, 3)

class NoEditDelegate(QtWidgets.QStyledItemDelegate):
    def __init__(self, parent=None):
        super(NoEditDelegate, self).__init__(parent)

    def createEditor(self, parent, option, index):
        return None

class AnnonItem(QtWidgets.QTreeWidgetItem):
    ID_NAME = 0
    ID_OFFSET = 1
    ID_SIZE = 2
    ID_VALUE = 3
    ID_DESCRIPTION = 4
    ID_COLOR = 5


    def __init__(self, x, parent, color):
        super(AnnonItem, self).__init__(x)
        self._color = color
        self._t_parent = parent

        self.cmb = QColorButton()
        self.cmb.setColor(self._color)

        #self._t_parent.setItemWidget(self, self.ID_COLOR, self.cmb)



    def setName(self, name):
        self._name = name
        self.setText(self.ID_NAME, name)

    def getName(self):
        return self._name

    def setOffset(self, offset):
        self._offset = offset
        self.setText(self.ID_OFFSET, hex(offset))

    def getOffset(self):
        return self._offset

    def setSize(self, size):
        self._size = size
        self.setText(self.ID_SIZE, hex(size))

    def getSize(self):
        return self._size

    def setValue(self, value):
        self._value = value
        self.setText(self.ID_VALUE, value)

    def getValue(self):
        return self._value

    def setDescription(self, description):
        self._description = description
        self.setText(self.ID_DESCRIPTION, description)

    def getDescription(self):
        return self._description


class QColorButton(QtWidgets.QPushButton):
    '''
    Custom Qt Widget to show a chosen color.

    Left-clicking the button shows the color-chooser, while
    right-clicking resets the color to None (no-color).
    '''

    '''
    based on http://martinfitzpatrick.name/article/qcolorbutton-a-color-selector-tool-for-pyqt/
    '''
    colorChanged = QtCore.pyqtSignal()

    def __init__(self, *args, **kwargs):
        super(QColorButton, self).__init__(*args, **kwargs)

        self._color = None
        self.setMaximumWidth(32)
        self.pressed.connect(self.onColorPicker)

    def setColor(self, color):
        if color != self._color:
            self._color = color
            self.colorChanged.emit()

        if self._color:
            self.setStyleSheet("background-color: %s;" % self._color)
        else:
            self.setStyleSheet("")

    def color(self):
        return self._color

    def onColorPicker(self):
        '''
        Show color-picker dialog to select color.

        Qt will use the native dialog by default.

        '''
        dlg = QtGui.QColorDialog(QtGui.QColor(self._color), None)

        #if self._color:
        #    dlg.setCurrentColor(QtGui.QColor(self._color))

        if dlg.exec_():
            self.setColor(dlg.currentColor().name())

    def mousePressEvent(self, e):
        if e.button() == QtCore.Qt.RightButton:
            self.setColor(None)

        return super(QColorButton, self).mousePressEvent(e)

class ComboBoxItem(QtWidgets.QComboBox):
    def __init__(self, item, column):
        super(ComboBoxItem, self).__init__()

        self.item = item
        self.column = column

class Annotation(QtWidgets.QDialog):

    _fieldIdx = 0
    def __init__(self, parent, view):
        super(Annotation, self).__init__(parent)

        self.parent = parent
        self.view = view
        self.oshow = super(Annotation, self).show

        import os
        root = os.path.dirname(sys.argv[0])
        self.ui = PyQt5.uic.loadUi(os.path.join(root, 'annotation.ui'), baseinstance=self)

#        self.ei = ImportsEventFilter(plugin, self.ui.treeWidgetImports)

        self.ei = treeEventFilter(view, self.ui.treeWidget)
        self.ui.treeWidget.installEventFilter(self.ei)

        self.initUI()

    def newFieldName(self):
        name = 'field_{}'.format(self._fieldIdx)
        self._fieldIdx += 1
        return name
        
    def show(self):

        # TODO: remember position? resize plugin windows when parent resize?
        pwidth = self.parent.parent.size().width()
        pheight = self.parent.parent.size().height()

        width = self.ui.treeWidget.size().width()+15
        height = self.ui.treeWidget.size().height()+15

        self.setGeometry(pwidth - width - 15, pheight - height, width, height)
        self.setFixedSize(width, height)

        self.oshow()

    def initUI(self):

        self.setWindowTitle('Annotations')
        self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)

        shortcut = QtWidgets.QShortcut(QtGui.QKeySequence("Shift+/"), self, self.close, self.close)

class treeEventFilter(QtCore.QObject):
    def __init__(self, view, widget):
        super(QtCore.QObject, self).__init__()
        self.widget = widget
        self.view = view

    def eventFilter(self, watched, event):
        if event.type() == QtCore.QEvent.KeyPress:
            if event.key() == QtCore.Qt.Key_Delete:

                # get RVA column from treeView

                item = self.widget.currentItem()

                offset = item.getOffset()#int(str(item.text(1)),0)
                size = item.getSize()#int(str(item.text(2)),0)
                u = offset
                v = offset + size

                self.view.selector.removeSelection(u, v, TextSelection.SelectionType.PERMANENT)
                # TODO: remove tree!

                item.parent().removeChild(item)
                #self.widget.takeTopLevelItem(self.widget.indexOfTopLevelItem(item))
                #print item
                #rva = self.widget.indexFromItem(item, 1).data().toString()

        return False