From f51c2b602f130eb8742a3a6d671f317094c89c8a Mon Sep 17 00:00:00 2001 From: Axel Tillequin Date: Sat, 12 Mar 2016 23:16:54 +0100 Subject: [PATCH] merging some SSE instructions semantics and items for qt and gtk ui.graphics packages. --- README.rst | 9 + amoco/arch/x64/formats.py | 8 +- amoco/arch/x86/asm.py | 220 +++++++++++++++- amoco/arch/x86/env.py | 1 + amoco/arch/x86/formats.py | 9 +- amoco/cas/expressions.py | 10 +- amoco/code.py | 26 +- amoco/config.py | 1 + amoco/main.py | 3 + amoco/ui/graphics/__init__.py | 17 +- amoco/ui/graphics/gtk_/canvas.py | 67 +++++ amoco/ui/graphics/gtk_/engine.py | 29 +++ amoco/ui/graphics/gtk_/graphwin.py | 103 ++++++++ amoco/ui/graphics/gtk_/items.py | 406 +++++++++++++++++++++++++++++ amoco/ui/graphics/gtk_/mainwin.py | 55 ++++ amoco/ui/graphics/kivy_/engine.py | 14 + amoco/ui/graphics/qt_/engine.py | 47 ++++ amoco/ui/graphics/qt_/graphwin.py | 69 +++++ amoco/ui/graphics/qt_/items.py | 199 ++++++++++++++ amoco/ui/graphics/qt_/mainwin.py | 63 +++++ amoco/ui/graphics/qt_/statswin.py | 7 + amoco/ui/graphics/term.py | 32 ++- amoco/ui/render.py | 48 +++- amoco/ui/views.py | 69 ++++- tests/samples/x86/puttygen.exe | Bin 0 -> 180224 bytes tests/test_arch_x86.py | 13 + tests/test_ui_qt.py | 16 ++ 27 files changed, 1491 insertions(+), 50 deletions(-) create mode 100644 amoco/ui/graphics/gtk_/canvas.py create mode 100644 amoco/ui/graphics/gtk_/engine.py create mode 100644 amoco/ui/graphics/gtk_/graphwin.py create mode 100644 amoco/ui/graphics/gtk_/items.py create mode 100644 amoco/ui/graphics/gtk_/mainwin.py create mode 100644 amoco/ui/graphics/kivy_/engine.py create mode 100644 amoco/ui/graphics/qt_/engine.py create mode 100644 amoco/ui/graphics/qt_/graphwin.py create mode 100644 amoco/ui/graphics/qt_/items.py create mode 100644 amoco/ui/graphics/qt_/mainwin.py create mode 100644 amoco/ui/graphics/qt_/statswin.py create mode 100644 tests/samples/x86/puttygen.exe create mode 100644 tests/test_ui_qt.py diff --git a/README.rst b/README.rst index 3523069..55474b6 100644 --- a/README.rst +++ b/README.rst @@ -1338,6 +1338,14 @@ Please see `LICENSE`_. Changelog ========= +- `v2.4.4`_ + + * add some SSE instruction semantics + * add ui.graphics qt package with block/func/xfunc items classes + * add initial ui.graphics gtk package + * move vltable in ui.views.blockView class + * fix various x86/64 decoding/formating/semantics + - `v2.4.3`_ * add ui.graphics packages (emptied) @@ -1444,6 +1452,7 @@ Changelog .. _ply: http://www.dabeaz.com/ply/ .. _zodb: http://www.zodb.org .. _LICENSE: https://github.com/bdcht/amoco/blob/release/LICENSE +.. _v2.4.4: https://github.com/bdcht/amoco/releases/tag/v2.4.4 .. _v2.4.3: https://github.com/bdcht/amoco/releases/tag/v2.4.3 .. _v2.4.2: https://github.com/bdcht/amoco/releases/tag/v2.4.2 .. _v2.4.1: https://github.com/bdcht/amoco/releases/tag/v2.4.1 diff --git a/amoco/arch/x64/formats.py b/amoco/arch/x64/formats.py index 238bab3..e415764 100644 --- a/amoco/arch/x64/formats.py +++ b/amoco/arch/x64/formats.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- +from amoco.cas.expressions import regtype from amoco.arch.core import Formatter def pfx(i): @@ -18,7 +19,12 @@ def deref(op): d = '%+d'%op.a.disp if op.a.disp else '' s = {8:'byte ptr ',16:'word ptr ',32:'dword ptr ', 128:'xmmword ptr '}.get(op.size,'') s += '%s:'%op.a.seg if op.a.seg is not '' else '' - s += '[%s%s]'%(op.a.base,d) + b = op.a.base + if op.a.base._is_reg and op.a.base.type==regtype.STACK: + base10 = True + else: + base10 = False + s += '[%s%s]'%(op.a.base,op.a.disp_to_string(base10)) return s def opers(i): diff --git a/amoco/arch/x86/asm.py b/amoco/arch/x86/asm.py index e6b8084..13503b6 100644 --- a/amoco/arch/x86/asm.py +++ b/amoco/arch/x86/asm.py @@ -392,7 +392,10 @@ def i_MOVSB(i,fmap): def i_MOVSW(i,fmap): _movs_(i,fmap,2) def i_MOVSD(i,fmap): - _movs_(i,fmap,4) + if i.misc['opdsz']==128: + sse_MOVSD(i,fmap) + else: + _movs_(i,fmap,4) #------------------------------------------------------------------------------ def i_IN(i,fmap): @@ -1208,3 +1211,218 @@ def i_SYSEXIT(i,fmap): fmap[cs] = top(16) fmap[ss] = top(16) +def i_PAND(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = fmap(i.operands[1]) + x=fmap(op1)&op2 + fmap[op1] = x + +def i_PANDN(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = fmap(i.operands[1]) + x=fmap(~op1)&op2 + fmap[op1] = x + +def i_POR(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = fmap(i.operands[1]) + x=fmap(op1)|op2 + fmap[op1] = x + +def i_PXOR(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = fmap(i.operands[1]) + x=fmap(op1)^op2 + fmap[op1] = x + +def i_MOVQ(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = fmap(i.operands[1]) + fmap[op1] = op2.zeroextend(op1.size) + +def sse_MOVSD(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = i.operands[1] + if op1._is_mem: + src = fmap(op2[0:op1.size]) + elif op2._is_mem: + src = fmap(op2).zeroextend(op1.size) + fmap[op1] = src + +def i_MOVDQU(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = i.operands[1] + fmap[op1] = fmap(op2) + +def i_MOVDQA(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = i.operands[1] + fmap[op1] = fmap(op2) + +def i_MOVUPS(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = i.operands[1] + fmap[op1] = fmap(op2) + +def i_MOVAPS(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = i.operands[1] + fmap[op1] = fmap(op2) + +def i_PADDB(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = i.operands[1] + assert op1.size==op2.size + for __i in range(0,op1.size,8): + src1 = fmap(op1[__i:__i+8]) + src2 = fmap(op2[__i:__i+8]) + fmap[op1[__i:__i+8]] = src1+src2 + +def i_PSUBUSB(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = i.operands[1] + assert op1.size==op2.size + for __i in range(0,op1.size,8): + src1 = fmap(op1[__i:__i+8]) + src2 = fmap(op2[__i:__i+8]) + res = src1-src2 + fmap[op1[__i:__i+8]] = tst(src1src2,src1,src2) + +def i_PMINUB(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = i.operands[1] + assert op1.size==op2.size + for __i in range(0,op1.size,8): + src1 = fmap(op1[__i:__i+8]) + src2 = fmap(op2[__i:__i+8]) + fmap[op1[__i:__i+8]] = tst(src1>src2.value for v1 in val1] + fmap[op1] = composer(res) + +def i_PSLLQ(i,fmap): + fmap[eip] = fmap[eip]+i.length + op1 = i.operands[0] + op2 = i.operands[1] + assert op1.size==op2.size + src1 = fmap(op1) + src2 = fmap(op2) + val1 = (src1[i:i+64] for i in range(0,op1.size,64)) + res = [v1<'%(self.__class__.__name__,self.name,id(self)) @@ -207,6 +186,7 @@ def makemap(self,withmap=None,widening=True): if withmap is not None: tmap <<= withmap heads[t] = tmap + self.misc['heads'] = heads # lets walk the function's cfg, in rank priority order: while len(spool)>0: count += 1 diff --git a/amoco/config.py b/amoco/config.py index bf6a7ea..982ba68 100644 --- a/amoco/config.py +++ b/amoco/config.py @@ -26,6 +26,7 @@ # ui section conf.add_section('ui') conf.set('ui', 'formatter', 'Null') +conf.set('ui', 'graphics', 'term') # overwrite with config file: import os diff --git a/amoco/main.py b/amoco/main.py index 65bb8b0..671cf7d 100644 --- a/amoco/main.py +++ b/amoco/main.py @@ -430,6 +430,9 @@ def check_func(self,node): # so we can return now : if len(T)>0: logger.verbose('extending cfg of %s (new target found)'%f) + for t in T: + for k,v in f.misc['heads'].iteritems(): + if v(pc)==t.cst: t.parent = k else: logger.info('lbackward: function %s done'%f) f.map = m diff --git a/amoco/ui/graphics/__init__.py b/amoco/ui/graphics/__init__.py index bf0bef4..ece3817 100644 --- a/amoco/ui/graphics/__init__.py +++ b/amoco/ui/graphics/__init__.py @@ -1,3 +1,18 @@ from .term import engine as termengine -default = termengine +class Engine(object): + engine = termengine + +def configure(**kargs): + from amoco.config import get_module_conf + conf = get_module_conf('ui') + conf.update(kargs) + g = conf['graphics'] + if g is 'gtk': + from amoco.ui.graphics.gtk_ import engine + Engine.engine = engine + elif g is 'qt': + from amoco.ui.graphics.qt_ import engine + Engine.engine = engine + +configure() diff --git a/amoco/ui/graphics/gtk_/canvas.py b/amoco/ui/graphics/gtk_/canvas.py new file mode 100644 index 0000000..6b394a7 --- /dev/null +++ b/amoco/ui/graphics/gtk_/canvas.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2008 Axel Tillequin (bdcht3@gmail.com) +# This code is part of Masr +# published under GPLv2 license + +from goocanvas import Canvas as GooCanvas +from goocanvas import polyline_new_line,Grid,LineDash +from goocanvas import ITEM_VISIBLE,ITEM_INVISIBLE +import gtk + +#------------------------------------------------------------------------------ +# just a wrapper above GooCanvas class, with texture dict added. +# we just wrap it so that introspection/debug is easy... +class Canvas(GooCanvas): + textures = {} + + def __init__(self,**args): + GooCanvas.__init__(self,**args) + # infinite world should replace scroll_region + self.set_properties(automatic_bounds=True, + integer_layout=False, + bounds_from_origin=False, + bounds_padding=100) + + self.root = self.get_root_item() + self.root.set_properties(fill_color='white') + + polyline_new_line(self.root,-5,0,5,0,stroke_color='red') + polyline_new_line(self.root,0,-5,0,5,stroke_color='red') + + self.zoom = False + + # GooCanvas will transmit all keyboard events to + # its parent unless one of its item has focus. + self.parent.connect_object("key_press_event", + Canvas.eventhandler,self) + self.parent.connect_object("key_release_event", + Canvas.eventhandler,self) + self.connect("event",Canvas.eventhandler) + + def eventhandler(self,e): + if e.type == gtk.gdk.KEY_PRESS: + kvn = gtk.gdk.keyval_name(e.keyval) + if kvn == 'a': + self.scroll_to(0,0) + if kvn == 'Control_L': + if not self.zoom: + self.zoom = True + elif kvn == 'plus' and self.zoom: + self.props.scale *= 1.2 + elif kvn == 'minus' and self.zoom: + self.props.scale *= 0.8 + return False + elif e.type == gtk.gdk.KEY_RELEASE: + if gtk.gdk.keyval_name(e.keyval) == 'Control_L': + self.zoom = False + return True + elif e.type == gtk.gdk.SCROLL and self.zoom: + if e.direction == gtk.gdk.SCROLL_UP: + self.props.scale *= 1.2 + elif e.direction == gtk.gdk.SCROLL_DOWN: + self.props.scale *= 0.8 + return True + elif e.type == gtk.gdk.BUTTON_PRESS: + print "click:(%d,%d)"%e.get_coords() + return False + diff --git a/amoco/ui/graphics/gtk_/engine.py b/amoco/ui/graphics/gtk_/engine.py new file mode 100644 index 0000000..9856297 --- /dev/null +++ b/amoco/ui/graphics/gtk_/engine.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from .mainwin import gtkWindow +from .items import * + +def builder(view): + if view._is_block: + return Node_codeblock(str(view.of)) + else: + return Node_basic(name=str(view.of),r=50) + +def setw(view,w): + view.obj.set_properties('width',w) + +def seth(view,h): + view.obj.set_properties('height',h) + +def getw(view): + return view.obj.wh[0] + +def geth(view): + return view.obj.wh[1] + +def setxy(view,xy): + view.obj.xy = xy + +def getxy(view): + return view.obj.xy + diff --git a/amoco/ui/graphics/gtk_/graphwin.py b/amoco/ui/graphics/gtk_/graphwin.py new file mode 100644 index 0000000..0e7088e --- /dev/null +++ b/amoco/ui/graphics/gtk_/graphwin.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2010 Axel Tillequin (bdcht3@gmail.com) +# This code is part of Amoco +# published under GPLv2 license + +import gtk + +from grandalf.routing import * + +from .items import * + +#------------------------------------------------------------------------------ +class GraphScene(object): + + def __init__(self,c,sug): + self.parent = c + self.sug = sug + self.sug.route_edge = route_with_lines + self.sug.dx,self.sug.dy = 5,5 + self.sug.dirvh=0 + c.parent.connect_object("button-press-event",GraphScene.eventhandler,self) + c.parent.connect_object("button-release-event",GraphScene.eventhandler,self) + c.parent.connect_object("key-press-event",GraphScene.eventhandler,self) + c.parent.connect_object("key-release-event",GraphScene.eventhandler,self) + for n in self.sug.g.sV: self.connect_add(n.view) + for e in self.sug.g.sE: + e.view = Edge_basic(e.v[0].view.obj,e.v[1].view.obj,head=True) + + def Draw(self,N=1,stepflag=False,constrained=False,opt=False): + #self.sug.init_all(cons=constrained,optimize=opt) + if stepflag: + self.drawer=self.sug.draw_step() + self.greens=[] + else: + self.sug.draw(N) + for e in self.sug.alt_e: e.view.set_properties(stroke_color='red') + for e in self.sug.g.sE: + self.parent.root.add_child(e.view) + # move edge start/end to CX points: + e.view.update_points() + + def connect_add(self,item): + self.parent.root.add_child(item.obj) + + def disconnect(self): + self.parent.parent.disconnect_by_func(GraphScene.eventhandler) + + + def clean(self): + r = self.parent.root + for v in self.sug.g.sV: + r.remove_child(v.view.obj) + for e in self.sug.g.sE: + for cx in e.view.cx: + cx.unregister(e.view) + r.remove_child(e.view) + + # Scene-Wide (default) event handler on items events: + def eventhandler(self,e): + if e.type == gtk.gdk.KEY_PRESS: + if e.keyval == ord('p'): + for l in self.sug.layers: + for v in l: + v.view.xy = (self.sug.grx[v].x[self.sug.dirvh],v.view.xy[1]) + self.sug.draw_edges() + self.sug.dirvh = (self.sug.dirvh+1)%4 + if e.keyval == ord('W'): + self.sug.xspace += 1 + self.sug.setxy() + self.sug.draw_edges() + if e.keyval == ord('w'): + self.sug.xspace -= 1 + self.sug.setxy() + self.sug.draw_edges() + if e.keyval == ord('H'): + self.sug.yspace += 1 + self.sug.setxy() + self.sug.draw_edges() + if e.keyval == ord('h'): + self.sug.yspace -= 1 + self.sug.setxy() + self.sug.draw_edges() + if e.keyval == ord(' '): + try: + l,mvmt = next(self.drawer) + for x in self.greens: + x.view.shadbox.set_properties(fill_color='grey44') + self.greens=[] + for x in l: + if hasattr(x.view,'_obj'): + x.view._obj.shadbox.set_properties(fill_color='green') + if x in mvmt: x.view._obj.shadbox.set_properties(fill_color='orange') + self.greens.append(x) + except StopIteration: + print 'drawer terminated' + del self.drawer + del self.greens + except AttributeError: + print 'drawer created' + self.drawer=self.sug.draw_step() + self.greens=[] + diff --git a/amoco/ui/graphics/gtk_/items.py b/amoco/ui/graphics/gtk_/items.py new file mode 100644 index 0000000..26ef6b8 --- /dev/null +++ b/amoco/ui/graphics/gtk_/items.py @@ -0,0 +1,406 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2009 Axel Tillequin (bdcht3@gmail.com) +# This code is part of Amoco +# published under GPLv2 license + +import gtk +import math + +from goocanvas import * + +# connectors CX are embedded inside node views. These objects are drawn on +# the node's surface and exists only as sub-objects of their node. CX are used +# as 'ports' for edges connected with a node. A node can have several such CX +# and can register or unregister its edges on such CX. +class CX(Rect): + def __init__(self,e=None): + Rect.__init__(self,width=3,height=3) + self.set_properties(line_width=0,fill_color='red') + #self.props.visibility=False + # list of edges connected to this CX: + self.registered = [] + if e!=None: self.register(e) + #self.connect('event',CX.eventhandler) + + def set_wh(self,wh): + self.set_properties(width = wh[0], height = wh[1]) + def get_wh(self): + return self.get_properties('width','height') + wh = property(get_wh,set_wh) + + def getpos(self): + bb = self.get_bounds() + return (bb.x1,bb.y1) + + def getcenter(self): + xy = self.getpos() + wh = self.get_wh() + return (xy[0]+wh[0]/2.,xy[1]+wh[1]/2.) + + # manage Edge_basic that are using this CX: + def register(self,item): + self.registered.append(item) + def unregister(self,item): + self.registered.remove(item) + + def eventhandler(*args): + print "CX eventhandler on",args + +#------------------------------------------------------------------------------ +# decorators for eventhandlers: this sets the 'clicked' field to the mouse +# button id, and moves the object along with mouse-1 movements. +def mouse1moves(h): + def wrapper(self,item,e): + #self.last_msec = [0] + if e.type is gtk.gdk.BUTTON_PRESS: + self.clicked = e.button + self.oldx,self.oldy = e.get_coords() + #self.last_msec[0] = 0. + elif e.type is gtk.gdk.BUTTON_RELEASE: + self.clicked = 0 + elif e.type is gtk.gdk.MOTION_NOTIFY: + #if abs(e.time - self.last_msec[0])<10: return False + #self.last_msec[0]=e.time + if self.clicked==1: + newx,newy = e.get_coords() + tx,ty = newx-self.oldx,newy-self.oldy + self.translate(tx,ty) + self.notify('transform') + return h(self,item,e) + return wrapper + +#------------------------------------------------------------------------------ +# This is a 'circle' shaped view for nodes. +class Node_basic(Group): + + def __init__(self,name='?',r=10): + Group.__init__(self) + self.el = Ellipse(parent=self, + fill_color='gray88', + stroke_color='black', + line_width=2) + # extra: + self.alpha = 1. + self.r = r + self.label = Text(parent=self, + text='[%s]'%name, + font="monospace, bold, 8", + fill_color='blue', + anchor=gtk.ANCHOR_CENTER) + #self.label.props.visibility = False + # edges connectors: + self.cx = [] + # events: + self.connect("enter-notify-event",Node_basic.eventhandler) + self.connect("leave-notify-event",Node_basic.eventhandler) + self.connect("button-press-event",Node_basic.eventhandler) + self.connect("button-release-event",Node_basic.eventhandler) + self.connect("motion-notify-event",Node_basic.eventhandler) + # clicked: 1=mouse1, 2=mouse2, 3=mouse3 + self.clicked=0 + self.connect('notify::transform',Node_basic.notifyhandler) + self.raise_(None) + self.w,self.h = self.wh + + #prop: + def set_r(self,r): + self._r = r + self.el.set_properties(radius_x = r, radius_y = r) + def get_r(self): + return self._r + r = property(get_r,set_r) + + def set_wh(self,wh): pass + def get_wh(self): + bb = self.get_bounds() + self.w,self.h = (bb.x2-bb.x1,bb.y2-bb.y1) + return (self.w,self.h) + wh = property(get_wh,set_wh) + + def set_xy(self,xy): + self.props.x,self.props.y = xy + def get_xy(self): + return (self.props.x,self.props.y) + xy = property(get_xy,set_xy) + + # put the cx pt at the intersection between the circle shape of Node_basic + # and the radius from centre to pt 'topt'. + def intersect(self,topt,cx): + assert self.find_child(cx)!=-1 + #get cx pos on canvas: + bb = self.get_bounds() + w,h = self.get_wh() + x1,y1 = (w/2.,h/2.) + # intersect with target pt: + x2,y2 = topt[0]-bb.x1,topt[1]-bb.y1 + theta = math.atan2(y2-y1,x2-x1) + newx = math.cos(theta)*self._r + newy = math.sin(theta)*self._r + cx.set_properties(x=newx,y=newy) + self._angle = theta + + def set_alpha(self,a): + color = self.props.fill_color_rgba & 0xffffff00 + self.props.fill_color_rgba = color+(int(a*255.)&0xff) + def get_alpha(self): + return (self.props.fill_color_rgba&0xff)/255. + alpha = property(get_alpha,set_alpha) + + @mouse1moves + def eventhandler(self,item,e): + if e.type is gtk.gdk.ENTER_NOTIFY: + self.props.line_width=2.0 + elif e.type is gtk.gdk.LEAVE_NOTIFY: + self.props.line_width=1.0 + return False + + def notifyhandler(self,prop): + #print "notify %s on "%prop.name,self + for cx in self.cx: + for e in cx.registered: e.update_points() + +#------------------------------------------------------------------------------ +class Edge_basic(Polyline): + def __init__(self,n0,n1,head=False): + self.n = [n0,n1] + x0,y0 = n0.xy + x1,y1 = n1.xy + self.cx = [CX(self),CX(self)] + n0.cx.append(self.cx[0]); n0.add_child(self.cx[0]) + n1.cx.append(self.cx[1]); n1.add_child(self.cx[1]) + Polyline.__init__(self,points=Points([(x0,y0),(x1,y1)])) + self.set_properties(close_path=False, + stroke_color='black', + end_arrow=True, + fill_pattern=None, + line_width=2) + if head: + self.set_properties(end_arrow=True) + self.update_points() + self.lower(None) + self.clicked=0 + + def setpath(self,l): + self.props.points = Points(l) + self.update_points() + + def update_points(self): + pts = self.props.points.coords + self.n[0].intersect(topt=pts[1],cx=self.cx[0]) + self.n[1].intersect(topt=pts[-2],cx=self.cx[-1]) + self.cx[-1].set_properties(fill_color='blue') + cx = self.cx[0].getcenter() + pts[0] = cx + cx = self.cx[-1].getcenter() + pts[-1] = cx + self.props.points = Points(pts) + +#------------------------------------------------------------------------------ +class Edge_curve(Path): + + def __init__(self,n0,n1,head=True): + self.n = [n0,n1] + self.has_head=head + self.cx = [CX(self),CX(self)] + n0.cx.append(self.cx[0]); n0.add_child(self.cx[0]) + n1.cx.append(self.cx[1]); n1.add_child(self.cx[1]) + Path.__init__(self) + self.set_properties(stroke_color = 'black', + fill_pattern=None, + line_width = 2, + line_cap = 1, + line_join = 1) + self.splines = [[n0.xy,n1.xy]] + self.update_points() + self.lower(None) + self.clicked=0 + + def make_data(self): + p0 = self.splines[0][0] + data = "M %d %d"%p0 + for s in self.splines: + if len(s)==2: + data += " L %d %d"%s[1] + else: + data += " C %d %d"%s[1] + data += " %d %d"%s[2]+" %d %d"%s[3] + if self.has_head: + s=self.splines[-1] + p = s[-1] + dx = p[0]-s[-2][0] + dy = p[1]-s[-2][1] + l = math.sqrt(dx*dx+dy*dy) + dx,dy = dx/l*6,dy/l*6 + data += " l %d %d"%(-dx-dy,-dy+dx) + data += " M %d %d"%p + data += " l %d %d"%(-dx+dy,-dy-dx) + data += " M %d %d"%p + return data + + def setpath(self,l): + try: + self.splines = self.setcurve(l) + except: + pass + + def update_points(self): + try: + spl0 = self.splines[0] + spl1 = self.splines[-1] + # move CX to intersection between edge and Node border: + self.n[0].intersect(topt=spl0[-1],cx=self.cx[0]) + self.n[1].intersect(topt=spl1[0],cx=self.cx[1]) + cx = self.cx[0].getcenter() + spl0[0] = cx + cx = self.cx[1].getcenter() + spl1[-1] = cx + self.set_properties(data=self.make_data()) + except: + pass + +#------------------------------------------------------------------------------ +class Node_codeblock(Group): + + def __init__(self,code): + Group.__init__(self,can_focus=True) + self.codebox = Rect(parent=self,can_focus=True) + self.code = Text(parent=self, + text=code, + font='monospace, 10', + use_markup=True, + can_focus=True, + fill_color='black') + self.padding = 4 + bbink,bblogic = self.code.get_natural_extents() + w = (bblogic[2]*0.001)+2*self.padding + h = (bblogic[3]*0.001)+2*self.padding + self.codebox.set_properties(width=w,height=h) + self.codebox.set_properties(fill_color='white', + stroke_color='black', + line_width=1) + self.code.raise_(self.codebox) + # shadow : + self.shadow = s = 4 + self.code.set_properties(x=self.padding,y=self.padding) + self.shadbox = Rect(x=s,y=s,width=w,height=h, + fill_color='grey44', + line_width=0) + self._wh = (w+s,h+s) + self.w,self.h = self._wh + self.cx = [] + self.add_child(self.shadbox,0) + self.shadbox.lower(self.codebox) + # events: + self.clicked=0 + self.connect("enter-notify-event",Node_codeblock.eventhandler) + self.connect("leave-notify-event",Node_codeblock.eventhandler) + self.connect("button-press-event",Node_codeblock.eventhandler) + self.connect("button-release-event",Node_codeblock.eventhandler) + self.connect("key-press-event",Node_codeblock.eventhandler) + self.connect("key-release-event",Node_codeblock.eventhandler) + self.connect("motion-notify-event",Node_codeblock.eventhandler) + self.connect('notify::transform',Node_codeblock.notifyhandler) + + def set_wh(self,wh): pass + def get_wh(self): + return self._wh + wh = property(get_wh,set_wh) + + # xy property is bound to center of object + def get_xy(self): + w,h = self.codebox.get_properties('width','height') + return (self.props.x+w/2.,self.props.y+h/2.) + def set_xy(self,xy): + w,h = self.codebox.get_properties('width','height') + self.props.x,self.props.y = xy[0]-w/2.,xy[1]-h/2. + xy = property(get_xy,set_xy) + + def intersect(self,topt,cx): + assert self.find_child(cx)!=-1 + bb = self.get_bounds() + w,h = self.codebox.get_properties('width','height') + x1,y1 = (w/2.,h/2.) + x2,y2 = topt[0]-bb.x1,topt[1]-bb.y1 + # now try all 4 segments of self rectangle: + S = [((x1,y1),(x2,y2),(0,0),(w,0)), + ((x1,y1),(x2,y2),(w,0),(w,h)), + ((x1,y1),(x2,y2),(0,h),(w,h)), + ((x1,y1),(x2,y2),(0,h),(0,0))] + for segs in S: + xy = intersect2lines(*segs) + if xy!=None: + cx.set_properties(x=xy[0],y=xy[1]) + break + + def highlight_on(self,style=None): + import re + if style is None: + style = {'addr': '%s', + 'code': '%s', + 'mnem': '%s', + 'strg': '%s', + 'cons': '%s', + 'comm': '%s', + } + lre = re.compile("(0x[0-9a-f]+ )('[0-9a-f]+' +)(.*)$") + hcode = [] + for l in self.code.get_properties('text')[0].splitlines(): + if l.startswith('#'): + hcode.append(style['comm']%l) + else: + m = lre.match(l) + if m is None: return + g = m.groups() + s = [style['addr']%g[0]] + s += [style['strg']%g[1]] + s += [style['code']%g[2]] + hcode.append(''.join(s)) + self.code.set_properties(text='\n'.join(hcode)) + self.code.set_properties(use_markup=True) + + def highlight_off(self): + import re + lre = re.compile("]+>(.*?)") + code = [] + for l in self.code.get_properties('text').splitlines(): + g = lre.findall(l) + if len(g)>0: code.append(''.join(g)) + self.code.set_properties(text='\n'.join(code)) + self.code.set_properties(use_markup=False) + + @mouse1moves + def eventhandler(self,item,e): + #print "*** CODEBLOCK EVENT =",e + if e.type is gtk.gdk.ENTER_NOTIFY: + self.codebox.set_properties(line_width=2.0) + elif e.type is gtk.gdk.LEAVE_NOTIFY: + self.codebox.set_properties(line_width=1.0) + return False + + def notifyhandler(self,prop): + #print "notify %s on "%prop.name,self + for cx in self.cx: + for e in cx.registered: e.update_points() + + def resrc(self,code): + self.code.props.text = code + bb = self.code.get_bounds() + w = (bb.x2-bb.x1)+self.padding + h = (bb.y2-bb.y1)+self.padding + self.codebox.set_properties(width=w,height=h) + self.shadbox.set_properties(width=w,height=h) + + +def intersect2lines((x1,y1),(x2,y2),(x3,y3),(x4,y4)): + b = (x2-x1,y2-y1) + d = (x4-x3,y4-y3) + det = b[0]*d[1] - b[1]*d[0] + if det==0: return None + c = (x3-x1,y3-y1) + t = float(c[0]*b[1] - c[1]*b[0])/(det*1.) + if (t<0. or t>1.): return None + t = float(c[0]*d[1] - c[1]*d[0])/(det*1.) + if (t<0. or t>1.): return None + x = x1 + t*b[0] + y = y1 + t*b[1] + return (x,y) diff --git a/amoco/ui/graphics/gtk_/mainwin.py b/amoco/ui/graphics/gtk_/mainwin.py new file mode 100644 index 0000000..efbdb76 --- /dev/null +++ b/amoco/ui/graphics/gtk_/mainwin.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 2008 Axel Tillequin (bdcht3@gmail.com) +# This code is part of Amoco +# published under GPLv2 license + +import pygtk +pygtk.require('2.0') +import gtk +import os + +#if os.name=='posix': +# gtk.gdk.threads_init() + +class gtkWindow(object): + def __init__(self): + self.window = None + self.canvas = None + self.events = None + self.gui = None + + # called by Masr init : + def initWindow(self): + # we provide only the main gtk window here, not the drawing area: + self.window = gtk.Window() + self.window.set_title("amoco-gtk") + # handler of events on the X11 window (close,kill,etc): + self.window.connect("destroy", gtk.main_quit) + self.window.connect("delete_event", gtk.main_quit) + self.window.set_border_width(0) + #self.window.fullscreen() + # add a vertical stacking box for menu/canvas/statusbar: + self.vbox = gtk.VBox() + self.window.add(self.vbox) + self.initCanvas() + + def initCanvas(self): + from .canvas import Canvas + # the canvas will be added to a scrollable window: + scrolled_window = gtk.ScrolledWindow() + scrolled_window.set_border_width(2) + ## always show h,v scroll bars + scrolled_window.set_policy(gtk.POLICY_ALWAYS, gtk.POLICY_ALWAYS) + # create canvas object which holds the layout: + self.canvas = Canvas(parent=scrolled_window) + # add it to vpan widget: + self.vpan = gtk.VPaned() + self.vbox.add(self.vpan) + self.vpan.add(scrolled_window) + + def mainLoop(self): + # create the gtk window and everything in it: + self.window.show_all() + self.window.set_focus(self.canvas) + # let gtk handle the event loop: + gtk.main() diff --git a/amoco/ui/graphics/kivy_/engine.py b/amoco/ui/graphics/kivy_/engine.py new file mode 100644 index 0000000..a68b8bf --- /dev/null +++ b/amoco/ui/graphics/kivy_/engine.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- + +def setw(view,w): + pass +def getw(view): + pass +def seth(view,h): + pass +def geth(view): + pass +def setxy(view,xy): + pass +def getxy(view): + pass diff --git a/amoco/ui/graphics/qt_/engine.py b/amoco/ui/graphics/qt_/engine.py new file mode 100644 index 0000000..ccfe68b --- /dev/null +++ b/amoco/ui/graphics/qt_/engine.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +from PyQt5.QtWidgets import QApplication + +from amoco.ui.render import Formats + +from .graphwin import * +from .mainwin import * + +app = QApplication([]) + +def builder(view): + if view._is_block: + fmt = Formats['Html'] + tbl = view._vltable(formatter=fmt) + tbl.rowparams['head'] = u'' + tbl.rowparams['sep'] = u'' + tbl.rowparams['tail'] = u'' + tbl.header = u"\n\n\n"%fmt.get_style_defs() + tbl.footer = u'
\n\n' + return Node_codeblock(str(tbl)) + elif view._is_func: + r = len(view.of.blocks)*5 + return Node_basic(str(view.of),r) + else: + return Node_basic(str(view.of)) + +def setw(view,w): + pass + +def seth(view,h): + pass + +def getw(view): + return view.obj.boundingRect().width() + +def geth(view): + return view.obj.boundingRect().height() + +def setxy(view,xy): + view.obj.setPos( QPointF(*xy)-view.obj.center() ) + +def getxy(view): + pt = view.obj.center() + return (pt.x(),pt.y()) + + + diff --git a/amoco/ui/graphics/qt_/graphwin.py b/amoco/ui/graphics/qt_/graphwin.py new file mode 100644 index 0000000..5e928c9 --- /dev/null +++ b/amoco/ui/graphics/qt_/graphwin.py @@ -0,0 +1,69 @@ +from .items import * +from grandalf.routing import * + +class GraphScene(QGraphicsScene): + + def __init__(self,sug=None): + super(GraphScene,self).__init__() + p = QPen() + p.setColor(QColor('red')) + self.addLine(-5,0,5,0,p) + self.addLine(0,-5,0,5,p) + self.sug = sug + if self.sug: + self.sug.route_edge = route_with_lines + self.sug.dx,self.sug.dy = 5,5 + self.sug.dirvh=0 + for n in self.sug.g.sV: self.connect_add(n.view) + for e in self.sug.g.sE: + e.view = Edge_basic(e.v[0].view.obj,e.v[1].view.obj) + self.addItem(e.view) + + def connect_add(self,nv): + self.addItem(nv.obj) + + def Draw(self,N=1,stepflag=False,constrained=False,opt=False): + #self.sug.init_all(cons=constrained,optimize=opt) + if stepflag: + self.drawer=self.sug.draw_step() + self.greens=[] + else: + self.sug.draw(N) + #for e in self.sug.alt_e: e.view.set_properties(stroke_color='red') + for e in self.sug.g.sE: + #self.parent.root.add_child(e.view) + # move edge start/end to CX points: + e.view.update_points() + +from math import pow +from PyQt5.QtCore import Qt,QRectF + +class GraphView(QGraphicsView): + + def __init__(self,scene): + super(GraphView,self).__init__(scene) + self.setRenderHints(QPainter.Antialiasing) + self.setBackgroundBrush(QBrush(QColor('#dff'))) + #self.setViewportUpdateMode(QGraphicsView.BoundingRectViewportUpdate) + #self.setDragMode(QGraphicsView.ScrollHandDrag) + #self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) + #self.setResizeAnchor(QGraphicsView.AnchorViewCenter) + + def keyPressEvent(self, event): + key = event.key() + if key == Qt.Key_Plus: + self.scaleView(1.2) + elif key == Qt.Key_Minus: + self.scaleView(1 / 1.2) + else: + super(GraphView, self).keyPressEvent(event) + + def wheelEvent(self, event): + self.scaleView(pow(2.0, -event.angleDelta().y() / 240.0)) + + def scaleView(self, scaleFactor): + factor = self.transform().scale(scaleFactor, scaleFactor).mapRect(QRectF(0, 0, 1, 1)).width() + if factor < 0.07 or factor > 100: + return + self.scale(scaleFactor, scaleFactor) + diff --git a/amoco/ui/graphics/qt_/items.py b/amoco/ui/graphics/qt_/items.py new file mode 100644 index 0000000..2f6158b --- /dev/null +++ b/amoco/ui/graphics/qt_/items.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- +from PyQt5.QtCore import pyqtSignal +from PyQt5.QtWidgets import * +from PyQt5.QtGui import QPainter +from PyQt5.Qt import * + +from math import sin,cos,pi,radians + +class Node_basic(QGraphicsItem): + + def __init__(self,name='?',r=10): + super(Node_basic,self).__init__() + self.setFlag(QGraphicsItem.ItemIsMovable) + self.setFlag(QGraphicsItem.ItemIsFocusable) + self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) + self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) + self.setAcceptHoverEvents(True) + # define circle shape: + w = 2*r+2 + self.el = QGraphicsEllipseItem(0,0,w,w) + self.el.setBrush(QBrush(QColor('white'))) + shadow = QGraphicsDropShadowEffect() + shadow.setOffset(4) + self.el.setGraphicsEffect(shadow) + self.el.setParentItem(self) + # define node label shape: + self.label = QGraphicsTextItem(name) + self.label.setDefaultTextColor(QColor('blue')) + self.label.setFlag(QGraphicsItem.ItemIsSelectable) + self.label.setParentItem(self) + self.el.setZValue(1.) + self.label.setZValue(2.) + center = self.center()-self.label.boundingRect().center() + self.label.setPos(self.mapFromScene(center)) + self.setZValue(1.) + self.cx = [] + + def boundingRect(self): + e = self.el.boundingRect() + l = self.label.boundingRect() + l = self.mapRectToItem(self,l) + return e.united(l) + + def shape(self): + e = self.el.shape() + l = self.label.shape() + l = self.mapToItem(self,l) + return e.united(l) + + def paint(self,painter,option,widget=None): + pass + + def center(self): + return self.el.sceneBoundingRect().center() + + def focusOutEvent(self, event): + self.label.setTextInteractionFlags(Qt.NoTextInteraction) + super(Node_basic, self).focusOutEvent(event) + + def mouseDoubleClickEvent(self, event): + if self.label.textInteractionFlags() == Qt.NoTextInteraction: + self.label.setTextInteractionFlags(Qt.TextEditorInteraction) + super(Node_basic, self).mouseDoubleClickEvent(event) + + def itemChange(self, change, value): + if change == QGraphicsItem.ItemPositionHasChanged: + for e in self.cx: + e.update_points() + return super(Node_basic, self).itemChange(change, value) + + def contextMenuEvent(self, event): + menu = QMenu() + testAction = QAction('Test', None) + testAction.triggered.connect(self.print_out) + menu.addAction(testAction) + menu.exec_(event.screenPos()) + + def print_out(self): + print 'Triggered' + +#------------------------------------------------------------------------------ + +class Node_codeblock(QGraphicsItem): + def __init__(self,block): + super(Node_codeblock,self).__init__() + self.setFlag(QGraphicsItem.ItemIsMovable) + self.setFlag(QGraphicsItem.ItemIsFocusable) + self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) + self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) + self.setAcceptHoverEvents(True) + # define code text shape: + self.code = QGraphicsTextItem() + self.code.setHtml(block) + f = QFont('Monospace') + f.setPointSize(8) + self.code.setFont(f) + self.code.setParentItem(self) + # define circle shape: + self.codebox = QGraphicsRectItem(self.code.boundingRect().adjusted(-2,-2,2,2)) + self.codebox.setBrush(QBrush(QColor('#fdf6e3'))) + shadow = QGraphicsDropShadowEffect() + shadow.setOffset(4) + self.codebox.setGraphicsEffect(shadow) + self.codebox.setParentItem(self) + self.codebox.setZValue(1.) + self.code.setZValue(2.) + center = self.codebox.boundingRect().center()-self.code.boundingRect().center() + self.code.setPos(center) + self.setZValue(1.) + self.cx = [] + + def boundingRect(self): + b = self.codebox.boundingRect() + return b + + def center(self): + return self.codebox.sceneBoundingRect().center() + + def shape(self): + return self.codebox.shape() + + def paint(self,painter,option,widget=None): + pass + + def hoverEnterEvent(self, event): + self.codebox.setBrush(QBrush(QColor('white'))) + super(Node_codeblock, self).hoverEnterEvent(event) + + def hoverLeaveEvent(self, event): + self.codebox.setBrush(QBrush(QColor('#fdf6e3'))) + self.code.setTextInteractionFlags(Qt.NoTextInteraction) + super(Node_codeblock, self).hoverLeaveEvent(event) + + def mouseDoubleClickEvent(self, event): + if self.code.textInteractionFlags() == Qt.NoTextInteraction: + self.code.setTextInteractionFlags(Qt.TextEditorInteraction) + super(Node_codeblock, self).mouseDoubleClickEvent(event) + + def itemChange(self, change, value): + if change == QGraphicsItem.ItemPositionHasChanged: + for e in self.cx: + e.update_points() + return super(Node_codeblock, self).itemChange(change, value) + +#------------------------------------------------------------------------------ +class Edge_basic(QGraphicsItem): + def __init__(self,n0,n1): + super(Edge_basic,self).__init__() + self.n = [n0,n1] + n0.cx.append(self) + n1.cx.append(self) + self.points = [None,None] + self.update_points() + + def setpath(self,l): + self.points = [QPointF(*p) for p in l] + self.update_points() + + def update_points(self): + self.prepareGeometryChange() + self.points[0] = self.n[0].center() + self.points[-1] = self.n[1].center() + self.adjust() + + def adjust(self): + nend = self.n[1] + nendshape = nend.shape() + s = nend.mapToScene(nendshape) + x = s.intersected(self.shape()) + self.points[-1] = x.pointAtPercent(1.) + + def boundingRect(self): + return self.getqgp().boundingRect() + + def getqgp(self): + qpp = QPainterPath(self.points[0]) + for p in self.points[1:]: + qpp.lineTo(p) + return QGraphicsPathItem(qpp) + return qgp + + def shape(self): + return self.getqgp().shape() + + def paint(self,painter,option,widget=None): + qgp = self.getqgp() + pen = QPen() + pen.setWidth(2) + qgp.setPen(pen) + qgp.setBrush(QBrush(Qt.NoBrush)) + qgp.paint(painter,option,widget) + lastp = self.points[-1] + angle = radians(qgp.path().angleAtPercent(1.)) + angle = angle + pi + p = lastp+QPointF(cos(angle-pi/6.)*10,-sin(angle-pi/6.)*10) + q = lastp+QPointF(cos(angle+pi/6.)*10,-sin(angle+pi/6.)*10) + painter.setBrush(QBrush(QColor('black'))) + painter.drawPolygon(QPolygonF([lastp,p,q])) + diff --git a/amoco/ui/graphics/qt_/mainwin.py b/amoco/ui/graphics/qt_/mainwin.py new file mode 100644 index 0000000..4f2b36d --- /dev/null +++ b/amoco/ui/graphics/qt_/mainwin.py @@ -0,0 +1,63 @@ +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QMainWindow,QDockWidget +from PyQt5.QtGui import QFont,QKeySequence +from PyQt5.Qt import QBrush, QPen, QColor + +from .graphwin import GraphScene,GraphView +from .statswin import StatsWin + +class MainWindow(QMainWindow): + def __init__(self,z=None,session=None): + super(MainWindow, self).__init__() + self.setWindowTitle("amoco-g") + self.createSession(z,session) + self.createActions() + self.createMenus() + self.createDockWindows() + self.statusBar().showMessage("GUI ready") + + def createSession(self,z,session): + from amoco.db import Session + self.session = session or Session('/tmp/amoco.db') + self.z = z + self.statusBar().showMessage("session created (/tmp/amoco.db)") + + def createFuncGraph(self,f): + self.grscene = GraphScene(f.view.layout) + self.graphwin = GraphView(self.grscene) + self.setCentralWidget(self.graphwin) + self.grscene.Draw() + + def createActions(self): + pass + + def createMenus(self): + self.viewMenu = self.menuBar().addMenu("&View") + + def createDockWindows(self): + self.createDockStats() + self.createDockBin() + self.createDockMap() + self.createDockFuncs() + + def createDockStats(self): + dock = QDockWidget('stats',self) + dock.setAllowedAreas(Qt.TopDockWidgetArea) + dock.setFeatures(dock.DockWidgetClosable|dock.DockWidgetVerticalTitleBar) + self.statsui = StatsWin(dock,cur=self) + self.statsui.setMaximumHeight(100) + dock.setWidget(self.statsui) + self.addDockWidget(Qt.TopDockWidgetArea, dock) + a = dock.toggleViewAction() + self.viewMenu.addAction(a) + a.setEnabled(True) + + def createDockBin(self): + pass + + def createDockMap(self): + pass + + def createDockFuncs(self): + pass + diff --git a/amoco/ui/graphics/qt_/statswin.py b/amoco/ui/graphics/qt_/statswin.py new file mode 100644 index 0000000..07d5d4a --- /dev/null +++ b/amoco/ui/graphics/qt_/statswin.py @@ -0,0 +1,7 @@ +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import * +from PyQt5.QtWebEngineWidgets import QWebEngineView + +class StatsWin(QWebEngineView): + def __init__(self,parent=None,cur=None): + super(StatsWin,self).__init__(parent) diff --git a/amoco/ui/graphics/term.py b/amoco/ui/graphics/term.py index 5498db0..25dfc19 100644 --- a/amoco/ui/graphics/term.py +++ b/amoco/ui/graphics/term.py @@ -1,3 +1,33 @@ +# -*- coding: utf-8 -*- + class engine(object): - pass + @staticmethod + def setw(view,w): + pass + + @staticmethod + def getw(view): + if view._is_block: + return view._vltable().width + else: + return len(str(view.of)) + + @staticmethod + def seth(view,h): + pass + + @staticmethod + def geth(view): + if view._is_block: + return view._vltable().nrows + else: + return 1 + + @staticmethod + def setxy(view,xy): + view._xy = xy + + @staticmethod + def getxy(view): + return view._xy diff --git a/amoco/ui/render.py b/amoco/ui/render.py index 4db2752..86ead07 100644 --- a/amoco/ui/render.py +++ b/amoco/ui/render.py @@ -29,32 +29,52 @@ def format(self,tokensource,outfile): for t,v in tokensource: outfile.write(v) Formats = { - 'null':NullFormatter(), + 'Null':NullFormatter(), } else: logger.info("pygments package imported") has_pygments = True - class DefaultStyle(Style): + class DarkStyle(Style): default_style = "" styles = { - Token.Literal: '#fff', + #Token.Literal: '#fff', Token.Address: '#fb0', Token.Constant: '#f30', - Token.Prefix: '#fff', - Token.Mnemonic: 'bold #fff', + #Token.Prefix: '#fff', + Token.Mnemonic: 'bold', Token.Register: '#33f', Token.Memory: '#3ff', Token.Comment: '#8f8', - Token.Name: 'underline #fff', + Token.Name: 'underline', Token.Tainted: 'bold #f00', Token.Column: '#bbb', Token.Hide: '#222', } + class LightStyle(Style): + default_style = "" + styles = { + Token.Literal: '#000', + Token.Address: '#b58900', + Token.Constant: '#dc322f', + Token.Prefix: '#000', + Token.Mnemonic: 'bold', + Token.Register: '#268bd2', + Token.Memory: '#859900', + Token.Comment: '#93a1a1', + Token.Name: 'underline', + Token.Tainted: 'bold #f00', + Token.Column: '#222', + Token.Hide: '#bbb', + } + DefaultStyle = DarkStyle Formats = { 'Null':NullFormatter(), 'Terminal':TerminalFormatter(style=DefaultStyle), 'Terminal256':Terminal256Formatter(style=DefaultStyle), + 'TerminalDark':Terminal256Formatter(style=DarkStyle), + 'TerminalLight':Terminal256Formatter(style=LightStyle), + 'Html':HtmlFormatter(style=LightStyle,nowrap=True), } default_formatter = NullFormatter() @@ -71,6 +91,7 @@ def configure(**kargs): def highlight(toks,formatter=None,outfile=None): formatter = formatter or default_formatter + if isinstance(formatter,str): formatter = Formats[formatter] outfile = outfile or StringIO() formatter.format(toks,outfile) return outfile.getvalue() @@ -202,7 +223,7 @@ def __str__(self): class tokenrow(object): def __init__(self,toks=None): if toks is None: toks = [] - self.toks = toks + self.toks = [(t,unicode(s)) for (t,s) in toks] self.maxwidth = float('inf') self.align = '<' self.fill = ' ' @@ -246,10 +267,12 @@ def show(self,raw=False,**params): colsz = params.get('colsize') hidden_c = params.get('hidden_c',set()) squash_c = params.get('squash_c',True) + head = params.get('head','') + tail = params.get('tail','') if raw: - formatter=None + formatter='Null' outfile=None - r = [] + r = [head] tz = 0 for i,c in enumerate(self.cols): toks = [] @@ -271,13 +294,12 @@ def show(self,raw=False,**params): pad = fill*(mz-sz) if align=='<': toks[-1][1] += pad elif align=='>': toks[0][1] = pad+toks[0][1] - if tt==Token.Column: - if sep: tv=sep - toks.append((tt,tv)) if i in hidden_c: if not squash_c: - toks = [(TokenHide,highlight(toks,None,None))] + toks = [(TokenHide,highlight(toks,'Null',None))] else: toks = [] r.append(highlight(toks,formatter,outfile)) + if tt==Token.Column and sep: r.append(sep) + r.append(tail) return ''.join(r) diff --git a/amoco/ui/views.py b/amoco/ui/views.py index f83e71a..b4d8dbb 100644 --- a/amoco/ui/views.py +++ b/amoco/ui/views.py @@ -6,26 +6,81 @@ from grandalf.layouts import SugiyamaLayout -from amoco.ui import graphics +from amoco.ui.graphics import Engine +from amoco.ui.render import Token,vltable -class View(object): - backend = graphics.default +class View(Engine): + _is_block = False + _is_func = False + _is_xfunc = False - def __init__(self,w=1,h=1,of=None): - self.w = w - self.h = h - self.xy = None + def __init__(self,of=None): self.of = of + @property + def obj(self): + try: + return self._obj + except AttributeError: + self._obj = self.engine.builder(self) + return self._obj + + def setw(self,w): + self.engine.setw(self,w) + def getw(self): + return self.engine.getw(self) + w = property(getw,setw) + + def seth(self,h): + self.engine.seth(self,h) + def geth(self): + return self.engine.geth(self) + h = property(geth,seth) + + def setxy(self,xy): + self.engine.setxy(self,xy) + def getxy(self): + return self.engine.getxy(self) + xy = property(getxy,setxy) + + class blockView(View): + _is_block = True + def __init__(self,block): super(blockView,self).__init__(of=block) + def _vltable(self,**kargs): + T = vltable(**kargs) + n = len(self.of.instr) + for i in self.of.instr: + ins2 = i.toks() + if isinstance(ins2,str): ins2 = [(Token.Literal,ins2)] + ins = [ (Token.Address,'{:<10}'.format(i.address)), + (Token.Column,''), + (Token.Literal,"'%s'"%(i.bytes.encode('hex'))), + (Token.Column,'') ] + T.addrow(ins+ins2) + if conf.getboolean('block','bytecode'): + pad = conf.getint('block','padding') or 0 + T.colsize[1] += pad + if conf.getboolean('block','header'): + T.header = ('# --- block %s ---' % self.of.name).ljust(T.width,'-') + if conf.getboolean('block','footer'): + T.footer = '-'*T.width + return T + + def __str__(self): + return str(self._vltable()) + + class funcView(View): + _is_func = True def __init__(self,func): super(funcView,self).__init__(of=func) self.layout = SugiyamaLayout(func.cfg) class xfuncView(View): + _is_xfunc = True def __init__(self,xfunc): super(xfuncView,self).__init__(of=xfunc) diff --git a/tests/samples/x86/puttygen.exe b/tests/samples/x86/puttygen.exe new file mode 100644 index 0000000000000000000000000000000000000000..7dfc6aa6b6ed5a17bca53d918fbf00747508cb40 GIT binary patch literal 180224 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 literal 0 HcmV?d00001 diff --git a/tests/test_arch_x86.py b/tests/test_arch_x86.py index ecb54e3..2e09f5f 100644 --- a/tests/test_arch_x86.py +++ b/tests/test_arch_x86.py @@ -159,6 +159,19 @@ def test_decoder_019(): i = cpu.disassemble(c) assert str(i.operands[0])=='M64(ecx)' +# movzx eax, byte ptr [eax+0x806fb088] +def test_decoder_020(): + c = '\x0f\xb6\x80\x88\xb0\x6f\x80' + i = cpu.disassemble(c) + assert str(i.operands[1])=='M8(eax-2140163960)' + assert i.toks()[-1][1] == 'byte ptr [eax+0x806fb088]' + +# mov eax, [esp-300] +def test_decoder_021(): + c = '\x8b\x84\x24\xd4\xfe\xff\xff' + i = cpu.disassemble(c) + assert str(i.operands[1])=='M32(esp-300)' + assert i.toks()[-1][1] == '[esp-300]' #------------------------------------------------------------------------------ diff --git a/tests/test_ui_qt.py b/tests/test_ui_qt.py new file mode 100644 index 0000000..3e4d4bd --- /dev/null +++ b/tests/test_ui_qt.py @@ -0,0 +1,16 @@ +import amoco +from amoco.ui.graphics.qt_.engine import * +amoco.ui.graphics.configure(graphics="qt") + +p = amoco.system.loader.load_program('samples/x86/flow.elf') +z = amoco.lbackward(p) +z.getcfg() +f = z.G.C[2].sV[0].data.misc['func'] + +w = MainWindow(z) +w.createFuncGraph(f) +#gs = GraphScene(f.view.layout) +#gv = GraphView(gs) +#gs.Draw() +#gv.show() +w.show()