#!/usr/bin/env python import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from gi.repository import Gio from gi.repository import Gdk from gi.repository import GLib from gi.repository import GdkPixbuf from gi.repository import Pango from gi.repository import cairo try: gi.require_version('Poppler', '0.18') from gi.repository import Poppler rendering_library_name='poppler' except: #To use fitz, install pymupdf not fitz by pip ##pip3 install pymupdf try: import fitz import io rendering_library_name='mupdf' except: import pdf2image import pdfrw import io from gi.repository import GLib, GdkPixbuf rendering_library_name='pdf2image' print("Loaded", rendering_library_name, "as pdf rendering library") import sys import zipfile import os.path import os import urllib import urllib.parse import urllib.request import json import re import string import time def print_log(comment): print("#Log:", comment) def get_int_from_spinbutton(spinbutton): if spinbutton.get_text(): return int(spinbutton.get_text()) else: return spinbutton.get_value_as_int() ##################################################### def get_pdfdocument_from_uri(uri): if rendering_library_name=='poppler': return pdfDocumentByPoppler(uri) elif rendering_library_name=='mupdf': return pdfDocumentByPymupdf(uri) elif rendering_library_name=='pdf2image': return pdfDocumentByPdf2image(uri) else: return None class pdfRenderer: def get_n_pages(self): pass def get_size_of_page(self,i): pass def paint_page(self,page,ctx): pass class pdfDocumentByPoppler(pdfRenderer): def __init__(self,uri): #self.document = Poppler.document_new_from_file(uri,None) self.document = Poppler.Document.new_from_file(uri,None) self.pages=[ None for i in range(self.get_n_pages())] def paint_page(self,page,ctx): self.get_page(page).render(ctx) def get_n_pages(self): return self.document.get_n_pages() def get_size_of_page(self,page): return self.get_page(page).get_size() def get_page(self,page): if self.pages[page]: return self.pages[page] else: self.pages[page]=self.get_page_img(page) return self.pages[page] def get_page_img(self,page): return self.document.get_page(page) class pdfDocumentByPdf2image(pdfRenderer): def __init__(self,uri): #self.document = Poppler.document_new_from_file(uri,None) filename = uri[7:] pdf = pdfrw.PdfReader(filename) bb=pdf.pages[0].MediaBox size=(float(bb[2]),float(bb[3])) #self.pages = pdf2image.convert_from_path(uri,size=size,fmt="png") self.pages = pdf2image.convert_from_path(uri,size=size) def get_n_pages(self): return len(self.pages) def get_size_of_page(self,page): return self.pages[page].size def paint_page(self,page,ctx): img = self.image2pixbuf(self.pages[page]) Gdk.cairo_set_source_pixbuf(ctx,img,0,0) ctx.paint() #img = self.image2imagesurface(self.pages[page]) #ctx.set_source_surface(img,0,0) #ctx.paint() def image2pixbuf(self,im): data = im.tobytes() (w,h) = im.size data = GLib.Bytes.new(data) pix = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB, False, 8, w, h, w * 3) return pix def image2imagesurface(self,img): return cairo.image_surface_create(img) class pdfDocumentByPymupdf(pdfRenderer): def __init__(self,uri): p=urllib.parse.urlparse(uri) path=urllib.parse.unquote(p.path) self.document = fitz.open(path) self.pages=[ None for i in range(self.get_n_pages())] def paint_page(self,page,ctx): ctx.set_source_surface(self.get_page(page),0,0) ctx.paint() def get_n_pages(self): return self.document.pageCount def get_size_of_page(self,page): p=self.get_page(page) return (p.get_width(),p.get_height()) def get_page(self,page): if self.pages[page]: return self.pages[page] else: self.pages[page]=self.get_page_img(page) return self.pages[page] def get_page_img(self,page): p=self.document.loadPage(page) pix = p.getPixmap() #gtk.gdk.pixbuf_new_from_data(pix.samples,gtk.gdk.COLORSPACE_RGB,True,pix.n,pix.width,pix.height,pix.stride) return cairo.ImageSurface.create_from_png(io.BytesIO(pix.getPNGData())) ##################################################### class applicationFormData: def int2alphabet(self,n): r="" while n>19: r=chr(ord('A')+(n%20))+r n=(n-(n%20))//20 return chr(ord('A')+n)+r def create_bgimage_file(self,pdffullpath,destzip,rootdir): destzip.write(pdffullpath,os.path.join(rootdir,self.pdffilename())) def __init__(self,projectdata): self.projectdata=projectdata self.UNITLENGTH=1.0 self.XMARGIN=1.0 self.YMARGIN=1.0 self.PREFIX_SETVARAT="@set@temp@vars@" self.SUFFIX_SETVARAT="" self.PREFIX_ROUNDRECTANGLEAT="" self.SUFFIX_ROUNDRECTANGLEAT="@roundrectangle" self.PREFIX_TABLEAT="@table@" self.SUFFIX_TABLEAT="" self.PREFIX_TABLEROWAT="@table@row@" self.SUFFIX_TABLEROWAT="" self.PREFIX_TABLE="tableform" self.SUFFIX_TABLE="" self.PREFIX_TABLECOL="col" self.SUFFIX_TABLECOL="" self.PREFIX_TABLECOLAT="@table@col@" self.SUFFIX_TABLECOLAT="" self.SUFFIX_PAGEATFIRST="@at@first@" self.SUFFIX_PAGENONEAT="@none@" self.SUFFIX_PAGEPDFAT="@pdf@" self.PREFIX_PAGENAMEBASE="pageNo" self.SUFFIX_PAGENAMEBASE="" self.FINALROW_HOOK_NAME="\\final@row@hook" self.SUFFIX_LOCALCOMMANDS="@"+projectdata.localcommandsuffix+"@nu@" self.bgfilename=self.projectdata.bgimagepath def get_boundingboxstring(self,n): (ltx,lty,rbx,rby)=self.projectdata.boundingboxes[n] r=str(int(ltx))+' '+str(int(lty))+' '+str(int(rbx))+' '+str(int(rby)) return r def dtppt2texpt(self,dtp_pt): return dtp_pt*(72.27)/(72.0) def dtppt2unitlength(self,dtp_pt): return self.dtppt2texpt(dtp_pt)/self.UNITLENGTH def dtppt2unitlength_as_str(self,dtp_pt): return str(self.dtppt2unitlength(dtp_pt)) def setvarATname(self,name): return "\\"+self.PREFIX_SETVARAT+name+self.SUFFIX_SETVARAT+'@'+self.SUFFIX_LOCALCOMMANDS def roundrectangleATname(self,name): return "\\"+self.PREFIX_ROUNDRECTANGLEAT+name+self.SUFFIX_ROUNDRECTANGLEAT+'@'+self.SUFFIX_LOCALCOMMANDS def tablerowsATname(self,i): return "\\"+self.PREFIX_TABLEROWAT+self.int2alphabet(i)+self.SUFFIX_TABLEROWAT+'@'+self.SUFFIX_LOCALCOMMANDS def tableATname(self,i): return "\\"+self.PREFIX_TABLEAT+self.int2alphabet(i)+self.SUFFIX_TABLEAT+'@'+self.SUFFIX_LOCALCOMMANDS def tablename(self,i): return self.PREFIX_TABLE+self.int2alphabet(i)+self.SUFFIX_TABLE def tablecolname(self,i): return "\\"+self.PREFIX_TABLECOL+self.int2alphabet(i)+self.SUFFIX_TABLECOL def tablecolATname(self,i): return "\\"+self.PREFIX_TABLECOLAT+self.int2alphabet(i)+self.SUFFIX_TABLECOLAT+'@'+self.SUFFIX_LOCALCOMMANDS def pagename_base(self,n): return self.PREFIX_PAGENAMEBASE+self.int2alphabet(n)+self.SUFFIX_PAGENAMEBASE def page_atfirst(self,n): return "\\"+self.pagename_base(n)+self.SUFFIX_PAGEATFIRST+'@'+self.SUFFIX_LOCALCOMMANDS def pagename_none(self,n): return self.pagename_base(n)+self.SUFFIX_PAGENONEAT+'@'+self.SUFFIX_LOCALCOMMANDS def pagename_pdf(self,n): return self.pagename_base(n)+self.SUFFIX_PAGEPDFAT+'@'+self.SUFFIX_LOCALCOMMANDS def pagename_frontend(self,n): return self.pagename_base(n) def pdffilename(self): return self.projectdata.bgimagepath def form_table_sample(self,tabledata): r="" r=r +r'\begin{' r=r +self.tablename(tabledata.id_as_int) r=r +r'}' r=r +'\n' for ri in tabledata.table: for j,rij in enumerate(ri): boxdata=self.projectdata.get_boxdata_by_id(rij) r=r +r' ' if boxdata.type==BoxData.TYPE_ENVIRONMENT: r=r +r'% ' r=r + self.tablecolname(j) r=r +r'{' r=r +boxdata.sampletext r=r +r'}'+'\n' r=r +r'\nextrow'+'\n' r=r +r'\end{' r=r +self.tablename(tabledata.id_as_int) r=r +r'}' return r def form_sample(self,boxdata,as_comment): r="" if as_comment: r="% " bl='\n% ' else: bl='\n' if boxdata.type==BoxData.TYPE_ENVIRONMENT: r=r +r'\begin{' r=r + boxdata.name r=r +r'}' r=r +bl r=r + boxdata.sampletext r=r +bl r=r +r'\end{' r=r + boxdata.name r=r +r'}' elif boxdata.type==BoxData.TYPE_COMMAND: r=r +'\\' r=r + boxdata.name r=r +'{' r=r + boxdata.sampletext r=r +'}' elif boxdata.type==BoxData.TYPE_CHECKMARK: r=r +'\\' r=r + boxdata.name elif boxdata.type!=BoxData.TYPE_STRIKE: r=r +'\\' r=r + boxdata.name elif boxdata.type!=BoxData.TYPE_RULE: r=r +'\\' r=r + boxdata.name return r def formfrontenddef_env(self,name,projectname,boxname,as_comment=False): if as_comment: r="% " else: r="" r=r +r'\newenvironment{' r=r + name r=r +r'}{\begin{nu@documentonform@put@box@env}{' r=r + projectname r=r +r'}{' r=r + boxname r=r +r'}}{\end{nu@documentonform@put@box@env}}' return r def formfrontenddef_com(self,name,projectname,boxname,as_comment=False): if as_comment: r="% " else: r="" r=r +r'\newcommand{'+"\\" r=r +name r=r +r'}[1]{\nu@documentonform@put@box@com{' r=r + projectname r=r +r'}{' r=r + boxname r=r +r'}{#1}}' return r def formfrontenddef_checkmark(self,name,projectname,boxname,as_comment=False): if as_comment: r="% " else: r="" r=r +r'\newcommand{'+"\\" r=r + name r=r +r'}{\nu@documentonform@put@checkmark@com{' r=r + projectname r=r +r'}{' r=r + boxname r=r +r'}}' return r def formfrontenddef_strike(self,name,projectname,boxname,as_comment=False): if as_comment: r="% " else: r="" r=r +r'\newcommand{'+"\\" r=r + name r=r +r'}{\nu@documentonform@put@strike@com{' r=r + projectname r=r +r'}{' r=r + boxname r=r +r'}}' return r def formfrontenddef_rule(self,name,projectname,boxname,as_comment=False): if as_comment: r="% " else: r="" r=r +r'\newcommand{'+"\\" r=r + name r=r +r'}{\nu@documentonform@put@rule@com{' r=r + projectname r=r +r'}{' r=r + boxname r=r +r'}}' return r def formfrontenddef_check_circle(self,name,projectname,boxname,as_comment=False): if as_comment: r="% " else: r="" r=r +r'\newcommand{'+"\\" r=r + name r=r +r'}{\nu@documentonform@put@oval@com{' r=r + projectname r=r +r'}{' r=r + boxname r=r +r'}}' return r def formfrontenddef(self,boxdata,projectname): r=[] as_comment=not(boxdata.type==BoxData.TYPE_ENVIRONMENT) r.append(self.formfrontenddef_env(boxdata.name,projectname,boxdata.name,as_comment)) as_comment=not(boxdata.type==BoxData.TYPE_COMMAND) r.append(self.formfrontenddef_com(boxdata.name,projectname,boxdata.name,as_comment)) as_comment=not(boxdata.type==BoxData.TYPE_CHECKMARK) r.append(self.formfrontenddef_checkmark(boxdata.name,projectname,boxdata.name,as_comment)) as_comment=not(boxdata.type==BoxData.TYPE_STRIKE) r.append(self.formfrontenddef_strike(boxdata.name,projectname,boxdata.name,as_comment)) as_comment=not(boxdata.type==BoxData.TYPE_RULE) r.append(self.formfrontenddef_rule(boxdata.name,projectname,boxdata.name,as_comment)) as_comment=not(boxdata.type==BoxData.TYPE_CHECK_CIRCLE) r.append(self.formfrontenddef_check_circle(boxdata.name,projectname,boxdata.name,as_comment)) return "\n".join(r) def setvardef(self,boxdata): (x1,x2,w,y1,y2,h)=self.projectdata.get_box_coordinate(boxdata) x=self.dtppt2unitlength_as_str(x1-self.XMARGIN) if boxdata.valign==BoxData.VALIGN_BOTTOM: y=-y2 com_makebox_pos='bl' elif boxdata.valign==BoxData.VALIGN_CENTER: y=-y1-0.5*h com_makebox_pos='l' else: y=-y1 com_makebox_pos='tl' y=self.dtppt2unitlength_as_str(y+self.YMARGIN) r="" r=r +r'\newcommand{' r=r + self.setvarATname(boxdata.name) r=r +r'}{\set@my@temp@var{' r=r + x r=r +r'}{' r=r + y r=r +r'}{' r=r + com_makebox_pos r=r +r'}{' r=r + self.dtppt2unitlength_as_str(w) r=r +r'\unitlength}{' r=r + self.dtppt2unitlength_as_str(h) r=r +r'\unitlength}}' return r def roundcircledef(self,boxdata): (x1,x2,w,y1,y2,h)=self.projectdata.get_box_coordinate(boxdata) put_roundrectangle_at=self.roundrectangleATname(boxdata.name) midx=self.dtppt2unitlength_as_str(x1+0.5*w-self.XMARGIN) midy=self.dtppt2unitlength_as_str(-y1-0.5*h+self.YMARGIN) round_R=min(w,h) if round_R >20: round_R=20 round_r=int(0.5*round_R) round_wr=int(0.5*w) round_hr=int(0.5*h) round_w=round_wr-round_r round_h=round_hr-round_r r="" r=r +r'\newcommand{' r=r + put_roundrectangle_at r=r +r'}{' if round_R <5: r=r +r'\put(' r=r + midx r=r +r',' r=r + midy r=r +r'){' r=r +r'\makebox(0,0)[c]{$\circ$}' r=r +r'}' else: r=r +r'\put@roundCorners@nu{' r=r + midx r=r +r'}{' r=r + midy r=r +r'}{' r=r + self.dtppt2unitlength_as_str(round_r*2) r=r +r'}{' r=r + self.dtppt2unitlength_as_str(round_w) r=r +r'}{' r=r + self.dtppt2unitlength_as_str(round_h) r=r +r'}{' r=r + self.dtppt2unitlength_as_str(round_wr) r=r +r'}{' r=r + self.dtppt2unitlength_as_str(round_hr) r=r +r'}' r=r +r'}' return r def tablerowdef(self,tabledata): r='' l=len(tabledata.table) for i,ri in enumerate(tabledata.table): r=r +r'\def' r=r + self.tablerowsATname(i) r=r +r'{' for j,rij in enumerate(ri): r=r +r'\def' r=r + self.tablecolATname(j) r=r +r'{' r=r +'\\' r=r +self.projectdata.get_boxdata_by_id(rij).name r=r +r'}' if l-1==i: r=r +r'\def\nextrow{' r=r +self.FINALROW_HOOK_NAME r=r + self.tablerowsATname(0) r=r +r'}' else: r=r +r'\def\nextrow{' r=r + self.tablerowsATname(i+1) r=r +r'}' r=r+r"}"+"\n" r=r + self.tablerowsATname(0) return r def tablebackenddef(self,tabledata): r="" r=r +r'\newcommand{' r=r + self.tableATname(tabledata.id_as_int) r=r +r'}{%'+'\n' r=r + self.tablerowdef(tabledata) r=r +r"%"+'\n'+r"}" return r def tablefrontenddef(self,tabledata): r="" r=r +r'\newenvironment{' r=r +self.tablename(tabledata.id_as_int) r=r +r'}{' r=r + self.tableATname(tabledata.id_as_int)+"\n" r=r +r'\def' r=r+self.FINALROW_HOOK_NAME+"{}\n" for i,ri in enumerate(tabledata.table[0]): r=r +r'\def' r=r + self.tablecolname(i) r=r +r'{' r=r + self.tablecolATname(i) r=r +r'}'+"\n" r=r +r'}{}' return r def pagedef_pdf(self,n): r="" r=r +r'\newenvironment{' r=r + self.pagename_pdf(n) r=r +r'}{\thispagestyle{empty}\begin{overwrappicture}[bb=' r=r + self.get_boundingboxstring(n) r=r +r', page=' r=r +str(n+1) r=r +r']{' r=r + self.pdffilename() r=r +r'}' r=r + self.page_atfirst(n) r=r +r'}{\end{overwrappicture}}' return r def def_page_atfirst(self,n): return r'\newcommand{'+self.page_atfirst(n)+r'}{}' def pagedef_woimage(self,n): r="" r=r +r'\newenvironment{' r=r + self.pagename_none(n) r=r +r'}{\thispagestyle{empty}\begin{overwrappicture*}[bb=' r=r + self.get_boundingboxstring(n) r=r +r', page=' r=r +str(n+1) r=r +r']{' r=r + self.pdffilename() r=r +r'}' r=r + self.page_atfirst(n) r=r +r'}{\end{overwrappicture*}}' return r def pagedef_frontend(self,n): r='' r=r +r"\if@PDF@image@type@nu@" r=r +r'\newenvironment{' r=r + self.pagename_frontend(n) r=r +r'}{\begin{' r=r + self.pagename_pdf(n) r=r +r'}}{\end{' r=r + self.pagename_pdf(n) r=r +r'}}' +"\n" r=r +r'\else' +"\n" r=r +r'\newenvironment{' r=r + self.pagename_frontend(n) r=r +r'}{\begin{' r=r + self.pagename_none(n) r=r +r'}}{\end{' r=r + self.pagename_none(n) r=r +r'}}'+"\n" r=r +r'\fi' return r def common_command(self): return r''' % This file was created by AFM. % The names of commands to put boxes may be just Box IDs. % They are not good names to use. % Please define commands to use as frontend. % % Please redefine the command '''+self.page_atfirst(0)+r''' % if you want to do something whenever '''+self.pagename_frontend(0)+r''' is called. % % Please redefine \baseuplength % if you want to move background image up. % % Boxcommands, e.g. \boxIDa, % between \begin{groupedcolumns} and \end{groupedcolumns} % use common top margin. % The command \nextrow in the environment updates the margin. %%%%%%%%%%%%%%%%%%%%%%%%%%% % Definition of basic commands \NeedsTeXFormat{LaTeX2e} \RequirePackage{documentonform} \newif\if@PDF@image@type@nu@ \@PDF@image@type@nu@true \newif\if@js@basecls@nu@ \@js@basecls@nu@false \DeclareOption{pdf}{\@PDF@image@type@nu@true} \DeclareOption{none}{\@PDF@image@type@nu@false} \DeclareOption{js}{\@js@basecls@nu@true} \ProcessOptions\relax %%%%%%%%%%%%%%%%%%%%%%%%%%% ''' def get_style_code(self): r="%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" r=r+self.common_command() r=r+r'\nu@input@json@project@data{'+self.projectdata.localcommandsuffix+'}{projectdata.json}'+"\n" page_atfirst="" page_def="" page_front="" form_front="" for i in self.projectdata.get_pages_with_boxdata(): page_front=page_front+"\n"+self.pagedef_frontend(i) page_def=page_def+"\n"+self.pagedef_woimage(i) page_def=page_def+"\n"+self.pagedef_pdf(i) page_atfirst=page_atfirst+"\n"+self.def_page_atfirst(i) form_front=form_front+"\n% page "+str(i+1)+" i.e.," +self.int2alphabet(i) for boxdata in self.projectdata.x_boxdata_in_the_page(i): #form_def=form_def+"\n"+self.setvardef(boxdata) #form_front=form_front+"\n\n"+self.setvardef(boxdata) #form_def=form_def+"\n"+self.roundcircledef(boxdata)+"\n\n" form_front=form_front+"\n\n"+self.formfrontenddef(boxdata,self.projectdata.localcommandsuffix) form_front=form_front+"\n" table_backend="" table_front="" for tabledata in self.projectdata.tables: table_backend=table_backend+"\n"+self.tablebackenddef(tabledata) table_front=table_front+"\n"+self.tablefrontenddef(tabledata) r=r+"%%%%%%%%%%%%%%%%%%%%%%%%%%%\n% Backend commands for pages." r=r+page_def r=r+"\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%\n% Backend commands for table forms." r=r+table_backend r=r+"\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%\n% Forntend commands of boxes\n" r=r+form_front r=r+"\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%\n% Forntend commands of pages\n" r=r+page_front r=r+"\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%\n% Hooked commands\n" r=r+page_atfirst r=r+"\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%\n% Forntend commands of table forms\n" r=r+table_front r=r+"\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%\n" return r def get_sample_makefile(self,sample_file,style_file): r="LATEX=latex\nDVI2PDF=dvipdfmx\n" r=r+"STYLEFILE="+style_file+"\n\n" r=r+"TEXFILE="+sample_file+"\n\n" r=r+"all: pdf\ndvi: ${TEXFILE}.dvi\npdf: ${TEXFILE}.pdf\n\n" r=r+"${TEXFILE}.dvi: ${TEXFILE}.tex ${STYLEFILE}.sty documentonform.sty\n\t${LATEX} ${TEXFILE} && ${LATEX} ${TEXFILE}\n" r=r+"${TEXFILE}.pdf: ${TEXFILE}.dvi\n\t${DVI2PDF} ${TEXFILE}.dvi\n" r=r+"clean:\n\trm -f ${TEXFILE}.dvi ${TEXFILE}.pdf ${TEXFILE}.log ${TEXFILE}.aux texput.log" return r def get_sample_code(self,sty_file): r=r'''%sample \documentclass[a4paper,12pt]{amsart} \nofiles %\usepackage{graphicx} %\usepackage[none]{'''+sty_file+r'''} \usepackage[dvipdfmx]{graphicx} \usepackage[pdf]{'''+sty_file+r'''} % If you use the class jsarticle, then use js option %\usepackage[pdf,js]{'''+sty_file+r'''} \begin{document} ''' for i in self.projectdata.get_pages_with_boxdata(): r=r+"\n% page"+str(i+1)+"\n" r=r+r'\begin{' r=r+self.pagename_frontend(i) r=r+r'}' r=r+"\n" for boxdata in self.projectdata.x_boxdata_in_the_page(i): if boxdata.type!=BoxData.TYPE_ENVIRONMENT and self.projectdata.table_contains(boxdata): r=r+"\n"+self.form_sample(boxdata,True) else: r=r+"\n"+self.form_sample(boxdata,False) r=r+"\n" for tabledata in self.projectdata.x_tabledata_in_the_page(i): r=r+"\n"+self.form_table_sample(tabledata) r=r+"\n"+r'\end{' r=r+self.pagename_frontend(i) r=r+r'}' r=r+"\n\n" r=r+r'\end{document}' return r ##################################################### class GridData: serialnum=0 def __init__(self,page,value,is_horizontal,id=None): if id==None: self.id=GridData.serialnum GridData.serialnum=GridData.serialnum+1 else: self.id=id GridData.serialnum=max(GridData.serialnum,id)+1 self.page=page self.value=value self.is_horizontal=is_horizontal def dump_as_dictionary(self): d={} d["id"]=self.id d["page"]=self.page d["value"]=self.value d["is_horizontal"]=self.is_horizontal return d @classmethod def construct_from_dictionary(cls,d): return GridData(d["page"], d["value"],d["is_horizontal"],d["id"]) class TableData: serialnum=0 def int2alphabet(self,n): r="" while n>19: r=chr(ord('a')+(n%20))+r n=(n-(n%20))//20 return chr(ord('a')+n)+r def __init__(self,table_of_id,id_as_int=None): if id_as_int==None: self.id_as_int=TableData.serialnum TableData.serialnum=TableData.serialnum+1 else: self.id_as_int=id_as_int TableData.serialnum=max(TableData.serialnum,id_as_int)+1 self.id=self.int2alphabet(self.id_as_int) self.table=[[rij for rij in ri] for ri in table_of_id] def dump_as_dictionary(self): d={} d["id_as_int"]=self.id_as_int d["table"]=self.table return d @classmethod def construct_from_dictionary(cls,d): r=TableData(d["table"],d["id_as_int"]) return r class BoxData: serialnum=0 VALIGN_TOP=1 VALIGN_BOTTOM=2 VALIGN_CENTER=3 DESCRIPTION_VALIGN={1:"top",3:"center",2:"bottom"} HALIGN_LEFT=1 HALIGN_RIGHT=2 HALIGN_CENTER=3 DESCRIPTION_HALIGN={1:"left",3:"center",2:"right"} TYPE_ENVIRONMENT=0 TYPE_COMMAND=1 TYPE_CHECKMARK=2 TYPE_STRIKE=3 TYPE_RULE=4 TYPE_CHECK_CIRCLE=5 DESCRIPTION_TYPE={0:"environment",1:"command",2:"checkmark",3:"strike",4:"rule",5:"check by circle (or oval)"} def int2alphabet(self,n): r="" while n>19: r=chr(ord('a')+(n%20))+r n=(n-(n%20))//20 return chr(ord('a')+n)+r def __init__(self,page,x1,x2,y1,y2,id_as_int=None): if id_as_int==None: self.id_as_int=BoxData.serialnum BoxData.serialnum=BoxData.serialnum+1 else: self.id_as_int=id_as_int BoxData.serialnum=max(BoxData.serialnum,id_as_int)+1 self.id=self.int2alphabet(self.id_as_int) self.x_1=x1 self.x_2=x2 self.y_1=y1 self.y_2=y2 self.name="boxID"+self.id self.sampletext="Example of "+self.name self.hilight=False self.page=page self.valign=1 self.halign=1 self.type=0 def dump_as_dictionary(self): d={} d["id_as_int"]=self.id_as_int d["x_1"]=self.x_1 d["x_2"]=self.x_2 d["y_1"]=self.y_1 d["y_2"]=self.y_2 d["name"]=self.name d["sampletext"]=self.sampletext d["page"]=self.page d["valign"]=self.valign d["halign"]=self.halign d["type"]=self.type return d @classmethod def construct_from_dictionary(cls,d): r=BoxData(d["page"],d["x_1"],d["x_2"],d["y_1"],d["y_2"],d["id_as_int"]) r.name=d["name"] r.sampletext=d["sampletext"] r.valign=d["valign"] r.halign=d["halign"] r.type=d["type"] return r class BoxDataEntryArea: COMBO_VALIGN=[("top",BoxData.VALIGN_TOP),("center",BoxData.VALIGN_CENTER),("bottom",BoxData.VALIGN_BOTTOM)] COMBO_HALIGN=[("left",BoxData.HALIGN_LEFT),("center",BoxData.HALIGN_CENTER),("right",BoxData.HALIGN_RIGHT)] COMBO_TYPE=[("environment",BoxData.TYPE_ENVIRONMENT),("command",BoxData.TYPE_COMMAND),("checkmark",BoxData.TYPE_CHECKMARK),("strike",BoxData.TYPE_STRIKE),("rule",BoxData.TYPE_RULE),("check by circle",BoxData.TYPE_CHECK_CIRCLE)] def __init__(self,boxdata,message,projectdata): self.projectdata=projectdata self.boxdata=boxdata vbox=Gtk.VBox() self.vbox=vbox label=Gtk.Label() label.set_markup(message) vbox.pack_start(label,False,False,10) table=Gtk.Table(n_rows=2,n_columns=12) vbox.add(table) label=Gtk.Label() label.set_markup("Id") table.attach(label,1,2,1,2) entry=Gtk.Entry() table.attach(entry,2,3,1,2) entry.set_text(str(boxdata.id)) entry.set_editable(False) label=Gtk.Label() label.set_markup("Name") table.attach(label,1,2,2,3) entry=Gtk.Entry() self.entry_name=entry entry.set_text(str(boxdata.name)) table.attach(entry,2,3,2,3) label=Gtk.Label() label.set_markup("sample text") table.attach(label,1,2,3,4) entry=Gtk.TextView() self.entry_sampletext=entry entry.get_buffer().set_text(str(boxdata.sampletext)) self.entry_sampletext=entry sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(entry) table.attach(sw,2,3,3,4) label=Gtk.Label() label.set_markup("virtical align") table.attach(label,1,2,4,5) combobox = Gtk.ComboBoxText() for i,(text,v) in enumerate(self.COMBO_VALIGN): combobox.append_text(text) if boxdata.valign==v: combobox.set_active(i) self.entry_valign=combobox table.attach(combobox,2,3,4,5) label=Gtk.Label() label.set_markup("horizontal align") table.attach(label,1,2,5,6) combobox = Gtk.ComboBoxText() for i,(text,v) in enumerate(self.COMBO_HALIGN): combobox.append_text(text) if boxdata.halign==v: combobox.set_active(i) self.entry_halign=combobox table.attach(combobox,2,3,5,6) label=Gtk.Label() label.set_markup("type") table.attach(label,1,2,6,7) combobox = Gtk.ComboBoxText() for i,(text,v) in enumerate(self.COMBO_TYPE): combobox.append_text(text) if boxdata.type==v: combobox.set_active(i) self.entry_type=combobox table.attach(combobox,2,3,6,7) label=Gtk.Label() label.set_markup("page") table.attach(label,1,2,7,8) adjustment = Gtk.Adjustment(value=boxdata.page,lower=0,upper=projectdata.document.get_n_pages(),step_increment=1) entry=Gtk.SpinButton() entry.set_adjustment(adjustment) entry.set_value(boxdata.page) self.entry_page=entry table.attach(entry,2,3,7,8) label=Gtk.Label() label.set_markup("top") table.attach(label,1,2,8,9) adjustment = Gtk.Adjustment(value=boxdata.y_1,lower=0,upper=projectdata.lheight,step_increment=1,page_increment=1) entry=Gtk.SpinButton() entry.set_adjustment(adjustment) entry.set_value(boxdata.y_1) self.entry_y1=entry table.attach(entry,2,3,8,9) label=Gtk.Label() label.set_markup("bottom") table.attach(label,1,2,9,10) adjustment = Gtk.Adjustment(value=boxdata.y_2,lower=0,upper=projectdata.lheight,step_increment=1,page_increment=1) entry=Gtk.SpinButton() entry.set_adjustment(adjustment) entry.set_value(boxdata.y_2) self.entry_y2=entry table.attach(entry,2,3,9,10) label=Gtk.Label() label.set_markup("left") table.attach(label,1,2,10,11) adjustment = Gtk.Adjustment(value=boxdata.x_1,lower=0,upper=projectdata.lwidth,step_increment=1,page_increment=1) entry=Gtk.SpinButton() entry.set_adjustment(adjustment) entry.set_value(boxdata.x_1) self.entry_x1=entry table.attach(entry,2,3,10,11) label=Gtk.Label() label.set_markup("right") table.attach(label,1,2,11,12) adjustment = Gtk.Adjustment(value=boxdata.x_2,lower=0,upper=projectdata.lwidth,step_increment=1,page_increment=1) entry=Gtk.SpinButton() entry.set_adjustment(adjustment) entry.set_value(boxdata.x_2) self.entry_x2=entry table.attach(entry,2,3,11,12) self.set_editable_all(True) vbox.show_all() def get_box(self): return self.vbox def set_editable_all(self,is_editable): self.entry_name.set_editable(is_editable) self.entry_sampletext.set_editable(is_editable) self.entry_page.set_editable(is_editable) self.entry_x1.set_editable(is_editable) self.entry_x2.set_editable(is_editable) self.entry_y1.set_editable(is_editable) self.entry_y2.set_editable(is_editable) def update_and_get_boxdata(self): self.boxdata.name=self.entry_name.get_text() (st, end) = self.entry_sampletext.get_buffer().get_bounds() self.boxdata.sampletext=self.entry_sampletext.get_buffer().get_text(st,end,False) self.boxdata.valign=self.COMBO_VALIGN[self.entry_valign.get_active()][1] self.boxdata.halign=self.COMBO_HALIGN[self.entry_halign.get_active()][1] self.boxdata.type=self.COMBO_TYPE[self.entry_type.get_active()][1] self.boxdata.hilight=False self.boxdata.page=get_int_from_spinbutton(self.entry_page) self.boxdata.x_1=get_int_from_spinbutton(self.entry_x1) self.boxdata.x_2=get_int_from_spinbutton(self.entry_x2) self.boxdata.y_1=get_int_from_spinbutton(self.entry_y1) self.boxdata.y_2=get_int_from_spinbutton(self.entry_y2) return self.boxdata class TableDataEntryArea: COMBO_VALIGN=[("top",BoxData.VALIGN_TOP),("center",BoxData.VALIGN_CENTER),("bottom",BoxData.VALIGN_BOTTOM)] COMBO_HALIGN=[("left",BoxData.HALIGN_LEFT),("center",BoxData.HALIGN_CENTER),("right",BoxData.HALIGN_RIGHT)] COMBO_TYPE=[("environment",BoxData.TYPE_ENVIRONMENT),("command",BoxData.TYPE_COMMAND),("checkmark",BoxData.TYPE_CHECKMARK),("strike",BoxData.TYPE_STRIKE),("rule",BoxData.TYPE_RULE),("check by circle",BoxData.TYPE_CHECK_CIRCLE)] def __init__(self,message,projectdata,current_page): self.projectdata=projectdata vbox=Gtk.VBox() self.vbox=vbox label=Gtk.Label() label.set_markup(message) vbox.pack_start(label,False,False,10) table=Gtk.Table(n_rows=2,n_columns=10) vbox.add(table) label=Gtk.Label() label.set_markup("virtical align") table.attach(label,1,2,4,5) combobox = Gtk.ComboBoxText() for i,(text,v) in enumerate(self.COMBO_VALIGN): combobox.append_text(text) #if boxdata.valign==v: # combobox.set_active(i) combobox.set_active(1) self.entry_valign=combobox table.attach(combobox,2,3,4,5) label=Gtk.Label() label.set_markup("horizontal align") table.attach(label,1,2,5,6) combobox = Gtk.ComboBoxText() for i,(text,v) in enumerate(self.COMBO_HALIGN): combobox.append_text(text) #if boxdata.halign==v: # combobox.set_active(i) combobox.set_active(1) self.entry_halign=combobox table.attach(combobox,2,3,5,6) label=Gtk.Label() label.set_markup("type") table.attach(label,1,2,6,7) combobox = Gtk.ComboBoxText() for i,(text,v) in enumerate(self.COMBO_TYPE): combobox.append_text(text) #if boxdata.type==v: # combobox.set_active(i) combobox.set_active(1) self.entry_type=combobox table.attach(combobox,2,3,6,7) label=Gtk.Label() label.set_markup("page") table.attach(label,1,2,7,8) adjustment = Gtk.Adjustment(value=current_page,lower=0,upper=projectdata.document.get_n_pages(),step_increment=1) entry=Gtk.SpinButton() entry.set_adjustment(adjustment) entry.set_value(current_page) self.entry_page=entry table.attach(entry,2,3,7,8) label=Gtk.Label() label.set_markup("t,b;t,b;...;t,b") table.attach(label,1,2,8,9) entry=Gtk.Entry() self.entry_yy=entry entry.set_text("") table.attach(entry,2,3,8,9) label=Gtk.Label() label.set_markup("l,r;l,r;...;l,r") table.attach(label,1,2,9,10) entry=Gtk.Entry() entry.set_text("") self.entry_xx=entry table.attach(entry,2,3,9,10) self.set_editable_all(True) vbox.show_all() def get_box(self): return self.vbox def set_editable_all(self,is_editable): self.entry_page.set_editable(is_editable) self.entry_xx.set_editable(is_editable) self.entry_yy.set_editable(is_editable) def get_tabledata(self): valign=self.COMBO_VALIGN[self.entry_valign.get_active()][1] halign=self.COMBO_HALIGN[self.entry_halign.get_active()][1] boxtype=self.COMBO_TYPE[self.entry_type.get_active()][1] hilight=False page=get_int_from_spinbutton(self.entry_page) xx=self.get_grids(self.entry_xx.get_text()) yy=self.get_grids(self.entry_yy.get_text()) rr=[[BoxData(page,x1,x2,y1,y2) for (x1,x2) in xx] for (y1,y2) in yy ] for ri in rr: for bi in ri: bi.valign=valign bi.halign=halign bi.type=boxtype bi.hilight=hilight rt=TableData([[ rij.id for rij in ri] for ri in rr]) return (rr,rt) def get_grids(self,s): rr=[ si.split(",") for si in s.split(";")] return [(int(ri[0]),int(ri[-1])) for ri in rr] class BoxDataListArea: def __init__(self,parent): self.parent=parent vbox=Gtk.VBox() self.vbox=vbox store = Gtk.ListStore (str,str,str, int, int,int,int,int, str,str,str) treeview=Gtk.TreeView(model=store) self.treeview=treeview #treeview.set_rules_hint(True) treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) tvcolumn = Gtk.TreeViewColumn('page') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 3) treeview.set_reorderable(True) tvcolumn = Gtk.TreeViewColumn('id') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 0) tvcolumn = Gtk.TreeViewColumn('Neme') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 1) tvcolumn = Gtk.TreeViewColumn('y') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 6) treeview.set_reorderable(True) tvcolumn = Gtk.TreeViewColumn("y'") treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 7) treeview.set_reorderable(True) tvcolumn = Gtk.TreeViewColumn('x') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 4) treeview.set_reorderable(True) tvcolumn = Gtk.TreeViewColumn("x'") treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 5) treeview.set_reorderable(True) tvcolumn = Gtk.TreeViewColumn('valign') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 8) treeview.set_reorderable(True) tvcolumn = Gtk.TreeViewColumn('halign') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 9) treeview.set_reorderable(True) tvcolumn = Gtk.TreeViewColumn('type') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 10) treeview.set_reorderable(True) tvcolumn = Gtk.TreeViewColumn('Sample Text') treeview.append_column(tvcolumn) cell = Gtk.CellRendererText() tvcolumn.pack_start(cell, True) tvcolumn.add_attribute(cell, 'text', 2) treeview.set_reorderable(True) sw = Gtk.ScrolledWindow() sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) sw.add(treeview) vbox.add(sw) hbox = Gtk.HButtonBox() vbox.pack_start(hbox,False,False,0) hbox.set_layout(Gtk.ButtonBoxStyle.END) self.buttonbox=hbox # button = Gtk.Button(stock=Gtk.STOCK_NEW) # button = Gtk.Button.new_with_mnemonic("NEW") button = Gtk.Button.new_from_icon_name("list-add",Gtk.IconSize.LARGE_TOOLBAR) hbox.add(button) self.button_new=button # button = Gtk.Button(stock=Gtk.STOCK_REMOVE) button = Gtk.Button.new_from_icon_name("list-remove",Gtk.IconSize.LARGE_TOOLBAR) hbox.add(button) self.button_remove=button button = Gtk.Button(stock=Gtk.STOCK_EDIT) #button = Gtk.Button.new_from_icon_name("list-edit",Gtk.IconSize.LARGE_TOOLBAR) #button = Gtk.Button.new_from_icon_name("gtk-edit",Gtk.IconSize.LARGE_TOOLBAR) hbox.add(button) self.button_edit=button vbox.show_all() for boxdata in self.parent.projectdata.boxes: self.append_boxdata(boxdata) def get_vbox(self): return self.vbox def get_buttonbox(self): return self.buttonbox def get_buttons(self): return (self.button_new,self.button_remove,self.button_edit) def append_boxdata(self,boxdata): data=(boxdata.id,boxdata.name,boxdata.sampletext,boxdata.page,boxdata.x_1,boxdata.x_2,boxdata.y_1,boxdata.y_2,BoxData.DESCRIPTION_VALIGN[boxdata.valign],BoxData.DESCRIPTION_HALIGN[boxdata.halign],BoxData.DESCRIPTION_TYPE[boxdata.type]) self.treeview.get_model().append(data) #self.treeview.scroll_to_cell(path) def get_selected_id(self): (model,iteralist,idlist)=self.get_selected_ids() if iteralist: return (model,iteralist[0],idlist[0]) else: return (model,None,None) def get_selected_ids(self): (model, pathlist) = self.treeview.get_selection().get_selected_rows() if pathlist: iteralist=[model.get_iter(path)for path in pathlist] return (model,iteralist,[model.get(itera,0)[0] for itera in iteralist]) else: return (model,None,None) class LayoutOverBoxes(Gtk.Layout): def __init__(self,projectdata): Gtk.Layout.__init__(self) self.message='test text' self.width = self.height = 0 self.connect('size-allocate', self.on_self_size_allocate) self.connect('draw', self.on_self_expose_event) self.projectdata=projectdata self.page=0 def refresh_preview(self): if self.get_window(): self.get_window().invalidate_rect(self.get_allocation(),True) def set_page(self,page): n=self.projectdata.document.get_n_pages() if n>0: self.page=page % n else: self.page=page self.refresh_preview() def on_self_size_allocate(self, widget, allocation): self.width = allocation.width self.height = allocation.height def on_self_expose_event(self, widget, event): #ctx = widget.get_bin_window().cairo_create() ctx = widget.get_window().cairo_create() if not self.projectdata: return self.projectdata.document.paint_page(self.page,ctx) for box in self.projectdata.x_boxdata_in_the_page(self.page): (x1,x2,width,y1,y2,height)=self.projectdata.get_box_coordinate(box) (r,g,b,a)=(0.3, 0.3, 0.6,1.0) ctx.set_source_rgba(r,g,b,a) #ctx.select_font_face('Serif', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) ctx.set_font_size(12) ctx.move_to(x1, y2) ctx.show_text(box.id) if box.hilight: (r,g,b,a)=(0.9,0.2,0.4, 0.005) else: (r,g,b,a)=(1,0.5,0.5, 0.1) ctx.set_source_rgba(r,g,b,a) ctx.new_path() if box.valign==BoxData.VALIGN_TOP: if box.halign==BoxData.HALIGN_LEFT: a=0.8 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to((x1+x2)/2,y1) ctx.line_to(x1, (y1+y2)/2) ctx.stroke() a=0.3 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to((x1+x2)/2,y1) ctx.line_to(x1, (y1+y2)/2) ctx.line_to(x1, y1) ctx.close_path() ctx.fill() elif box.halign==BoxData.HALIGN_RIGHT: ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to((x1+x2)/2,y1) ctx.line_to(x2, (y1+y2)/2) ctx.stroke() a=0.3 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to((x1+x2)/2,y1) ctx.line_to(x2, (y1+y2)/2) ctx.line_to(x2, y1) ctx.close_path() ctx.fill() else: ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x1,y1) ctx.line_to((x1+x2)/2,(y1+y2)/2) ctx.line_to(x2,y1) ctx.stroke() a=0.3 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x1,y1) ctx.line_to((x1+x2)/2,(y1+y2)/2) ctx.line_to(x2,y1) ctx.close_path() ctx.fill() elif box.valign==BoxData.VALIGN_BOTTOM: if box.halign==BoxData.HALIGN_LEFT: a=0.8 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to((x1+x2)/2,y2) ctx.line_to(x1, (y1+y2)/2) ctx.stroke() a=0.3 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to((x1+x2)/2,y2) ctx.line_to(x1, (y1+y2)/2) ctx.line_to(x1, y2) ctx.close_path() ctx.fill() elif box.halign==BoxData.HALIGN_RIGHT: ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to((x1+x2)/2,y2) ctx.line_to(x2, (y1+y2)/2) ctx.stroke() a=0.3 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to((x1+x2)/2,y2) ctx.line_to(x2, (y1+y2)/2) ctx.line_to(x2, y2) ctx.close_path() ctx.fill() else: ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x1,y2) ctx.line_to((x1+x2)/2,(y1+y2)/2) ctx.line_to(x2,y2) ctx.stroke() a=0.3 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x1,y2) ctx.line_to((x1+x2)/2,(y1+y2)/2) ctx.line_to(x2,y2) ctx.close_path() ctx.fill() else: if box.halign==BoxData.HALIGN_LEFT: a=0.8 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x1,y1) ctx.line_to((x1+x2)/2, (y1+y2)/2) ctx.line_to(x1,y2) ctx.stroke() a=0.3 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x1,y1) ctx.line_to((x1+x2)/2, (y1+y2)/2) ctx.line_to(x1,y2) ctx.close_path() ctx.fill() elif box.halign==BoxData.HALIGN_RIGHT: a=0.8 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x2,y1) ctx.line_to((x1+x2)/2, (y1+y2)/2) ctx.line_to(x2,y2) ctx.stroke() a=0.3 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x2,y1) ctx.line_to((x1+x2)/2, (y1+y2)/2) ctx.line_to(x2,y2) ctx.close_path() ctx.fill() else: a=0.8 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x1+0.4*width,(y1+y2)/2) ctx.line_to(x2-0.4*width,(y1+y2)/2) ctx.stroke() ctx.new_path() ctx.move_to((x1+x2)/2,y1+0.35*height) ctx.line_to((x1+x2)/2,y2-0.35*height) ctx.stroke() a=0.1 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x1, y1) ctx.rel_line_to(width, 0) ctx.rel_line_to(0, height) ctx.rel_line_to(-width, 0) ctx.close_path() ctx.fill() a=0.8 ctx.set_source_rgba(r,g,b,a) ctx.set_line_width(0.5) ctx.new_path() ctx.move_to(x1, y1) ctx.rel_line_to(width, 0) ctx.rel_line_to(0, height) ctx.rel_line_to(-width, 0) ctx.close_path() ctx.stroke() if box.hilight: a=0.5 ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(x1, y1) ctx.rel_line_to(width, height) ctx.stroke() w=int(width*2/3) d=-int(width/10) dy=int(height/6) class Bar(Gtk.DrawingArea): def __init__(self,direction,margin): Gtk.DrawingArea.__init__(self) self.width = self.height = 0 self.direction=direction self.connect('size-allocate', self.on_self_size_allocate) self.connect('draw', self.on_self_expose_event) self.margin=margin self.hilight_mode=0 self.hilight_spot_size=8 def set_hilight_mode(self,mode): self.hilight_mode=mode def get_background_rgba(self): if self.hilight_mode==0: return (0.1,0.2,1.0,1) elif self.hilight_mode==1: return (0.3,0.7,0.7,1) elif self.hilight_mode==2: return (0.5,0.1,0.1,1) elif self.hilight_mode==3: return (0.1,0.5,0.5,1) elif self.hilight_mode==4: return (0.1,0.5,0.1,1) elif self.hilight_mode==5: return (1.0,1.0,0.5,1) else: return (0.5,0.5,0.5,0.5) def get_line_rgba(self): if self.hilight_mode==0: return (0.4,0.05,0.05,0.5) elif self.hilight_mode==1: return (1.0,0.3,0.3,0.5) elif self.hilight_mode==2: return (0.1,0.1,0.5,0.5) elif self.hilight_mode==3: return (0.1,0.5,0.5,0.5) elif self.hilight_mode==4: return (0.5,0.1,0.5,0.5) elif self.hilight_mode==5: return (0.5,0.5,0.1,0.5) else: return (0.5,0.5,0.5,0.5) def on_self_size_allocate(self, widget, allocation): self.width = allocation.width self.height = allocation.height def on_self_expose_event(self, widget, event): ctx = widget.get_window().cairo_create() if self.direction & BarOnLayout.MASK_VIRTICAL_BAR: c = self.get_line_rgba() if c != None: (r,g,b,a)=c ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(0, 0) ctx.rel_line_to(0,self.height) ctx.rel_line_to(1, 0) ctx.rel_line_to(0,-self.height) ctx.close_path() ctx.fill() c = self.get_background_rgba() if c != None: (r,g,b,a)=c ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(0, self.margin) ctx.rel_line_to(0,self.hilight_spot_size) ctx.rel_line_to(self.width, 0) ctx.rel_line_to(0,-self.hilight_spot_size) ctx.close_path() ctx.fill() else: c= self.get_line_rgba() if c!=None: (r,g,b,a)=c ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(0, self.height) ctx.rel_line_to(self.width, 0) ctx.rel_line_to(0,-1) ctx.rel_line_to(-self.width, 0) ctx.close_path() ctx.fill() c= self.get_background_rgba() if c!=None: (r,g,b,a)=c ctx.set_source_rgba(r,g,b,a) ctx.new_path() ctx.move_to(self.margin, 0) ctx.rel_line_to(self.hilight_spot_size, 0) ctx.rel_line_to(0,self.height) ctx.rel_line_to(-self.hilight_spot_size, 0) ctx.close_path() ctx.fill() class SpinButtonForBarOnLayout(Gtk.SpinButton): def __init__(self,adj_max,id_adj,id_spb): adj=Gtk.Adjustment(value=0,lower=0,upper=adj_max,step_increment=1) Gtk.SpinButton.__init__(self) self.set_adjustment(adj) self.set_width_chars(5) self.set_alignment(1.0) self.bars=[] self.current_bar=None self.id_adj=id_adj self.id_spb=id_spb self.update_upper_and_lower() def append_bar(self,bar): self.bars.append(bar) self.update_upper_and_lower() def update_upper_and_lower(self): if self.bars==[]: self.id_adj.set_upper(-1) self.id_adj.set_lower(1) else: m=max([bar.griddata.id for bar in self.bars]) self.id_adj.set_upper(m) m=min([bar.griddata.id for bar in self.bars]) self.id_adj.set_lower(m) def set_current_bar(self,bar): if self.current_bar != None: self.current_bar.set_hilight_mode(0) if bar != None: bar.set_hilight_mode(1) if bar != self.current_bar: self.current_bar=bar if bar != None: self.set_value(bar.get_value()) else: self.set_value(0) if self.current_bar.griddata.id != get_int_from_spinbutton(self.id_spb): self.id_spb.set_value(self.current_bar.griddata.id) def get_bar_by_id(self,bar_id): for bar in self.bars: if bar.griddata.id==bar_id: return bar return None def set_current_bar_on_changed(self,widget): bar_id=get_int_from_spinbutton(widget) bar=self.get_bar_by_id(bar_id) self.set_current_bar(bar) def move_bar_on_changed(self,widget): if self.current_bar != None: self.current_bar.set_value(get_int_from_spinbutton(self)) class BarOnLayout(Gtk.EventBox): MASK_VIRTICAL_BAR=2 MASK_OPPOSIT_DIRECTION=1 LINEWIDTH=1 def __init__(self,direction,max_x,max_y,spinbuttonforbar,griddata,current_page): Gtk.EventBox.__init__(self) self.x=0 self.y=0 self.direction=direction self.max_x=max_x self.max_y=max_y self.griddata=griddata self.spinbutton=spinbuttonforbar if direction & self.MASK_VIRTICAL_BAR: self.height=max_y self.width=self.LINEWIDTH self.margin=(13+9*griddata.id)%self.height box=Gtk.HBox() label_box=Gtk.VBox() label_box.pack_start(Gtk.HSeparator(),False,False,0) else: self.width=max_x self.height=self.LINEWIDTH self.margin=(13+4*griddata.id)%self.width box=Gtk.VBox() label_box=Gtk.HBox() label_box.pack_start(Gtk.VSeparator(),False,False,0) drawingarea = Bar(direction,self.margin) self.drawingarea=drawingarea drawingarea.set_size_request(self.width,self.height) self.set_visible(True) self.set_visible_window(False) self.add(box) box.add(drawingarea) label=Gtk.Label() label.set_markup(''+str(self.griddata.id)+'') label_box.pack_start(label,False, False, 0) box.add(label_box) label_box.show_all() label.show() box.show() self.label_box=label_box drawingarea.show() self.draging=False self.connect("motion_notify_event", self.motion_notify_event) self.connect("button_press_event", self.button_press_event) self.connect("button_release_event", self.button_release_event) self.current_page=current_page self.hilight_mode=0 self.should_hide_if_not_current_page=True self.should_hide_whenever=False self.set_hilight() self.spinbutton.append_bar(self) def fit_to_size(self,width,height): if self.direction & self.MASK_VIRTICAL_BAR: if height > self.height: self.height=max(self.max_y,height) self.drawingarea.height=self.height self.drawingarea.set_size_request(self.width,self.height) else: if width > self.width: self.width=max(self.max_x,width) self.drawingarea.width=self.width self.drawingarea.set_size_request(self.width,self.height) def set_hilight(self): if self.current_page==self.griddata.page: self.set_visible(not self.should_hide_whenever) if self.hilight_mode==0: self.drawingarea.set_hilight_mode(0) else: self.drawingarea.set_hilight_mode(1) else: if self.hilight_mode==0: self.set_visible(not self.should_hide_if_not_current_page and not self.should_hide_whenever) self.drawingarea.set_hilight_mode(2) else: self.set_visible(not self.should_hide_whenever) self.drawingarea.set_hilight_mode(3) def set_hilight_mode(self,mode): self.hilight_mode=mode self.set_hilight() def set_current_page(self,p,should_hide_if_not_current_page,should_hide_whenever): self.current_page=p self.should_hide_if_not_current_page=should_hide_if_not_current_page self.should_hide_whenever=should_hide_whenever self.set_hilight() def get_spinbutton(self): return self.spinbutton def get_value(self): return self.griddata.value def set_value(self,v): if self.draging: return if self.direction & self.MASK_VIRTICAL_BAR: if v<0: v=0 if self.max_x self.projectdata.lwidth or rectangle.height > self.projectdata.lheight: lwidth = max(rectangle.width, self.projectdata.lwidth) lheight = max(rectangle.height, self.projectdata.lheight) widget.set_size(lwidth, lheight) for bar in self.rulers: bar.fit_to_size(lwidth, lheight) bar.queue_draw() def on_page_changed_event(self,widget): p=get_int_from_spinbutton(widget) self.current_page=p self.update_rulers() self.layout.set_page(p) def on_toggle_hide_grid(self, widget): if widget.get_active()==0: self.should_hide_whenever=False else: self.should_hide_whenever=True self.update_rulers() self.should_hide_whenever def update_rulers(self): for ri in self.rulers: ri.set_current_page(self.current_page,self.should_hide_if_not_current_page,self.should_hide_whenever) def get_currentpage(self): return self.layout.page class ProjectData: HEIGHT = 600 WIDTH = 600 DEFAULT_SAMPLE_BASE="sample" DEFAULT_JSON_BASE="projectdata" DEFAULT_JSON_EXT=".json" DEFAULT_MAKEFILE_PATH="Makefile" @classmethod def new_from_json(cls,uri): p=urllib.parse.urlparse(uri) path=urllib.parse.unquote(p.path) (destdir,filename)=os.path.split(path) f = open(path) prev_proj= json.load(f) bgimagepath=prev_proj["bgimagepath"] bgimagefullpath=os.path.join(destdir,bgimagepath) prev_proj["pdfuri"]='file://'+urllib.request.pathname2url(bgimagefullpath) return ProjectData(prev_proj,bgimagepath,bgimagefullpath) @classmethod def new_from_pdf(cls,uri): p=urllib.parse.urlparse(uri) path=urllib.parse.unquote(p.path) (destdir,filename)=os.path.split(path) (base,ext)=os.path.splitext(filename) bgimagefullpath=path bgimagepath=filename prev_proj={} prev_proj["pdfuri"]=uri prev_proj["stylename"]=base return ProjectData(prev_proj,bgimagepath,bgimagefullpath) def __init__(self,prev_proj,bgimagepath,bgimagefullpath): self.bgimagepath=bgimagepath self.bgimagefullpath=bgimagefullpath self.set_document(prev_proj["pdfuri"]) if "stylename" in prev_proj: self.stylename=prev_proj["stylename"] else: self.stylename=None if "localcommandsuffix" in prev_proj: self.localcommandsuffix=prev_proj["localcommandsuffix"] else: self.localcommandsuffix=re.sub(r'[^a-zA-Z]','',self.stylename) if "samplebase" in prev_proj: self.samplebase=prev_proj["samplebase"] else: self.samplebase=self.DEFAULT_SAMPLE_BASE if "makefilepath" in prev_proj: self.makefilepath=prev_proj["makefilepath"] else: self.makefilepath=self.DEFAULT_MAKEFILE_PATH if "jsonpath" in prev_proj: self.jsonpath=prev_proj["jsonpath"] else: self.jsonpath=self.DEFAULT_JSON_BASE+self.DEFAULT_JSON_EXT self.samplepath=self.samplebase+".tex" if "boxes" in prev_proj: self.boxes=[BoxData.construct_from_dictionary(d) for d in prev_proj["boxes"]] else: self.boxes=[] if "grids" in prev_proj: self.grids=[GridData.construct_from_dictionary(d) for d in prev_proj["grids"]] else: self.grids=[] if "tables" in prev_proj: self.tables=[TableData.construct_from_dictionary(d) for d in prev_proj["tables"]] else: self.tables=[] def set_document(self,uri): self.boundingboxes=[] if uri: self.document=get_pdfdocument_from_uri(uri) else: self.document=None if self.document: width = 0 height = 0 for i in range(self.document.get_n_pages()): (w,h)=self.document.get_size_of_page(i) self.boundingboxes.append((0,0,w,h)) if width0: xx.sort() x1=xx[0][1] x2=xx[-1][1] else: xx=[(griddata.value,griddata.id) for griddata in self.grids if not griddata.is_horizontal] if len(xx)>0: xx.sort() x1=xx[0][1] x2=xx[-1][1] yy=[(griddata.value,griddata.id) for griddata in self.grids if griddata.is_horizontal and griddata.page==p] if len(yy)>0: yy.sort() y1=yy[0][1] y2=yy[-1][1] else: yy=[(griddata.value,griddata.id) for griddata in self.grids if griddata.is_horizontal] if len(yy)>0: yy.sort() y1=yy[0][1] y2=yy[-1][1] return BoxData(p,x1,x2,y1,y2) class ProjectApplicationData: def __init__(self): self.current_page=0 def set_current_page(self,p): self.current_page=p class ProjectMetaData: def __init__(self,uri): p=urllib.parse.urlparse(uri) path=urllib.parse.unquote(p.path) (dirname,filename)=os.path.split(path) self.current_directory=dirname def get_current_dirctory(self): return self.current_directory class ProjectDataBin: def __init__(self,meta_data,document_data,application_data): self.application_data=application_data self.document_data=document_data self.meta_data=meta_data @classmethod def new_from_uri(cls,uri): p=urllib.parse.urlparse(uri) path=urllib.parse.unquote(p.path) (destdir,filename)=os.path.split(path) (base,ext)=os.path.splitext(filename) if ext==".json": document_data=ProjectData.new_from_json(uri) else: document_data=ProjectData.new_from_pdf(uri) meta_data=ProjectMetaData(uri) application_data=ProjectApplicationData() return ProjectDataBin(meta_data,document_data,application_data) class AFMMainWindow(Gtk.ApplicationWindow): def __init__(self, uri,required_sty_filenames, *args, **kwargs): super().__init__(*args, **kwargs) self.required_sty_filenames=required_sty_filenames[:] self.projectdatabin=ProjectDataBin.new_from_uri(uri) self.projectdata=self.projectdatabin.document_data self.preview=None self.build_ui() self.connect_menu_actions() self.set_default_size(360, 300) self.show_all() def connect_menu_actions(self): action = Gio.SimpleAction.new("saveas", None) action.connect("activate", lambda widget,parm:self.save_as()) self.add_action(action) action = Gio.SimpleAction.new_stateful("toggle_preview", None, GLib.Variant.new_boolean(False)) action.connect("change-state", lambda action,value: self.toggle_preview_dialog()) self.add_action(action) self.toggle_preview_action=action def build_ui(self): box=Gtk.Box() box.set_orientation(Gtk.Orientation.VERTICAL) self.add(box) listarea=BoxDataListArea(self) box.pack_start(listarea.get_vbox(),True,True,0) self.listarea=listarea (button_new,button_remove,button_edit)=listarea.get_buttons() button_new.connect('clicked', self.on_click_new) button_remove.connect('clicked', self.on_click_remove) button_edit.connect('clicked', self.on_click_edit) hbbox = Gtk.HButtonBox() listarea.get_buttonbox().pack_start(hbbox,False,False,0) hbbox.set_layout(Gtk.ButtonBoxStyle.END) button = Gtk.Button.new_with_mnemonic("Add a table") button.connect('clicked', self.on_click_addtable) hbbox.add(button) hbbox = Gtk.HButtonBox() listarea.get_vbox().pack_start(hbbox,False,False,0) hbbox.set_layout(Gtk.ButtonBoxStyle.END) hbbox.set_layout(Gtk.ButtonBoxStyle.END) #button = Gtk.Button.new_with_mnemonic("Show Grids/Preview") #button.connect('clicked', self.on_click_preview) button = Gtk.ToggleButton.new_with_mnemonic("Show Grids/Preview") self.toggle_preview_button_handler_id=button.connect("toggled",lambda widget:self.toggle_preview_dialog()) self.toggle_preview_button=button hbbox.add(button) button = Gtk.Button.new_from_icon_name("document-save-as",Gtk.IconSize.LARGE_TOOLBAR) button.connect('clicked', lambda widget:self.save_as()) hbbox.add(button) hbbox.show_all() def refresh_preview(self): if self.preview: self.preview.refresh_preview() def is_valid_boxdata(self,boxdata): if boxdata: return True else: return True def get_boxdata_by_dialog(self,boxdata,title,message): dialog=BoxDataDialog(title,None,True,boxdata,message,self.projectdata) dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT) r = dialog.run() if r==Gtk.ResponseType.ACCEPT: boxdata=dialog.get_boxdata() else: boxdata=None dialog.destroy() return boxdata def get_valid_boxdata_by_dialog(self,boxdata,title,message): boxdata=self.get_boxdata_by_dialog(boxdata,title,message) while not self.is_valid_boxdata(boxdata): boxdata=self.get_boxdata_by_dialog(boxdata,title,message) return boxdata def confirm_and_add(self,boxdata): title="New box." message="Add the following box:" boxdata=self.get_valid_boxdata_by_dialog(boxdata,title,message) if boxdata: self.listarea.append_boxdata(boxdata) self.projectdata.add_boxdata(boxdata) self.refresh_preview() def on_click_new(self,widget): p=0 if self.preview != None: p=self.preview.get_currentpage() boxdata=self.projectdata.new_boxdata_at_page(p) self.confirm_and_add(boxdata) def confirm_and_remove_by_id(self,boxid,model,itera): if not boxid: return boxdata=self.projectdata.get_boxdata_by_id(boxid) title="Remove BOX." message="Remove the following box:" boxdata=self.get_valid_boxdata_by_dialog(boxdata,title,message) if boxdata: self.projectdata.pop_boxdata_by_id(boxid) self.refresh_preview() model.remove(itera) def on_click_remove(self,widget): (model,iteralist,boxids)=self.listarea.get_selected_ids() if not boxids: return for (itera,boxid,) in zip(iteralist,boxids): self.confirm_and_remove_by_id(boxid,model,itera) def on_click_edit(self,widget): (model,iteralist,boxids)=self.listarea.get_selected_ids() if not boxids: return for (itera,boxid) in zip(iteralist,boxids): if not boxid: return boxdata=self.projectdata.get_boxdata_by_id(boxid) title="BOX id "+boxid message="Edit the following box:" boxdata=self.get_valid_boxdata_by_dialog(boxdata,title,message) if boxdata: self.projectdata.pop_boxdata_by_id(boxid) self.projectdata.add_boxdata(boxdata) self.refresh_preview() model.remove(itera) self.listarea.append_boxdata(boxdata) def get_tabledata_by_dialog(self,title,message,current_page): dialog=TableDataDialog(title,None,True,message,self.projectdata,current_page) dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_OK, Gtk.ResponseType.ACCEPT) r = dialog.run() if r==Gtk.ResponseType.ACCEPT: tabledata=dialog.get_tabledata() else: tabledata=None dialog.destroy() return tabledata def confirm_and_addtable(self): title="New Table." message="Add boxes:" p=0 if self.preview != None: p=self.preview.get_currentpage() t=self.get_tabledata_by_dialog(title,message,p) if t: (boxes,tabledata)=t for row in boxes: for boxdata in row: self.listarea.append_boxdata(boxdata) self.projectdata.add_boxdata(boxdata) self.projectdata.add_tabledata(tabledata) self.refresh_preview() def on_click_addtable(self,widget): self.confirm_and_addtable() ############################################################# def save_as(self): dialog = Gtk.FileChooserDialog(title='Select zip file to save.', parent=self, action=Gtk.FileChooserAction.SAVE) dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_SAVE, Gtk.ResponseType.ACCEPT) dialog.set_current_folder(self.projectdatabin.meta_data.get_current_dirctory()) dialog.set_current_name(self.projectdata.stylename+'-stylefile.zip') dialog.set_do_overwrite_confirmation(True) filter = Gtk.FileFilter() filter.set_name("ZIP files") filter.add_mime_type("application/x-compress") filter.add_mime_type("application/x-zip-compressed") filter.add_mime_type("application/zip") filter.add_mime_type("application/x-zip") filter.add_pattern("*.zip") dialog.add_filter(filter) filter = Gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") dialog.add_filter(filter) r = dialog.run() if r!=Gtk.ResponseType.ACCEPT: dialog.destroy() return destzipfilename=dialog.get_filename() dialog.destroy() print_log('creating '+destzipfilename+'.') rootdir=os.path.splitext(os.path.basename(destzipfilename))[0] destzip=zipfile.ZipFile(destzipfilename,'w') self.projectdata.output_to_zipfile(destzip,rootdir,self.required_sty_filenames) def toggle_preview_dialog(self): self.toggle_preview_button.handler_block(self.toggle_preview_button_handler_id) if self.preview: self.preview.destroy() self.preview=None self.toggle_preview_button.set_active(False) self.toggle_preview_action.set_state(GLib.Variant.new_boolean(False)) else: self.toggle_preview_button.set_active(True) self.toggle_preview_action.set_state(GLib.Variant.new_boolean(True)) dialog=HoganDialog("Preview",None,True,self.projectdatabin,0) dialog.connect("delete_event", lambda widget,event:self.toggle_preview_dialog()) dialog.show() self.preview=dialog self.toggle_preview_button.handler_unblock(self.toggle_preview_button_handler_id) class AFMApplication(Gtk.Application): def __init__(self,*args, **kwargs): super().__init__(*args, **kwargs) self.set_flags(Gio.ApplicationFlags.HANDLES_OPEN) self.APP_NAME="AFM" self.APP_VERSION="3.0.0" self.APP_DESCRIPTION="AFM is not a fuzzy mule. Application Form Maker." self.APP_URL="https://github.com/a175/afm/" self.APP_LOGO_FILE=None #self.APP_LOGO_FILE="./icon.png" self.MENUBAR_XML_FILENAME = os.path.join(os.path.dirname(__file__),"./afm_ui.xml") rs="./documentonform.sty" self.REQUIRED_STY_FILENAMES = [(os.path.join(os.path.dirname(__file__),rs),rs)] def do_startup(self): Gtk.Application.do_startup(self) self.build_menubar() def do_activate(self): self.select_file_and_open_as_new() def do_open(self,files,n_files,hint): for gfile in files: print(gfile.get_path(),gfile.get_uri()) uri=gfile.get_uri() window=AFMMainWindow(uri,self.REQUIRED_STY_FILENAMES,application=self) window.present() def select_file_and_open_as_new(self): dialog = Gtk.FileChooserDialog(title='Choose pdf file.',parent=None,action=Gtk.FileChooserAction.OPEN) dialog.add_buttons(Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT, Gtk.STOCK_OPEN, Gtk.ResponseType.ACCEPT) filter = Gtk.FileFilter() filter.set_name("PDF files") filter.add_mime_type("application/pdf") filter.add_mime_type("application/x-pdf") filter.add_pattern("*.pdf") dialog.add_filter(filter) filter = Gtk.FileFilter() filter.set_name("project JSON files") filter.add_mime_type("application/json") filter.add_mime_type("application/x-json") filter.add_pattern("*"+ProjectData.DEFAULT_JSON_EXT) dialog.add_filter(filter) filter = Gtk.FileFilter() filter.set_name("All files") filter.add_pattern("*") dialog.add_filter(filter) r = dialog.run() if r==Gtk.ResponseType.ACCEPT: file=dialog.get_file() dialog.destroy() self.open([file],"open_as_new") else: uri=None dialog.destroy() def build_menubar(self): builder = Gtk.Builder.new_from_file(self.MENUBAR_XML_FILENAME) menubar = builder.get_object("menubar") self.set_menubar(menubar) action = Gio.SimpleAction.new("open", None) action.connect("activate", self.on_open) self.add_action(action) action = Gio.SimpleAction.new("quit", None) action.connect("activate", self.on_quit) self.add_action(action) action = Gio.SimpleAction.new("about", None) action.connect("activate", self.on_about) self.add_action(action) action = Gio.SimpleAction.new("help", None) action.connect("activate", self.on_help) self.add_action(action) def on_quit(self,action,param): self.quit() def on_about(self, action, param): about_dialog = Gtk.AboutDialog() about_dialog.set_program_name(self.APP_NAME) about_dialog.set_version(self.APP_VERSION) about_dialog.set_website(self.APP_URL) about_dialog.set_comments(self.APP_DESCRIPTION) if self.APP_LOGO_FILE: pixbuf=GdkPixbuf.Pixbuf.new_from_file(self.APP_LOGO_FILE) about_dialog.set_logo(pixbuf) about_dialog.present() def on_help(self, action, param): help_dialog = Gtk.MessageDialog(flags=0,buttons=Gtk.ButtonsType.OK,text="Help") help_dialog.format_secondary_text("Help for AFM") link=Gtk.LinkButton.new_with_label("https://github.com/a175/afm/","link to help") link.show() contentarea=help_dialog.get_content_area() contentarea.add(link) help_dialog.run() help_dialog.destroy() def on_open(self,action,param): self.select_file_and_open_as_new() def main(): app=AFMApplication() app.run(sys.argv) if __name__ == "__main__": main()