mirror of
https://github.com/cemu-project/idapython.git
synced 2024-11-30 21:14:20 +01:00
Custom Viewer:
- renamed customview to customviewer - added GetCurrentWord()
This commit is contained in:
parent
0c11d8f170
commit
4bd83af5a3
@ -4,22 +4,26 @@
|
|||||||
#
|
#
|
||||||
import idaapi
|
import idaapi
|
||||||
import idc
|
import idc
|
||||||
from idaapi import simplecustview_t
|
from idaapi import simplecustviewer_t
|
||||||
#<pycode(py_custviewex1)>
|
#<pycode(py_custviewerex1)>
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
class mycv_t(simplecustview_t):
|
class mycv_t(simplecustviewer_t):
|
||||||
def Create(self, sn=None):
|
def Create(self, sn=None):
|
||||||
# Form the title
|
# Form the title
|
||||||
title = "Simple custom view test"
|
title = "Simple custom view test"
|
||||||
if sn:
|
if sn:
|
||||||
title += " %d" % sn
|
title += " %d" % sn
|
||||||
|
|
||||||
# Create the customview
|
# Create the customview
|
||||||
if not simplecustview_t.Create(self, title):
|
if not simplecustviewer_t.Create(self, title):
|
||||||
return False
|
return False
|
||||||
id = self.AddPopupMenu("Hello")
|
self.menu_hello = self.AddPopupMenu("Hello")
|
||||||
|
self.menu_world = self.AddPopupMenu("World")
|
||||||
|
|
||||||
for i in xrange(0, 100):
|
for i in xrange(0, 100):
|
||||||
self.AddLine("Line %d" % i)
|
self.AddLine("Line %d" % i)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def OnClick(self, shift):
|
def OnClick(self, shift):
|
||||||
@ -37,7 +41,9 @@ class mycv_t(simplecustview_t):
|
|||||||
@param shift: Shift flag
|
@param shift: Shift flag
|
||||||
@return Boolean. True if you handled the event
|
@return Boolean. True if you handled the event
|
||||||
"""
|
"""
|
||||||
print "OnDblClick, shift=%d" % shift
|
word = self.GetCurrentWord()
|
||||||
|
if not word: word = "<None>"
|
||||||
|
print "OnDblClick, shift=%d, current word=%s" % (shift, word)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def OnCursorPosChanged(self):
|
def OnCursorPosChanged(self):
|
||||||
@ -67,11 +73,14 @@ class mycv_t(simplecustview_t):
|
|||||||
self.Close()
|
self.Close()
|
||||||
elif vkey == 46:
|
elif vkey == 46:
|
||||||
n = self.GetLineNo()
|
n = self.GetLineNo()
|
||||||
|
if n is not None:
|
||||||
self.DelLine(n)
|
self.DelLine(n)
|
||||||
self.Refresh()
|
self.Refresh()
|
||||||
print "Deleted line %d" % n
|
print "Deleted line %d" % n
|
||||||
# Goto?
|
# Goto?
|
||||||
elif vkey == ord('G'):
|
elif vkey == ord('G'):
|
||||||
|
n = self.GetLineNo()
|
||||||
|
if n is not None:
|
||||||
v = idc.AskLong(self.GetLineNo(), "Where to go?")
|
v = idc.AskLong(self.GetLineNo(), "Where to go?")
|
||||||
if v:
|
if v:
|
||||||
self.Jump(v, 0, 5)
|
self.Jump(v, 0, 5)
|
||||||
@ -128,10 +137,17 @@ class mycv_t(simplecustview_t):
|
|||||||
def OnPopupMenu(self, menu_id):
|
def OnPopupMenu(self, menu_id):
|
||||||
"""
|
"""
|
||||||
A context (or popup) menu item was executed.
|
A context (or popup) menu item was executed.
|
||||||
@param menu_id: ID previously registered with add_popup_menu()
|
@param menu_id: ID previously registered with AddPopupMenu()
|
||||||
@return: Boolean
|
@return: Boolean
|
||||||
"""
|
"""
|
||||||
print "OnPopupMenu, menu_id=%d" % menu_id
|
print "OnPopupMenu, menu_id=%d" % menu_id
|
||||||
|
if menu_id == self.menu_hello:
|
||||||
|
print "Hello"
|
||||||
|
elif menu_id == self.menu_world:
|
||||||
|
print "World"
|
||||||
|
else:
|
||||||
|
# Unhandled
|
||||||
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
@ -156,11 +172,13 @@ if not mycv:
|
|||||||
del mycv
|
del mycv
|
||||||
|
|
||||||
def make_many(n):
|
def make_many(n):
|
||||||
|
L = []
|
||||||
for i in xrange(1, n+1):
|
for i in xrange(1, n+1):
|
||||||
t = mycv_t()
|
v = mycv_t()
|
||||||
if not t.Create(i):
|
if not v.Create(i):
|
||||||
break
|
break
|
||||||
t.Show()
|
v.Show()
|
||||||
return i
|
L.append(v)
|
||||||
|
return L
|
||||||
|
|
||||||
#</pycode(py_custviewex1)>
|
#</pycode(py_custviewerex1)>
|
134
swig/kernwin.i
134
swig/kernwin.i
@ -75,11 +75,11 @@ def askseg(defval, format):
|
|||||||
|
|
||||||
%{
|
%{
|
||||||
|
|
||||||
//<code(py_custview)>
|
//<code(py_custviewer)>
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
// Base class for all custview place_t providers
|
// Base class for all custviewer place_t providers
|
||||||
class custview_data_t
|
class custviewer_data_t
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual void *get_ud() = 0;
|
virtual void *get_ud() = 0;
|
||||||
@ -88,7 +88,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
class cvdata_simpleline_t: public custview_data_t
|
class cvdata_simpleline_t: public custviewer_data_t
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
strvec_t lines;
|
strvec_t lines;
|
||||||
@ -192,13 +192,13 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
class customview_t
|
class customviewer_t
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
qstring _title;
|
qstring _title;
|
||||||
TForm *_form;
|
TForm *_form;
|
||||||
TCustomControl *_cv;
|
TCustomControl *_cv;
|
||||||
custview_data_t *_data;
|
custviewer_data_t *_data;
|
||||||
int _features;
|
int _features;
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
@ -214,9 +214,9 @@ private:
|
|||||||
struct pyw_popupctx_t
|
struct pyw_popupctx_t
|
||||||
{
|
{
|
||||||
size_t menu_id;
|
size_t menu_id;
|
||||||
customview_t *cv;
|
customviewer_t *cv;
|
||||||
pyw_popupctx_t(): menu_id(0), cv(NULL) { }
|
pyw_popupctx_t(): menu_id(0), cv(NULL) { }
|
||||||
pyw_popupctx_t(size_t mid, customview_t *v): menu_id(mid), cv(v) { }
|
pyw_popupctx_t(size_t mid, customviewer_t *v): menu_id(mid), cv(v) { }
|
||||||
};
|
};
|
||||||
typedef std::map<unsigned int, pyw_popupctx_t> pyw_popupmap_t;
|
typedef std::map<unsigned int, pyw_popupctx_t> pyw_popupmap_t;
|
||||||
static pyw_popupmap_t _global_popup_map;
|
static pyw_popupmap_t _global_popup_map;
|
||||||
@ -226,7 +226,7 @@ private:
|
|||||||
|
|
||||||
static bool idaapi s_popup_cb(void *ud)
|
static bool idaapi s_popup_cb(void *ud)
|
||||||
{
|
{
|
||||||
customview_t *_this = (customview_t *)ud;
|
customviewer_t *_this = (customviewer_t *)ud;
|
||||||
return _this->on_popup();
|
return _this->on_popup();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,38 +241,38 @@ private:
|
|||||||
|
|
||||||
static bool idaapi s_cv_keydown(TCustomControl * /*cv*/, int vk_key, int shift, void *ud)
|
static bool idaapi s_cv_keydown(TCustomControl * /*cv*/, int vk_key, int shift, void *ud)
|
||||||
{
|
{
|
||||||
customview_t *_this = (customview_t *)ud;
|
customviewer_t *_this = (customviewer_t *)ud;
|
||||||
return _this->on_keydown(vk_key, shift);
|
return _this->on_keydown(vk_key, shift);
|
||||||
}
|
}
|
||||||
// The popup menu is being constructed
|
// The popup menu is being constructed
|
||||||
static void idaapi s_cv_popup(TCustomControl * /*cv*/, void *ud)
|
static void idaapi s_cv_popup(TCustomControl * /*cv*/, void *ud)
|
||||||
{
|
{
|
||||||
customview_t *_this = (customview_t *)ud;
|
customviewer_t *_this = (customviewer_t *)ud;
|
||||||
_this->on_popup();
|
_this->on_popup();
|
||||||
}
|
}
|
||||||
// The user clicked
|
// The user clicked
|
||||||
static bool idaapi s_cv_click(TCustomControl *cv, int shift, void *ud)
|
static bool idaapi s_cv_click(TCustomControl *cv, int shift, void *ud)
|
||||||
{
|
{
|
||||||
customview_t *_this = (customview_t *)ud;
|
customviewer_t *_this = (customviewer_t *)ud;
|
||||||
return _this->on_click(shift);
|
return _this->on_click(shift);
|
||||||
}
|
}
|
||||||
// The user double clicked
|
// The user double clicked
|
||||||
static bool idaapi s_cv_dblclick(TCustomControl * /*cv*/, int shift, void *ud)
|
static bool idaapi s_cv_dblclick(TCustomControl * /*cv*/, int shift, void *ud)
|
||||||
{
|
{
|
||||||
customview_t *_this = (customview_t *)ud;
|
customviewer_t *_this = (customviewer_t *)ud;
|
||||||
return _this->on_dblclick(shift);
|
return _this->on_dblclick(shift);
|
||||||
}
|
}
|
||||||
// Cursor position has been changed
|
// Cursor position has been changed
|
||||||
static void idaapi s_cv_curpos(TCustomControl * /*cv*/, void *ud)
|
static void idaapi s_cv_curpos(TCustomControl * /*cv*/, void *ud)
|
||||||
{
|
{
|
||||||
customview_t *_this = (customview_t *)ud;
|
customviewer_t *_this = (customviewer_t *)ud;
|
||||||
_this->on_curpos_changed();
|
_this->on_curpos_changed();
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
static int idaapi s_ui_cb(void *ud, int code, va_list va)
|
static int idaapi s_ui_cb(void *ud, int code, va_list va)
|
||||||
{
|
{
|
||||||
customview_t *_this = (customview_t *)ud;
|
customviewer_t *_this = (customviewer_t *)ud;
|
||||||
switch ( code )
|
switch ( code )
|
||||||
{
|
{
|
||||||
case ui_get_custom_viewer_hint:
|
case ui_get_custom_viewer_hint:
|
||||||
@ -331,12 +331,12 @@ public:
|
|||||||
_form = NULL;
|
_form = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
customview_t()
|
customviewer_t()
|
||||||
{
|
{
|
||||||
init_vars();
|
init_vars();
|
||||||
}
|
}
|
||||||
|
|
||||||
~customview_t()
|
~customviewer_t()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -386,6 +386,38 @@ public:
|
|||||||
return jumpto(pl, x, y);
|
return jumpto(pl, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------
|
||||||
|
bool get_current_word(bool mouse, qstring &word)
|
||||||
|
{
|
||||||
|
// query the cursor position
|
||||||
|
int x, y;
|
||||||
|
if ( get_place(mouse, &x, &y) == NULL )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// query the line at the cursor
|
||||||
|
const char *line = get_current_line(mouse, true);
|
||||||
|
if ( line == NULL )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ( x >= (int)strlen(line) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// find the beginning of the word
|
||||||
|
const char *ptr = line + x;
|
||||||
|
while ( ptr > line && !isspace(ptr[-1]) )
|
||||||
|
ptr--;
|
||||||
|
|
||||||
|
// find the end of the word
|
||||||
|
const char *begin = ptr;
|
||||||
|
ptr = line + x;
|
||||||
|
while ( !isspace(*ptr) && *ptr != '\0' )
|
||||||
|
ptr++;
|
||||||
|
|
||||||
|
word.qclear();
|
||||||
|
word.append(begin, ptr-begin);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
const char *get_current_line(bool mouse, bool notags)
|
const char *get_current_line(bool mouse, bool notags)
|
||||||
{
|
{
|
||||||
@ -434,7 +466,7 @@ public:
|
|||||||
{
|
{
|
||||||
size_t menu_id = _global_popup_id + 1;
|
size_t menu_id = _global_popup_id + 1;
|
||||||
// Overlap / already exists?
|
// Overlap / already exists?
|
||||||
if (_cv == NULL || // No custview?
|
if (_cv == NULL || // No custviewer?
|
||||||
menu_id == 0 || // Overlap?
|
menu_id == 0 || // Overlap?
|
||||||
_global_popup_map.find(menu_id) != _global_popup_map.end()) // Already exists?
|
_global_popup_map.find(menu_id) != _global_popup_map.end()) // Already exists?
|
||||||
{
|
{
|
||||||
@ -452,7 +484,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------
|
//--------------------------------------------------------------------------
|
||||||
bool create(const char *title, int features, custview_data_t *data)
|
bool create(const char *title, int features, custviewer_data_t *data)
|
||||||
{
|
{
|
||||||
// Already created? (in the instance)
|
// Already created? (in the instance)
|
||||||
if ( _form != NULL )
|
if ( _form != NULL )
|
||||||
@ -516,10 +548,10 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
customview_t::pyw_popupmap_t customview_t::_global_popup_map;
|
customviewer_t::pyw_popupmap_t customviewer_t::_global_popup_map;
|
||||||
size_t customview_t::_global_popup_id = 0;
|
size_t customviewer_t::_global_popup_id = 0;
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
class py_simplecustview_t: public customview_t
|
class py_simplecustview_t: public customviewer_t
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
cvdata_simpleline_t data;
|
cvdata_simpleline_t data;
|
||||||
@ -735,7 +767,7 @@ public:
|
|||||||
|
|
||||||
bool jumpto(size_t ln, int x, int y)
|
bool jumpto(size_t ln, int x, int y)
|
||||||
{
|
{
|
||||||
return customview_t::jumpto(&simpleline_place_t(ln), x, y);
|
return customviewer_t::jumpto(&simpleline_place_t(ln), x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initializes and links the Python object to this class
|
// Initializes and links the Python object to this class
|
||||||
@ -788,7 +820,7 @@ public:
|
|||||||
if ( !init(py_last_link, _title.c_str()) )
|
if ( !init(py_last_link, _title.c_str()) )
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return customview_t::show();
|
return customviewer_t::show();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get_selection(size_t *x1, size_t *y1, size_t *x2, size_t *y2)
|
bool get_selection(size_t *x1, size_t *y1, size_t *x2, size_t *y2)
|
||||||
@ -828,7 +860,7 @@ public:
|
|||||||
return py_this;
|
return py_this;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
//</code(py_custview)>
|
//</code(py_custviewer)>
|
||||||
|
|
||||||
bool idaapi py_menu_item_callback(void *userdata)
|
bool idaapi py_menu_item_callback(void *userdata)
|
||||||
{
|
{
|
||||||
@ -862,10 +894,10 @@ bool idaapi py_menu_item_callback(void *userdata)
|
|||||||
|
|
||||||
%rename (add_menu_item) wrap_add_menu_item;
|
%rename (add_menu_item) wrap_add_menu_item;
|
||||||
%inline %{
|
%inline %{
|
||||||
//<inline(py_custview)>
|
//<inline(py_custviewer)>
|
||||||
|
|
||||||
//
|
//
|
||||||
// Pywraps Simple Custom View functions
|
// Pywraps Simple Custom Viewer functions
|
||||||
//
|
//
|
||||||
PyObject *pyscv_init(PyObject *py_link, const char *title)
|
PyObject *pyscv_init(PyObject *py_link, const char *title)
|
||||||
{
|
{
|
||||||
@ -1022,6 +1054,18 @@ PyObject *pyscv_get_selection(PyObject *py_this)
|
|||||||
return _this->py_get_selection();
|
return _this->py_get_selection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyObject *pyscv_get_current_word(PyObject *py_this, bool mouse)
|
||||||
|
{
|
||||||
|
DECL_THIS;
|
||||||
|
if ( _this != NULL )
|
||||||
|
{
|
||||||
|
qstring word;
|
||||||
|
if ( _this->get_current_word(mouse, word) )
|
||||||
|
return PyString_FromString(word.c_str());
|
||||||
|
}
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
// Edits an existing line
|
// Edits an existing line
|
||||||
bool pyscv_edit_line(PyObject *py_this, size_t nline, PyObject *py_sl)
|
bool pyscv_edit_line(PyObject *py_this, size_t nline, PyObject *py_sl)
|
||||||
{
|
{
|
||||||
@ -1029,7 +1073,7 @@ bool pyscv_edit_line(PyObject *py_this, size_t nline, PyObject *py_sl)
|
|||||||
return _this == NULL ? false : _this->edit_line(nline, py_sl);
|
return _this == NULL ? false : _this->edit_line(nline, py_sl);
|
||||||
}
|
}
|
||||||
#undef DECL_THIS
|
#undef DECL_THIS
|
||||||
//</inline(py_custview)>
|
//</inline(py_custviewer)>
|
||||||
|
|
||||||
//<inline(py_choose2)>
|
//<inline(py_choose2)>
|
||||||
#ifdef CH_ATTRS
|
#ifdef CH_ATTRS
|
||||||
@ -1798,8 +1842,8 @@ class Choose:
|
|||||||
"""
|
"""
|
||||||
return _idaapi.choose_choose(self, self.flags, self.x0, self.y0, self.x1, self.y1, self.width)
|
return _idaapi.choose_choose(self, self.flags, self.x0, self.y0, self.x1, self.y1, self.width)
|
||||||
|
|
||||||
#<pycode(py_custview)>
|
#<pycode(py_custviewer)>
|
||||||
class simplecustview_t(object):
|
class simplecustviewer_t(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.this = None
|
self.this = None
|
||||||
@ -1875,11 +1919,17 @@ class simplecustview_t(object):
|
|||||||
return _idaapi.pyscv_add_line(self.this, self.make_sl_arg(line, fgcolor, bgcolor))
|
return _idaapi.pyscv_add_line(self.this, self.make_sl_arg(line, fgcolor, bgcolor))
|
||||||
|
|
||||||
def InsertLine(self, lineno, line, fgcolor=None, bgcolor=None):
|
def InsertLine(self, lineno, line, fgcolor=None, bgcolor=None):
|
||||||
"""Inserts a line in the given position"""
|
"""
|
||||||
|
Inserts a line in the given position
|
||||||
|
@return Boolean
|
||||||
|
"""
|
||||||
return _idaapi.pyscv_insert_line(self.this, lineno, self.make_sl_arg(line, fgcolor, bgcolor))
|
return _idaapi.pyscv_insert_line(self.this, lineno, self.make_sl_arg(line, fgcolor, bgcolor))
|
||||||
|
|
||||||
def EditLine(self, lineno, line, fgcolor=None, bgcolor=None):
|
def EditLine(self, lineno, line, fgcolor=None, bgcolor=None):
|
||||||
"""Edits an existing line"""
|
"""
|
||||||
|
Edits an existing line.
|
||||||
|
@return Boolean
|
||||||
|
"""
|
||||||
return _idaapi.pyscv_edit_line(self.this, lineno, self.make_sl_arg(line, fgcolor, bgcolor))
|
return _idaapi.pyscv_edit_line(self.this, lineno, self.make_sl_arg(line, fgcolor, bgcolor))
|
||||||
|
|
||||||
def PatchLine(self, lineno, offs, value):
|
def PatchLine(self, lineno, offs, value):
|
||||||
@ -1887,11 +1937,29 @@ class simplecustview_t(object):
|
|||||||
return _idaapi.pyscv_patch_line(self.this, lineno, offs, value)
|
return _idaapi.pyscv_patch_line(self.this, lineno, offs, value)
|
||||||
|
|
||||||
def DelLine(self, lineno):
|
def DelLine(self, lineno):
|
||||||
|
"""
|
||||||
|
Deletes an existing line
|
||||||
|
@return Boolean
|
||||||
|
"""
|
||||||
return _idaapi.pyscv_del_line(self.this, lineno)
|
return _idaapi.pyscv_del_line(self.this, lineno)
|
||||||
|
|
||||||
def GetLine(self, lineno):
|
def GetLine(self, lineno):
|
||||||
|
"""
|
||||||
|
Returns a line
|
||||||
|
@param lineno: The line number
|
||||||
|
@return:
|
||||||
|
Returns a tuple (colored_line, fgcolor, bgcolor) or None
|
||||||
|
"""
|
||||||
return _idaapi.pyscv_get_line(self.this, lineno)
|
return _idaapi.pyscv_get_line(self.this, lineno)
|
||||||
|
|
||||||
|
def GetCurrentWord(self, mouse = 0):
|
||||||
|
"""
|
||||||
|
Returns the current word
|
||||||
|
@param mouse: Use mouse position or cursor position
|
||||||
|
@return: None if failed or a String containing the current word at mouse or cursor
|
||||||
|
"""
|
||||||
|
return _idaapi.pyscv_get_current_word(self.this, mouse)
|
||||||
|
|
||||||
def GetCurrentLine(self, mouse = 0, notags = 0):
|
def GetCurrentLine(self, mouse = 0, notags = 0):
|
||||||
"""
|
"""
|
||||||
Returns the current line.
|
Returns the current line.
|
||||||
@ -2008,7 +2076,7 @@ class simplecustview_t(object):
|
|||||||
# print "OnPopupMenu, menu_id=" % menu_id
|
# print "OnPopupMenu, menu_id=" % menu_id
|
||||||
# return True
|
# return True
|
||||||
|
|
||||||
#</pycode(py_custview)>
|
#</pycode(py_custviewer)>
|
||||||
|
|
||||||
#<pycode(py_choose2)>
|
#<pycode(py_choose2)>
|
||||||
class Choose2:
|
class Choose2:
|
||||||
|
Loading…
Reference in New Issue
Block a user