python自動化によるGTKのレイアウトコードの生成(Qt模倣)

15224 ワード

【設計背景】
作業中の1つのメンテナンス型プロジェクトはopengl+gtk+sdlの方案を採用して設計実現を行い、その中でGTKはウィンドウの設計実現を担当する.
Qtを使用したことのある学生はQtCreatorデザインアシスタント(uiデザインの問題から解放された)を知っていますが、GTKはこんなにラッキーではありません.グループ内の歴史のコードを見て、任意のウィンドウコードは千行以上で、ソースコードのUIのコントロール作成、コントロールsizeデザイン、コントロールの位置などを見ると1/3近くを占めています.これはどんなに気まずいことか.
お客様は、開発者がuiコードを手動で作成して検証をコンパイルし、UIが良い位置に見えるまでテストし、調整する必要があります.このように、次の午前中はuiウィンドウの表示作業も完了しにくくなります.
このような気まずい場面に基づいて,pythonを用いてGTKの生成を自動化するレイアウトコードが芽生えた.
 
【設計原理】
私たちはQtCreatorの設計ファイルを知っています.uiは本質的にxmlファイルであり、ユーザードラッグコントロールの相対的な親子とレイアウトSize関係を含む.
xmlファイルはpythonのlxml魔窟で解析し、コントロールのタイプと位置の大きさの情報を記録すればいいです.
QtコントロールクラスをGTKのコントロールに1つずつマッピングし,最後にGTKのuiレイアウトコードを生成する.
 
