python自動化によるGTKのレイアウトコードの生成(Qt模倣)
【設計背景】
作業中の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)&コントロール情報 を抽出する 対応するコントロールタイプのGTKコード を生成する.
【設計まとめ】
日常の開発において、開発作業に退屈で規則的で創造性のない作業があることを発見した場合、自動化して生成できるかどうかを考えてみましょう.
開発を楽にし、面白くします.
作業中の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レイアウトコードを生成する.
【設計コード】
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()
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 + "
"
【設計まとめ】
日常の開発において、開発作業に退屈で規則的で創造性のない作業があることを発見した場合、自動化して生成できるかどうかを考えてみましょう.
開発を楽にし、面白くします.