--- a +++ b/src/main.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# main.py +# +# Copyright 2009-2010 Sandro Mani <manisandro@gmail.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. + + +import gtk +import gtkspell +import os +import subprocess +import tempfile + +import config +import dialogs +import drawingarea +#import recognize + +class gimagereader: + def __init__(self,builder,filename=None): + self.window = builder.get_object("main") + if not self.window: + return + self.window.connect("destroy", gtk.main_quit) + # Some variables + self.builder=builder + self.status=builder.get_object("status_msg") + self.output=builder.get_object("output") + + self.curdir=None + self.filename=None + self.lang_count=0 + self.lang=None + self.spellchecker=None + self.append_mode=0 + + # Complete the GUI with some widgets + self.outbuffer=gtk.TextBuffer(None) + self.output.set_buffer(self.outbuffer) + + self.langcombo = gtk.combo_box_new_text() + builder.get_object("tb_language").add(self.langcombo) + self.langcombo.show() + self.langcombo.set_sensitive(False) + self.langcombo.connect('changed', self.set_language) + + self.appendcombo = gtk.combo_box_new_text() + builder.get_object("tb_appendmode").add(self.appendcombo) + self.appendcombo.append_text("At cursor") + self.appendcombo.append_text("At end") + self.appendcombo.append_text("Replace") + self.appendcombo.set_active(0) + self.appendcombo.show() + self.appendcombo.connect("changed", self.set_append) + self.drawingarea=drawingarea.drawingarea(builder) + + # Connect signals + dic = { "on_tb_openimg_clicked" : self.open_image, + "on_tb_zoomin_clicked" : (self.drawingarea.zoom,+0.1), + "on_tb_zoomout_clicked" : (self.drawingarea.zoom,-0.1), + "on_tb_bestzoom_clicked" : self.drawingarea.zoom_fit, + "on_tb_resetzoom_clicked" : self.drawingarea.zoom_reset, + "on_tb_lrotate_clicked" : (self.drawingarea.rotate,+90), + "on_tb_rrotate_clicked" : (self.drawingarea.rotate,-90), + "on_tb_doocr_clicked" : self.doocr, + "on_menu_open_activate" : self.open_image, + "on_menu_config_activate" : self.show_config, + "on_menu_zoomin_activate" : (self.drawingarea.zoom,+0.1), + "on_menu_zoomout_activate" : (self.drawingarea.zoom,-0.1), + "on_menu_bestzoom_activate" : self.drawingarea.zoom_fit, + "on_menu_resetzoom_activate" : self.drawingarea.zoom_reset, + "on_menu_lrotate_activate" : (self.drawingarea.rotate,+90), + "on_menu_rrotate_activate" : (self.drawingarea.rotate,-90), + "on_menu_about_activate" : self.about, + "on_btn_save_clicked": self.out_save, + "on_tb_clearout_clicked" : self.clearoutput, + "output_sel_change" : self.outputselchange, + "on_btn_stripcrlf_clicked" : self.stripcrlf, + "gtk_main_quit" : gtk.main_quit } + builder.connect_signals(dic) + + # Load other modules + self.window.show() + self.conf=config.config(builder) + if not self.conf.valid: + self.window.connect('event-after', gtk.main_quit) + else: + self.load_dictionaries() + if filename==None: + self.status.set_text("Open an image to begin...") + self.curdir=os.path.expanduser("~") + else: + self.load_image(filename) + + ### Open and load images ### + def open_image(self,widget): + filename=dialogs.open_image(self.curdir,self.window) + if filename!="": + self.load_image(filename) + + def load_image(self,filename): + self.status.set_text("Loading '"+filename+"'...") + if not self.drawingarea.load(filename): + dialogs.error_dialog('Failed to load image','The file might not be an image or be corrupted.',self.window) + if self.filename==None: + self.status.set_text("Open an image to begin...") + else: + self.status.set_text("Drag a rectangle around the area to recognize, then press 'Recognize'...") + return + self.filename=filename + self.curdir=os.path.dirname(self.filename) + self.window.set_title(os.path.basename(self.filename)+" - gImageReader") + for obj in ("tb_zoomin", "tb_zoomout", "tb_bestzoom", "tb_resetzoom", "tb_rrotate", "tb_lrotate", + "menu_zoomin", "menu_zoomout", "menu_bestzoom", "menu_resetzoom", "menu_rrotate", "menu_lrotate"): + self.builder.get_object(obj).set_sensitive(True) + self.langcombo.set_sensitive(True) + self.clearoutput() + self.status.set_text("Drag a rectangle around the area to recognize, then press 'Recognize'...") + + ### Recognition ### + def set_append(self,widget): + i = widget.get_active() + if i==-1: + return + self.append_mode=i + + def doocr(self,widget): + self.window.set_sensitive(False) + self.status.set_text('Recognizing area, please wait...') + if file==None or os.path.isfile(self.filename)==False: + dialogs.error_dialog('Failed to perform recognition',"The source file '"+self.filename+"' could not be found.",self.window) + self.window.set_sensitive(True) + self.status.set_text("Drag a rectangle around the area to recognize, then press 'Recognize'...") + return + # Create cropped file + sel=self.drawingarea.osel + temptif=tempfile.mkstemp('.tif','tmpocr')[1]; + if sel[2]<0: + sel[0]+=sel[2] + sel[2]*=-1 + if sel[3]<0: + sel[1]+=sel[3] + sel[3]*=-1 + crop=str(int(sel[2]))+"x"+str(int(sel[3]))+"+"+str(int(sel[0]))+"+"+str(int(sel[1])) + p=subprocess.Popen([self.conf.conv_path+"convert",self.filename,"-depth","8","-compress","none","-rotate",str(self.drawingarea.angle),"-crop",crop,temptif], shell=False, stderr=subprocess.PIPE) + out=p.communicate() + if p.returncode!=0 or out[1]!='': + dialogs.error_dialog('Failed to perform recognition',"convert returned:\n"+out[1],self.window) + self.window.set_sensitive(True) + self.status.set_text("Drag a rectangle around the area to recognize, then press 'Recognize'...") + return + # Recognize + tmptxt=tempfile.mkstemp('.txt','tmpocr')[1]; + p=subprocess.Popen([self.conf.tess_path+"tesseract",temptif,tmptxt[:-4],"-l",self.lang], shell=False, stderr=subprocess.PIPE) + out=p.communicate() + os.remove(temptif) + if p.returncode!=0 or (out[1]!='' and out[1]!="Tesseract Open Source OCR Engine\n"): + dialogs.error_dialog('Failed to perform recognition',"tesseract returned:\n"+out[1]+"\nThis probabily is a bug in tesseract, retry using an image at a different resolution or by varying the selected region.",self.window) + self.window.set_sensitive(True) + self.status.set_text("Drag a rectangle around the area to recognize, then press 'Recognize'...") + return + outfile = open(tmptxt, "r") + if self.append_mode==0: #at cursor (selection) + iters=self.outbuffer.get_selection_bounds() + if len(iters)!=0: + self.outbuffer.delete(iters[0],iters[1]) + self.outbuffer.insert_at_cursor(outfile.read()) + elif self.append_mode==1: #at end + self.outbuffer.insert(self.outbuffer.get_end_iter(),outfile.read()) + elif self.append_mode==2: #replace + self.outbuffer.set_text(outfile.read()) + outfile.close() + os.remove(tmptxt) + if self.spellchecker!=None: + self.spellchecker.recheck_all() + self.builder.get_object("outputbox").show() + self.builder.get_object("hpane").set_position(-1) + self.status.set_text("Drag a rectangle around the area to recognize, then press 'Recognize'...") + self.window.set_sensitive(True) + + def outputselchange(self,widget,event): + self.builder.get_object("btn_stripcrlf").set_sensitive(self.outbuffer.get_has_selection()==True) + + def stripcrlf(self,widget): + self.output.set_editable(False) + iters=self.outbuffer.get_selection_bounds() + if len(iters)==0: + return #should not happen + txt=self.outbuffer.get_text(iters[0],iters[1]) + txt=txt.replace("\n"," ").replace(" "," ") + self.outbuffer.delete(iters[0],iters[1]) + self.outbuffer.insert_at_cursor(txt) + self.output.set_editable(True) + self.builder.get_object("btn_stripcrlf").set_sensitive(False) + + def out_save(self,widget): + savefile=dialogs.save_text(self.curdir) + if savefile=="": + return + try: + outfile = open(savefile, 'w') + outfile.write(self.outbuffer.get_text(self.outbuffer.get_start_iter(),self.outbuffer.get_end_iter())) + outfile.close() + except: + dialogs.error_dialog("Failed to save output","Check that you have writing permissions in the selected folder.",self.window) + + def clearoutput(self,widget=None): + self.outbuffer.set_text("") + self.builder.get_object("outputbox").hide() + + ### Set and detect languages ### + def show_config(self,widget): + self.conf.show() + self.load_dictionaries() + + def load_dictionaries(self): + for i in range(0,self.lang_count): + self.langcombo.remove_text(0) + self.lang_count=0 + for lang in (("eng","English"),("fra","French"),("deu","German"),("ita","Italian"),("spa","Spanish"),("nld","Dutch")): + if os.path.isfile(self.conf.dict_path+lang[0]+".unicharset"): + self.lang_count+=1 + self.langcombo.append_text(lang[1]) + if self.lang_count==0: + if dialogs.question_dialog("No dictionaries found","Do you want to correct the configuration? (Pressing 'No' will quit the program.)",self.window): + self.conf.show() + else: + self.window.connect('event-after', gtk.main_quit) + else: + self.langcombo.set_active(0) + + def set_language(self,widget): + list=widget.get_model() + i=widget.get_active() + if i==-1: + return + for lang in (("eng","English","en_US"),("fra","French","fr_FR"),("deu","German","de_DE"),("ita","Italian","it_IT"),("spa","Spanish","es_ES"),("nld","Dutch","nl_NL")): + if list[i][0]==lang[1]: + self.lang=lang[0] + if self.spellchecker!=None: + gtkspell.Spell.detach(self.spellchecker) + self.spellchecker=None + try: + self.spellchecker=gtkspell.Spell(self.output,lang[2]) + except: + pass + ### About ### + def about(self,widget): + response = self.builder.get_object("aboutdialog").run() + self.builder.get_object("aboutdialog").hide() +