【設計コード】
  • uiファイルを解析し、コントロールID(QtタイプとID)&コントロール情報
  • を抽出する
  • import os,sys
    import re
    from string import Template
    from xml.etree.ElementTree import ElementTree,Element
    
    ###############################################################################
    #	   @Function: description
    ###############################################################################
    def readFile(fname):
    	with open(fname, 'rb') as f:
    		return f.read()
    def writeFile(fname, data):
    	with open(fname, 'wb') as fo:
    		fo.write(data)
    
    ###############################################################################
    #	   @Function: read/write xml.
    ###############################################################################
    def read_xml(in_path):
    	tree = ElementTree()
    	tree.parse(in_path)
    	return tree
    def write_xml(tree, out_path):
    	tree.write(out_path, encoding="utf-8",xml_declaration=True)
    ###############################################################################
    #	   @Function: basic utils.
    ###############################################################################
    def if_match(node, kv_map):
    	for key in kv_map:
    		if node.get(key) != kv_map.get(key):
    			return False
    	return True
    ###############################################################################
    #	   @Function: various search actions.
    ###############################################################################
    def getElementsByTag(tree, path):
    	return tree.findall(path)
    
    def getElementsByKey(nodelist, kv_map):
    	result_nodes = []
    	for node in nodelist:
    		if if_match(node, kv_map):
    			result_nodes.append(node)
    	return result_nodes
    
    ###############################################################################
    #	   @Function: various modify actions.
    ###############################################################################
    def removeAttribute(nodelist, kv_map):
    	for node in nodelist:
    		for key in kv_map:
    				if key in node.attrib:
    					del node.attrib[key]
    def addAttribute(nodelist, kv_map):
    	for node in nodelist:
    		for key in kv_map:
    				node.set(key, kv_map.get(key))
    
    def setAttribute(nodelist, text):
    	for node in nodelist:
    			node.text = text
    def createElement(tag, property_map, content):
    	element = Element(tag, property_map)
    	element.text = content
    	return element
    
    def add_child_node(nodelist, element):
    	for node in nodelist:
    		node.append(element)
    
    def removeNodeByTagAndKey(nodelist, tag, kv_map):
    	for parent_node in nodelist:
    		children = parent_node.getchildren()
    		for child in children:
    			if child.tag == tag and if_match(child, kv_map):
    				parent_node.remove(child)
    
    class Ui(object):
    	def __init__(self,fname,platform):
    		self._fname = fname
    		self._classname = ""
    		self._widgetmap = {}
    		self._raw = ""
    		self._platform = platform
    		self._root = self.getWidget()
    		self._uiname = fname
    		self._tree = None
    		self.load()
    	def getWidget(self):
    		if self._platform == "html":
    			from widget_html import Widget_Html
    			return Widget_Html()
    		if self._platform == "qt":
    			from widget_qt import Widget_Qt
    			return Widget_Qt()
    		if self._platform == "android":
    			from widget_android import Widget_Android
    			return Widget_Android()
    		if self._platform == "cocos2d":
    			from widget_cocos2d import Widget_Cocos2d
    			return Widget_Cocos2d()
    		if self._platform == "mfc":
    			from widget_mfc import Widget_Mfc
    			return Widget_Mfc()
    		if self._platform == "x11":
    			from widget_x11 import Widget_X11
    			return Widget_X11()
    		if self._platform == "gtk":
    			from widget_gtk import Widget_Gtk
    			return Widget_Gtk()
    		if self._platform == "tk":
    			from widget_tk import Widget_Tk
    			return Widget_Tk()
    		if self._platform == "canvas":
    			from widget_canvas import Widget_Canvas
    			return Widget_Canvas()
    	def load(self):
    		self._tree = read_xml(self._uiname)
    		root = self._tree.getroot()
    	def parse(self):
    		#[1]
    		class_nodes = getElementsByTag(self._tree, "class")
    		if len(class_nodes) >= 1:
    			self._classname = class_nodes[0].text
    		else:
    			print "==> No Class Node!!"
    		#[2]
    		widget_nodes = getElementsByTag(self._tree, "widget")
    		self._root = self.parse_widget(widget_nodes[0],None)
    	def parse_widget(self,node,pp):
    		w = self.getWidget()
    		w._parent = pp
    		w._fname = self._fname
    		w._class = node.attrib["class"]
    		w._name = node.attrib["name"]
    		#[property geometry]
    		rnode = getElementsByKey(getElementsByTag(node, "property"),{"name":"geometry"})
    		if len(rnode) >= 1:
    			w._rect._x = getElementsByTag(rnode[0],"rect/x")[0].text
    			w._rect._y = getElementsByTag(rnode[0],"rect/y")[0].text
    			w._rect._width = getElementsByTag(rnode[0],"rect/width")[0].text
    			w._rect._height = getElementsByTag(rnode[0],"rect/height")[0].text
    		#[property text]
    		text_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"text"})
    		if len(text_node) >= 1:
    			w._vtext = getElementsByTag(text_node[0],"string")[0].text
    		#[property stylesheet]
    		stylesheet_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"styleSheet"})
    		if len(stylesheet_node) >= 1:
    			w._stylesheet = getElementsByTag(stylesheet_node[0],"string")[0].text
    		#[property tooltip]
    		tooltip_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"toolTip"})
    		if len(tooltip_node) >= 1:
    			w._tooltip = getElementsByTag(tooltip_node[0],"string")[0].text
    		#[property title]
    		title_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"title"})
    		if len(title_node) >= 1:
    			w._title = getElementsByTag(title_node[0],"string")[0].text
    		#[property orient]
    		orient_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"orientation"})
    		if len(orient_node) >= 1:
    			w._orient = getElementsByTag(orient_node[0],"enum")[0].text.split("::")[1]
    		#[property statustip]
    		statustip_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"statusTip"})
    		if len(statustip_node) >= 1:
    			w._statustip = getElementsByTag(statustip_node[0],"string")[0].text
    		#[property whatthis]
    		whatthis_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"whatsThis"})
    		if len(whatthis_node) >= 1:
    			w._whatthis = getElementsByTag(whatthis_node[0],"string")[0].text
    		#[property accessiblename]
    		accessiblename_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"accessibleName"})
    		if len(accessiblename_node) >= 1:
    			w._accessiblename = getElementsByTag(accessiblename_node[0],"string")[0].text
    		#[property autorepeatdelay]
    		autorepeatdelay_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"autoRepeatDelay"})
    		if len(autorepeatdelay_node) >= 1:
    			w._autorepeatdelay = getElementsByTag(autorepeatdelay_node[0],"number")[0].text
    		#[property autorepeatinterval]
    		autorepeatinterval_node = getElementsByKey(getElementsByTag(node, "property"),{"name":"autoRepeatInterval"})
    		if len(autorepeatinterval_node) >= 1:
    			w._autorepeatinterval = getElementsByTag(autorepeatinterval_node[0],"number")[0].text
    		#[children]
    		rchilds = getElementsByTag(node, "widget")
    		if len(rchilds) <= 0:
    			return w
    		for child in rchilds:
    			w._children.append(self.parse_widget(child,w))
    		return w
    	def generate(self):
    		self._root.print_msg()
    
    ###############################################################################
    #	   @Function: Main Function
    ###############################################################################
    if len(sys.argv) < 2:
    	print "dev.py *.ui [html/qt/cocos2d/android/mfc/x11/gtk]"
    	sys.exit(-1)
    pname = "html"
    
    if len(sys.argv) > 2:
    	pname = sys.argv[2]
    ui = Ui(sys.argv[1],pname)
    ui.parse()
    ui.generate()
     
  • 対応するコントロールタイプのGTKコード
    import os,sys,re
    from string import Template
    from widget import *
    
    S_Template = Template(
    '''/********************************************************************************
    ** Form generated from reading UI file '${CLASS_NAME}'
    **
    ** Created by: Qt User Interface Compiler version 5.4.1
    **
    ** WARNING! All changes made in this file will be lost when recompiling UI file!
    ********************************************************************************/
    
    #pragma once
    
    #include 
    
    class Ui_${CLASS_NAME} : public Gtk::Window
    {
    public:
    	Gtk::Fixed m_bg;
    ${WIDGET_DEF_LIST}
    public:
    	void setupUi(Gtk::Widget * parent)
    	{
    		(void)(parent);
    		add(m_bg);
    		set_size_request(1920,800);
    		set_decorated(true);
    		set_position(Gtk::WIN_POS_CENTER);
    		set_type_hint(Gdk::WINDOW_TYPE_HINT_MENU);
    		add_events(Gdk::KEY_RELEASE_MASK);
    
    ${WIDGET_LAYOUT_LIST}	
    	}
    };
    
    namespace Ui 
    {
    	class ${CLASS_NAME}: public Ui_${CLASS_NAME} {};
    }
    ''')
    
    
    def DEFINE_EXP(type_,name_):
    	return '\t%s %s;
    ' % (type_,name_) def DEFINE_LAYOUT(name_,x,y,w,h): s1 = "\t\tm_bg.put(%s,%s,%s);" % (name_,x,y) s2 = "\t\t%s.set_size_request(%s,%s);" % (name_,w,h) return '%s
    %s
    ' % (s1,s2) def DEFINE_TEXT(name_,value): return '\t\t%s.set_text(Tr::_("%s"));' % (name_,value) def DEFINE_LABEL_TEXT(name_,value): return '\t\t%s.set_label(Tr::_("%s"));' % (name_,value) def DEFINE_TEXT_BUFFER(name_): return "\tGlib::RefPtr<:textbuffer> %s_textBuffer;
    " % (name_) def CREATE_TEXT_BUFFER(name_): s0 = "\t\t%s_textBuffer = Gtk::TextBuffer::create();" % (name_) s1 = "\t\t%s.set_buffer(%s_textBuffer);" % (name_,name_) s2 = "\t\t%s.set_wrap_mode(Gtk::WRAP_WORD_CHAR);" % (name_) s3 = "\t\t%s.set_pixels_below_lines(5);" % (name_) return "%s
    %s
    %s
    %s
    " % (s0,s1,s2,s3) s_def_list = "" s_func_list = "" class Widget_Gtk(Widget): def on_code_end(self): global s_def_list,s_func_list content = S_Template.substitute(CLASS_NAME="KMiniGui",WIDGET_DEF_LIST=s_def_list,WIDGET_LAYOUT_LIST=s_func_list) print content def print_widget(self,pos): pass def print_button(self): global s_def_list,s_func_list (x,y) = self.abs_pos() s_def_list = s_def_list + DEFINE_EXP("Gtk::Button",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,x,y,self._rect._width,self._rect._height) s_func_list = s_func_list + "
    " def print_lineedit(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::Entry",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + "
    " def print_list(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::Listbox",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + "
    " def print_table(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::Table",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + "
    " def print_label(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::Label",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + DEFINE_TEXT(self._name,self._vtext); s_func_list = s_func_list + "

    " def print_textarea(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::TextView",self._name) s_def_list = s_def_list +DEFINE_TEXT_BUFFER(self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + CREATE_TEXT_BUFFER(self._name) s_func_list = s_func_list + "
    " def print_radiobox(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::RadioButton",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + "
    " def print_checkbox(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::CheckButton",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + "
    " def print_frame(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::Frame",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + DEFINE_LABEL_TEXT(self._name,self._vtext); s_func_list = s_func_list + "
    " def print_groupbox(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::Frame",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + DEFINE_LABEL_TEXT(self._name,self._vtext); s_func_list = s_func_list + "
    " def print_tabpage(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::NoteBook",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + "
    " def print_scrollarea(self): global s_def_list,s_func_list s_def_list = s_def_list +DEFINE_EXP("Gtk::ScrolledWindow",self._name) s_func_list = s_func_list + DEFINE_LAYOUT(self._name,self._rect._x,self._rect._y,self._rect._width,self._rect._height) s_func_list = s_func_list + "
    "
  • を生成する.
    【設計まとめ】
    日常の開発において、開発作業に退屈で規則的で創造性のない作業があることを発見した場合、自動化して生成できるかどうかを考えてみましょう.
    開発を楽にし、面白くします.