"""
summary: create custom listings in IDA

description:
  How to create simple listings, that will share many of the features
  as the built-in IDA widgets (highlighting, copy & paste,
  notifications, ...)

  In addition, creates actions that will be bound to the
  freshly-created widget (using `ida_kernwin.attach_action_to_popup`.)

keywords: listing, actions

level: intermediate
"""

import ida_kernwin
import ida_lines

# -----------------------------------------------------------------------
class say_something_handler_t(ida_kernwin.action_handler_t):
    def __init__(self, thing):
        ida_kernwin.action_handler_t.__init__(self)
        self.thing = thing

    def activate(self, ctx):
        print(self.thing)

    def update(self, ctx):
        return ida_kernwin.AST_ENABLE_ALWAYS

    @staticmethod
    def compose_action_name(v):
        return "custview:say_%s" % v


# create actions
actions_variants = ["Hello", "World"]
for av in actions_variants:
    actname = say_something_handler_t.compose_action_name(av)
    if ida_kernwin.unregister_action(actname):
        print("Unregistered previously-registered action \"%s\"" % actname)

    desc = ida_kernwin.action_desc_t(actname, "Say %s" % av, say_something_handler_t(av))
    if ida_kernwin.register_action(desc):
        print("Registered action \"%s\"" % actname)



# -----------------------------------------------------------------------
class mycv_t(ida_kernwin.simplecustviewer_t):
    def Create(self, sn=None, use_colors=True):
        # Form the title
        title = "Simple custom view test"
        if sn:
            title += " %d" % sn
        self.use_colors = use_colors

        # Create the customviewer
        if not ida_kernwin.simplecustviewer_t.Create(self, title):
            return False

        for i in range(0, 100):
            prefix, bg = ida_lines.COLOR_DEFAULT, None
            # make every 10th line a bit special
            if i % 10 == 0:
                prefix = ida_lines.COLOR_DNAME   # i.e., dark yellow...
                bg = 0xFFFF00                 # ...on cyan
            pfx = ida_lines.COLSTR("%3d" % i, ida_lines.SCOLOR_PREFIX)
            if self.use_colors:
                self.AddLine("%s: Line %d" % (pfx, i), fgcolor=prefix, bgcolor=bg)
            else:
                self.AddLine("%s: Line %d" % (pfx, i))

        return True

    def OnClick(self, shift):
        """
        User clicked in the view
        :param shift: Shift flag
        :returns: Boolean. True if you handled the event
        """
        print("OnClick, shift=%d" % shift)
        return True

    def OnDblClick(self, shift):
        """
        User dbl-clicked in the view
        :param shift: Shift flag
        :returns: Boolean. True if you handled the event
        """
        word = self.GetCurrentWord()
        if not word: word = "<None>"
        print("OnDblClick, shift=%d, current word=%s" % (shift, word))
        return True

    def OnCursorPosChanged(self):
        """
        Cursor position changed.
        :returns: Nothing
        """
        print("OnCurposChanged")

    def OnClose(self):
        """
        The view is closing. Use this event to cleanup.
        :returns: Nothing
        """
        print("OnClose " + self.title)

    def OnKeydown(self, vkey, shift):
        """
        User pressed a key
        :param vkey: Virtual key code
        :param shift: Shift flag
        :returns: Boolean. True if you handled the event
        """
        print("OnKeydown, vk=%d shift=%d" % (vkey, shift))
        # Escape?
        if vkey == ida_kernwin.IK_ESCAPE:
            self.Close()
        # Delete?
        elif vkey == ida_kernwin.IK_DELETE:
            n = self.GetLineNo()
            if n is not None:
                self.DelLine(n)
                self.Refresh()
                print("Deleted line %d" % n)
        # Goto?
        elif vkey == ord('G'):
            n = self.GetLineNo()
            if n is not None:
                v = ida_kernwin.ask_long(self.GetLineNo(), "Where to go?")
                if v:
                    self.Jump(v, 0, 5)
        elif vkey == ord('R'):
            print("refreshing....")
            self.Refresh()
        elif vkey == ord('C'):
            print("refreshing current line...")
            self.RefreshCurrent()
        elif vkey == ord('A'):
            s = ida_kernwin.ask_str("NewLine%d" % self.Count(), 0, "Append new line")
            self.AddLine(s)
            self.Refresh()
        elif vkey == ord('X'):
            print("Clearing all lines")
            self.ClearLines()
            self.Refresh()
        elif vkey == ord('I'):
            n = self.GetLineNo()
            s = ida_kernwin.ask_str("InsertedLine%d" % n, 0, "Insert new line")
            self.InsertLine(n, s)
            self.Refresh()
        elif vkey == ord('E'):
            l = self.GetCurrentLine(notags=1)
            if not l:
                return False
            n = self.GetLineNo()
            print("curline=<%s>" % l)
            l = l + ida_lines.COLSTR("*", ida_lines.SCOLOR_VOIDOP)
            self.EditLine(n, l)
            self.RefreshCurrent()
            print("Edited line %d" % n)
        else:
            return False
        return True

    def OnHint(self, lineno):
        """
        Hint requested for the given line number.

        :param lineno: The line number (zero based)
        :returns: tuple(number of important lines, hint string), or None: if no hint available
        """
        return (1, "OnHint, line=%d" % lineno)

    def Show(self, *args):
        ok = ida_kernwin.simplecustviewer_t.Show(self, *args)
        if ok:
            # permanently attach actions to this viewer's popup menu
            for av in actions_variants:
                actname = say_something_handler_t.compose_action_name(av)
                ida_kernwin.attach_action_to_popup(self.GetWidget(), None, actname)
        return ok

# -----------------------------------------------------------------------
try:
    # created already?
    mycv
    print("Already created, will close it...")
    mycv.Close()
    del mycv
except:
    pass

def show_win():
    x = mycv_t()
    if not x.Create():
        print("Failed to create!")
        return None
    x.Show()
    tcc = x.GetWidget()
    return x

mycv = show_win()
if not mycv:
    del mycv

def make_many(n):
    L = []
    for i in range(1, n+1):
        v = mycv_t()
        if not v.Create(i):
            break
        v.Show()
        L.append(v)
    return L
