""" pyEdit for S60 Copyright (c) 2005 - 2007 Lasse Huovinen """ import e32 import appuifw import os import sys import pyS60uiutil import pyS60cnfutil try: #= Try importing an external library called UITRICKS that allows #= customizing certain softkey labels. #= For further information regarding the UITRICKS library check out #= http://cyke64.googlepages.com from key_tricks import EAknSoftkeyExit, EAknSoftkeyOptions import uitricks LIB_UITRICKS_PRESENT = True except ImportError: appuifw.note(u'UITRICKS library missing. Menu labels may be messed up.', 'error') LIB_UITRICKS_PRESENT = False global options_menu_h global doc_list global default_path global sentences_list global clipboard_content global settings_changed EDITOR_CONFIG_FILE = 'dat/pyeditconf' EDITOR_HELP_FILE = 'pyedits60help.txt' escript_lock = e32.Ao_lock() CNF = pyS60cnfutil.configDb() #= #= Some useful helper functions. #= def set_rlabel(label): """ Set right softkey label provided that the required libraries are present. """ if LIB_UITRICKS_PRESENT: uitricks.set_text(label, EAknSoftkeyExit) def set_llabel(label): """ Set left softkey label provided that the required libraries are present. """ if LIB_UITRICKS_PRESENT: uitricks.set_text(label, EAknSoftkeyOptions) def unicode_list(list_txt): """ Takes a list of text strings and returns the list with each item unicoded. """ ulist = [] for i in list_txt: ulist.append(unicode(i)) return ulist #= #= Editor configuration. #= #= #= Currently supported text encodings. #= TXT_CODEC_NAMES = ['utf-8', 'utf-16', 'latin-1', 'iso-8859-1', 'ascii', 'binary-hex'] #= Max number of characters that fit to a list box line. #= If this number is exceeded the dialog automatically #= cuts the end of the line. MAX_LIST_BOX_LINE_LENGTH = 20 #= #= Dialogs for favorite document selection. #= MAX_FAVDOC_N = 7 def select_favdoc_dlg(index): pname = 'doc'+str(index) if CNF.getConfigParameter('favdocs', pname) != '': if appuifw.query(u'Leave unassigned?', 'query'): CNF.setConfigParameter('favdocs', pname, '') return doc = pyS60uiutil.dirBrowser('Select favorite document').select(None) if doc == None or doc == ('', ''): return doc_name = os.path.join(doc[0], doc[1]) if not os.path.isfile(doc_name): appuifw.note(u'Not a regular file', 'error') else: CNF.setConfigParameter('favdocs', pname, doc_name) def u_favdoc_list(): ulist = [] for i in range(MAX_FAVDOC_N): item = CNF.getConfigParameter('favdocs', 'doc'+str(i)) if len(item) > MAX_LIST_BOX_LINE_LENGTH: item = item[:1]+'..'+item[-(MAX_LIST_BOX_LINE_LENGTH-2):] ulist.append(unicode(item)) return ulist #= #= Miscellaneous configuration dialogs. #= MAX_FONT_SIZE = 256 #= ok? DEF_FONT_SIZE = 12 #= ok? def default_sentences_file_dlg(): global sentences_list if CNF.getConfigParameter('misc', 'default_sentences_file') != '': if appuifw.query(u'Leave sentences unassigned?', 'query'): CNF.setConfigParameter('misc', 'default_sentences_file', '') set_default_sentences_file() return doc = pyS60uiutil.dirBrowser('Select default sentences').select(None) if doc == None or doc == ('', ''): return doc_name = os.path.join(doc[0], doc[1]) if not os.path.isfile(doc_name): appuifw.note(u'Not a regular file', 'error') else: CNF.setConfigParameter('misc', 'default_sentences_file', doc_name) #= Take the selected sentences file into use... should be done in #= apply_editor_settings() but not straightforward thing to fix. sentences_list = load_sentences_file(doc_name) def default_document_file_dlg(): FIX: codec selection is missing from this dlg if CNF.getConfigParameter('misc', 'default_document_file') != '': if appuifw.query(u'Leave default document unassigned?', 'query'): CNF.setConfigParameter('misc', 'default_document_file', '') return doc = pyS60uiutil.dirBrowser('Select default document').select(None) if doc == None or doc == ('', ''): return doc_name = os.path.join(doc[0], doc[1]) if not os.path.isfile(doc_name): appuifw.note(u'Not a regular file', 'error') else: CNF.setConfigParameter('misc', 'default_document_file', doc_name) def initial_work_directory_dlg(): old = CNF.getConfigParameter('misc', 'initial_work_directory') if old != '': if appuifw.query(u'Leave directory unassigned?', 'query'): CNF.setConfigParameter('misc', 'initial_work_directory', '') return if old == '': old = None dir = pyS60uiutil.dirBrowser('Select default work directory').select(old) if dir == None or dir == ('', ''): return if not os.path.isdir(dir[0]): appuifw.note(u'Not a directory', 'error') else: CNF.setConfigParameter('misc', 'initial_work_directory', dir[0]) def screen_mode_dlg(): modes = ['normal', 'large', 'full'] ix = appuifw.popup_menu(unicode_list(modes)) if not ix == None: CNF.setConfigParameter('misc', 'screen_mode', modes[ix]) def font_size_dlg(): val = CNF.getConfigParameter('misc', 'font_size') if not val: try: val = appuifw.app.t.font[1] except: val = None if val == None or val < 0 or val > MAX_FONT_SIZE: val = DEF_FONT_SIZE sel = -1 while sel != None and (sel < 0 or sel > MAX_FONT_SIZE): sel = appuifw.query(u'Select font size (0-' + \ unicode(str(MAX_FONT_SIZE))+u')', 'number', val) if sel != None: CNF.setConfigParameter('misc', 'font_size', sel) def font_color_dlg(): font_color = CNF.getConfigParameter('misc', 'font_color') if font_color == None: font_color = (0, 0, 0) predef_menu = [(u'Black', (0,0,0)), \ (u'Blue', (0,0,255)), \ (u'Green', (0,255,0)), \ (u'Red', (255,0,0)), \ (u'Custom', font_color)] custom_index = 4 sel = pyS60uiutil.fontColorSelectionDlg(None, predef_menu, custom_index).select() if sel != None: CNF.setConfigParameter('misc', 'font_color', sel) def editor_settings(): """ Editor configuration interface. """ global doc_list, settings_changed tab_list = [u'Codec', u'Docs', u'Misc'] settings_changed = False set_rlabel(u'Exit') set_llabel(u'') def conf_exit(): appuifw.app.set_tabs([], lambda x: None) pyS60uiutil.restore_app_info(edit_app_info) config_lock.signal() def setting_page(page_ix): appuifw.app.menu = [] if page_ix == 0: def _make_lbox_menu(): lbmenu = unicode_list(TXT_CODEC_NAMES) lix = doc_list.get_active_doc().get_codec() lbmenu[lix] = lbmenu[lix] + u' *' return lbmenu def cb(): global settings_changed settings_changed = True doc_list.get_active_doc().set_codec(lbox.current()) #= Refresh the tabs and active page. appuifw.app.set_tabs(tab_list, setting_page) appuifw.app.activate_tab(page_ix) lbox.set_list(_make_lbox_menu(), lbox.current()) lbox = appuifw.Listbox(_make_lbox_menu(), cb) appuifw.app.body = lbox elif page_ix == 1: def cb(): global settings_changed settings_changed = True #= Tabs must removed and added again due to fileBrowser appuifw.app.set_tabs([], lambda x: None) select_favdoc_dlg(lbox.current()) #= Refresh the tabs and active page. appuifw.app.set_tabs(tab_list, setting_page) appuifw.app.activate_tab(page_ix) lbox.set_list(u_favdoc_list(), lbox.current()) lbox = appuifw.Listbox(u_favdoc_list(), cb) appuifw.app.body = lbox elif page_ix == 2: #= #= More stuff later on #= FIX: add default doc here... def _make_lbox_menu(): tmp = CNF.getConfigParameter('misc', 'default_sentences_file') tmp = os.path.split(tmp)[1] pre = 'Words: ' if len(pre) + len(tmp) > MAX_LIST_BOX_LINE_LENGTH: c_avail = MAX_LIST_BOX_LINE_LENGTH - len(pre) - 1 c_left = c_avail / 2 if (c_left * 2) < c_avail: c_right = c_left + 1 else: c_right = c_left tmp = tmp[:c_left]+'*'+tmp[-c_right:] tmp = pre + tmp lst = [unicode(tmp)] tmp = CNF.getConfigParameter('misc', 'initial_work_directory') pre = 'Work: ' if len(pre) + len(tmp) > MAX_LIST_BOX_LINE_LENGTH: c_avail = MAX_LIST_BOX_LINE_LENGTH - len(pre) - 3 tmp = tmp[:2]+'*'+tmp[-c_avail:] tmp = pre + tmp lst.append(unicode(tmp)) val = str(CNF.getConfigParameter('misc', 'screen_mode')) lst.append(u'Screen mode: ' + unicode(val)) val = str(CNF.getConfigParameter('misc', 'font')) lst.append(u'Font: ' + unicode(val)) val = str(CNF.getConfigParameter('misc', 'font_size')) lst.append(u'Font size: ' + unicode(val)) lst.append(u'Font color') return lst def cb(): global settings_changed settings_changed = True #= Tabs must removed and added again due to fileBrower appuifw.app.set_tabs([], lambda x: None) i = lbox.current() if i == 0: default_sentences_file_dlg() elif i == 1: initial_work_directory_dlg() elif i == 2: screen_mode_dlg() elif i == 3: CNF.setConfigParameter('misc', 'font', pyS60uiutil.\ fontSelectionDlg().select()) elif i == 4: font_size_dlg() elif i == 5: font_color_dlg() #= Refresh the tabs and active page. appuifw.app.set_tabs(tab_list, setting_page) appuifw.app.activate_tab(page_ix) lbox.set_list(_make_lbox_menu(), lbox.current()) lbox = appuifw.Listbox(_make_lbox_menu(), cb) appuifw.app.body = lbox edit_app_info = pyS60uiutil.save_current_app_info() appuifw.app.t = None config_lock = e32.Ao_lock() appuifw.app.screen = 'normal' appuifw.app.exit_key_handler = conf_exit appuifw.app.set_tabs(tab_list, setting_page) appuifw.app.activate_tab(0) setting_page(0) config_lock.wait() set_rlabel(u'Words') set_llabel(u'Options') if settings_changed: apply_editor_settings() if not appuifw.query(u'Save changes as default settings', 'query'): return #= Store default codec CNF.setConfigParameter('txtcodecs', 'default_codec_ix', doc_list.get_active_doc().get_codec()) #= Would be good if saveConfigData() indicated whether is was able #= to save the configuration or not. CNF.saveConfigData() else: appuifw.note(u'Settings not changed', 'conf') def apply_editor_settings(): """ Apply certain editor parameters read from the configuration file or set by the user. """ global default_path current_text = appuifw.app.t.get() current_pos = appuifw.app.t.get_pos() tmp = CNF.getConfigParameter('misc', 'font') if not tmp == None: appuifw.app.t.font = tmp tmp = CNF.getConfigParameter('misc', 'font_size') if tmp is not None: fontname = appuifw.app.t.font[0] fontflags = appuifw.app.t.font[2] appuifw.app.t.font = (fontname, tmp, fontflags) tmp = CNF.getConfigParameter('misc', 'font_color') if not tmp == None: appuifw.app.t.color = tmp appuifw.app.t.clear() appuifw.app.t.set(current_text) appuifw.app.t.set_pos(current_pos) default_path = CNF.getConfigParameter('misc', 'initial_work_directory') if default_path == '': default_path = None appuifw.app.screen = CNF.getConfigParameter('misc', 'screen_mode') def initialize_editor_settings(): """ Read settings from a configuration file and setup missing values to reasonable defaults. """ CNF.loadConfigData(EDITOR_CONFIG_FILE) dfl_codecs = {} dfl_codecs['default_codec_ix'] = 0 if CNF.getConfigClass('txtcodecs') == None: CNF.setConfigClass('txtcodecs', dfl_codecs) else: CNF.setClassMissingParameters('txtcodecs', dfl_codecs) dfl_docs = {} for i in range(MAX_FAVDOC_N): dfl_docs['doc'+str(i)] = '' if CNF.getConfigClass('favdocs') == None: CNF.setConfigClass('favdocs', dfl_docs) else: CNF.setClassMissingParameters('favdocs', dfl_docs) dfl_misc = {} dfl_misc['default_document_file'] = '' dfl_misc['default_sentences_file'] = '' dfl_misc['initial_work_directory'] = '' dfl_misc['screen_mode'] = 'normal' dfl_misc['font'] = None dfl_misc['font_size'] = None dfl_misc['font_color'] = None if CNF.getConfigClass('misc') == None: CNF.setConfigClass('misc', dfl_misc) else: CNF.setClassMissingParameters('misc', dfl_misc) #= #= Directory and file selection #= def select_file(default_path, message): set_rlabel(u'Cancel') sel = pyS60uiutil.dirBrowser(message).select(default_path) set_rlabel(u'Words') if sel == None or sel == ('', ''): return None else: return sel #= #= Saving #= def convert_text(teksti, codec): """ Converts the unicoded text to a format specified by codec; such as 'ascii', 'iso-8859-1', or 'latin-1'. """ cnt_euro = cnt_parag = cnt_other = i = 0 output = '' while 1: try: #= First lets see whether a character encodes properly, i.e., #= encode the character using 'strict' (default) conversion. output = output + teksti[i].encode(codec) except UnicodeError: #= If the character does not encode properly lets see whether it is #= something that can be converted to a character that closely #= looks like the original one. If we have no replacement for it #= then lets encode the character again using 'replace' mode #= meaning that Python inserts 'question mark' in the place of the #= original character. # print u'Uncodable (iso) char >' + teksti[i] + u'<' # print ord(teksti[i]) if teksti[i] == u'\u20ac': #= 'Euro sign' output = output + 'e' cnt_euro += 1 elif teksti[i] == u'\u2029': #= 'New Line' which is actually 'paragraph separator'. output = output + '\n' cnt_parag += 1 else: #= Instead of calling encode() we could insert the question #= mark immediately. Well, in this way we ensure 'standard #= operation'. output = output + teksti[i].encode(codec, 'replace') cnt_other += 1 except IndexError: break i = i + 1 if cnt_euro or cnt_other: #= Lets inform the user that all the characters could not be saved as #= they were originally. However, 'paragraph separator' -> 'new line' #= conversion is not error but merely natural behavior. msg = u'' if cnt_other: msg += unicode(str(cnt_other)) + u' chars replaced with ?\n' if cnt_euro: msg += unicode(str(cnt_euro)) + u' ' + u'\u20ac' + \ u' replaced with e\n' appuifw.note(msg, 'info') return output def relinehexstr(hexstr): """ This function could be used to reorganize the lines in the editor view if the document is in binary-hex format. Note: the function is not used at the moment. Note2: not tested yet """ linedstr = '' i = 0 for bt in hexstr.split(','): linedstr += bt+u',' i = (i + 1) % 8 if i == 0: linedstr += u'\u2029' return linedstr[ : -1] #= remove last colon def convent2hexstr(teksti): """ Convers a text string into hex digits """ hexstr = '' i = 0 for c in teksti: c2 = ord(c) if c2 <= 0xF: hexstr += '0'+hex(c2)[2:]+',' else: hexstr += hex(c2)[2:]+',' i = (i + 1) % 8 if i == 0: hexstr += '\n' return unicode(hexstr [ : -1]) #= remove last colon def hexstr2string(hexstr): """ Convers a string of hex digits into a text string. """ string = '' # for c in hexstr.split(','): for c in hexstr.replace(u'\u2029', '').lstrip().rstrip().split(','): #if not c == '': # remove when the last , removed from viewed text try: string += chr(int(c, 16)) except ValueError: appuifw.note(u'Invalid hex digit "'+c+u'"', 'error') raise ValueError return string def save(file_name, text, codec): #= If the text is being saved in other than 'UTF-8' or 'UTF-16' coding #= all the used characters may not be supported. The euro sign and 'new #= line' used in Text UI control are some examples. #= Since Python 2.2 does not support codec.register_error (available on #= Python 2.3 onwards) lets do the conversion in a hard way, i.e., call #= a function to do appropriate conversion. if not ((codec == 'utf-8') or (codec == 'utf-16') or \ (codec == 'binary-hex')): text = convert_text(text, codec) elif (codec == 'binary-hex'): text = hexstr2string(text) else: text = text.encode(codec) mfile = file(file_name, 'wb') mfile.write(text) mfile.close() def save_file_as(teksti): """ Note: the content of the currently active document is not necessarily up to date in the 'doc' object. """ global doc_list, default_path path = select_file(default_path, 'Select directory') if path == None: appuifw.note(u'File NOT saved', 'info') return default_path = path[0] fname = appuifw.query(u'Type file name', 'text', unicode(path[1])) if fname == None: appuifw.note(u'File NOT saved', 'info') return full_doc_name = os.path.join(path[0], fname) if os.path.isdir(full_doc_name): appuifw.note(u'Cannot overwrite directory', 'error') return if os.path.isfile(full_doc_name): if not appuifw.query(unicode('Overwrite ' + full_doc_name + '?'), 'query'): appuifw.note(u'File NOT saved', 'info') return try: codec = TXT_CODEC_NAMES[doc_list.get_active_doc().get_codec()] save(full_doc_name, teksti, codec) doc_list.get_active_doc().set_full_name(full_doc_name) appuifw.note(u'File saved', 'conf') except UnicodeError,detail: appuifw.note(u'Error while saving!\n' + unicode(detail), 'error') except ValueError: #= user informed already; this exception was raised in function #= hexstr2string() pass except IOError: appuifw.note(u'Error, possibly invalid file name!', 'error') #except: # appuifw.note(u'Error while saving!', 'error') def save_file(teksti): """ Note: the content of the currently active document is not necessarily up to date in the 'doc' object. """ global doc_list name = doc_list.get_active_doc().get_full_name() if name == '': save_file_as(teksti) else: codec = TXT_CODEC_NAMES[doc_list.get_active_doc().get_codec()] try: save(name, teksti, codec) appuifw.note(u'File saved', 'conf') except UnicodeError,detail: appuifw.note(u'Error while saving!\n' + unicode(detail), 'error') except ValueError: #= user informed already; this exception was raised in function #= hexstr2string() pass except IOError: appuifw.note(u'Error, possibly invalid file name!', 'error') #except: # appuifw.note(u'Error while saving!', 'error') #= #= Loading and inserting #= def load(file_name, codec=None): """ Load the given file. If the text codec is not provided the user will be prompted to select one. In the case the user is prompted to select codec the codec will be used for this document unless otherwise commanded. """ if codec == None: menu = unicode_list(TXT_CODEC_NAMES) codec_ix = appuifw.popup_menu(menu) if codec_ix == None: return None, None codec = TXT_CODEC_NAMES[codec_ix] else: #= Since the codec is already known, no need to return index here. codec_ix = None if codec == u'binary-hex': mfile = file(file_name, 'rb') teksti = mfile.read() #= reads everything. mfile.close() hexstr = convent2hexstr(teksti) return hexstr, codec_ix else: mfile = file(file_name, 'r') teksti = mfile.read() #= reads everything. mfile.close() return teksti.decode(codec), codec_ix def insert_file(): global default_path s = select_file(default_path, 'Select file to insert') if s == None: appuifw.note(u'No file loaded!', 'info'); return default_path = s[0] full_doc_name = os.path.join(s[0], s[1]) if os.path.isdir(full_doc_name): appuifw.note(u'Cannot insert a directory!', 'error') return try: teksti, dummy = load(full_doc_name) if teksti == None: return #except UnicodeError, detail: # appuifw.note(u'Error while reading!\n' + unicode(detail), # 'error') # return except: type, value = sys.exc_info() [:2] appuifw.note(u'Error while reading!\n' + \ unicode(str(type)+':'+str(value)), 'error') return appuifw.app.t.add(unicode(teksti)) def load_file(full_doc_name): if os.path.isdir(full_doc_name): appuifw.note(u'Cannot load a directory!', 'error') return if not os.path.isfile(full_doc_name): appuifw.note(u'File does not exist', 'error') return try: teksti, codec_ix = load(full_doc_name) if teksti == None: return #except UnicodeError, detail: # appuifw.note(u'Error while reading!\n' + unicode(detail), 'error') # return except: type, value = sys.exc_info() [:2] appuifw.note(u'Error while reading!\n' + \ unicode(str(type)+':'+str(value)), 'error') return if ((doc_list.get_nr_of_docs() == 1) and (doc_list.get_active_doc().get_full_name() == '') and (appuifw.app.t.len() == 0)): #= If only one doc exists, it has no name and its empty #= then 'overwrite' the document because it's the initial #= document. doc = doc_list.get_active_doc() doc.set_full_name(full_doc_name) doc.set_codec(codec_ix) else: doc = document() doc.set_full_name(full_doc_name) doc.set_codec(codec_ix) doc_list.add_doc(doc) switch_to_doc(doc) #= Note: the loaded content will be saved to 'doc' when switched to #= another doc so no need to do it here. appuifw.app.t.set(unicode(teksti)) appuifw.app.t.set_pos(0) options_menu_h.set_menu() def load_document(): global default_path s = select_file(default_path, 'Select file to load') if s == None: appuifw.note(u'No file loaded!', 'info'); return default_path = s[0] load_file(os.path.join(s[0], s[1])) def load_favorite(): menu = u_favdoc_list() ix = appuifw.popup_menu(menu) if ix == None or menu[ix] == unicode(''): return load_file(CNF.getConfigParameter('favdocs', 'doc'+str(ix))) def revert_file(): """ Re-read the content of the current document from the file on the disk. The content of the current buffer will be discarded. """ global doc_list full_doc_name = doc_list.get_active_doc().get_full_name() if full_doc_name == '': appuifw.note(u'Document has no name, yet', 'error') return if not os.path.isfile(full_doc_name): appuifw.note(u'File does not exist on disk', 'error') return try: codec_ix = doc_list.get_active_doc().get_codec() teksti, codec_ix = load(full_doc_name, TXT_CODEC_NAMES[codec_ix]) if teksti == None: appuifw.note(u'Nothing to read!', 'error') return #except UnicodeError, detail: # appuifw.note(u'Error while reading!\n' + unicode(detail), 'error') # return except: type, value = sys.exc_info() [:2] appuifw.note(u'Error while reading!\n' + \ unicode(str(type)+':'+str(value)), 'error') return appuifw.app.t.set(unicode(teksti)) #appuifw.app.t.set(0) #= #= Switch to anoher open document. #= def switch_to_doc(doc): global doc_list active_doc = doc_list.get_active_doc() if not active_doc == None: #= Save the latest information of the currently active document active_doc.set_content(appuifw.app.t.get()) active_doc.set_position(appuifw.app.t.get_pos()) #= Insert the new document doc.set_content_visible() appuifw.app.t.set_pos(doc.get_position()) doc_list.set_active_doc(doc) #= #= Select document from the list of open docs. #= def select_doc_from_list(): global doc_list ix = appuifw.popup_menu(doc_list.get_name_list(True)) if ix == None: return switch_to_doc(doc_list.get_doc_by_index(ix)) #= #= SMS #= def insert_sms(): """ Insert an SMS from the Inbox to the current cursor position. """ import sms_ui set_rlabel(u'Exit') imp_sms = sms_ui.sms_importer().select() if imp_sms != None: appuifw.app.t.add(unicode(imp_sms)) set_rlabel(u'Words') def send_as_sms(): """ Send currently active document using SMS. """ import sms_ui set_rlabel(u'Exit') sms_ui.send_sms(appuifw.app.t.get()) set_rlabel(u'Words') #= #= MMS #= def send_as_mms(): """ Send currently active document using MMS. """ global default_path import sms_ui set_rlabel(u'Exit') sms_ui.send_mms(appuifw.app.t.get(), default_path) set_rlabel(u'Words') #= #= BlueTooth #= def send_over_bt(): """ Send currently active document over BlueTooth. """ import bt_ui global doc_list fname = doc_list.get_active_doc().get_name() if fname == '': fname = 'noname' temp_dir = 'D:\__pyEditTemp' if os.path.exists(temp_dir): if not os.path.isdir(temp_dir): appuifw.note(u'Cannot create ' + unicode(temp_dir), 'error') return else: try: os.mkdir(temp_dir) except: appuifw.note('Cannot create ' + unicode(temp_dir), 'error') return temp_file = os.path.join(temp_dir, fname) try: save(temp_file, appuifw.app.t.get(), TXT_CODEC_NAMES[doc_list.get_active_doc().get_codec()]) except: appuifw.note(u'Cannot save temporary file ' + unicode(temp_file), 'error') return set_rlabel(u'Exit') bt_ui.send_over_bt(temp_file) set_rlabel(u'Words') try: os.remove(temp_file) os.rmdir(temp_dir) except: pass #= #= Sentences #= def sentences_handler(): appuifw.app.t.add(get_sentence()) def load_sentences_file(file_to_load=None): sentence_list = None if file_to_load == None: s = select_file(None, 'Select sentences to load') else: s = os.path.split(file_to_load) if s == None: appuifw.note(u'No sentences loaded!', 'info'); else: snt_file = os.path.join(s[0], s[1]) if os.path.isdir(snt_file): appuifw.note(u'Cannot load a directory!', 'error') else: try: #= The sentence list is always coded using utf-16. sentence_list, dummy = load(snt_file, 'utf-16') except UnicodeError, detail: appuifw.note(u'Error while reading!\n' + unicode(detail), 'error') except: appuifw.note(u'Error while reading!', 'error') sentences = [] if not sentence_list == None: for item in sentence_list.splitlines(): #= Make sure the reserved 'word' is dropped #= from the sentence list. if not item == '': sentences.append(unicode(item)) sentences.append(u'') return sentences def get_sentence(): #= Create a menu that can be used to select certain senteces. #= Also one that can be used to select loading a new file... global sentences_list sentence_items = sentences_list ix = appuifw.popup_menu(sentence_items) if ix == None: return u'' sentence = sentence_items[ix] if sentence[:11] == u'': words = sentence[11:].split('+', 1) try: sentence = u' '*int(words[0]) if len(words) > 1: sentence += words[1] except: sentence = u'' if sentence == u'': sentences_list = load_sentences_file() sentence = u'' return sentence def set_default_sentences_file(): global sentences_list snt_file = CNF.getConfigParameter('misc', 'default_sentences_file') if snt_file == '': sentences_list = [u'4', u'1+- ', u'5+* ', u'9+o ', u''] else: sentences_list = load_sentences_file(snt_file) #= #= Document info #= def show_doc_info(): global doc_list s1 = u'Chars: ' + unicode(appuifw.app.t.len()) s2 = u'Codec: ' + \ unicode(TXT_CODEC_NAMES[doc_list.get_active_doc().get_codec()]) s3 = u'Name: ' + unicode(doc_list.get_active_doc().get_full_name()) set_rlabel(u'Close') fv = pyS60uiutil.\ fileViewer('Document info', font=CNF.getConfigParameter('misc', 'font'), color=CNF.getConfigParameter('misc', 'font_color'), font_size=CNF.getConfigParameter('misc', 'font_size'), screen=CNF.getConfigParameter('misc', 'screen_mode')) fv.view(s1 + u'\n' + s2 + u'\n' + s3) set_rlabel(u'Words') return def set_doc_specific_codec(): #= FIX2 #= Voiko liittaa osaksi document luokkaa??? #= Yhdistaa default konfikin kanssa? appuifw.note(u'Not implemented yet', 'info') #= #= Create a new document #= def create_document(): global doc_list doc = document() doc.set_codec(CNF.getConfigParameter('txtcodecs', 'default_codec_ix')) doc_list.add_doc(doc) switch_to_doc(doc) options_menu_h.set_menu() #= #= Close document #= def close_document(): """ Closes currently active document. If this is the last document then a new one is created automatically. """ if appuifw.app.t.len(): if not appuifw.query(u'Want to close current document?', 'query'): return global doc_list doc_list.del_doc() if doc_list.get_nr_of_docs() == 0: create_document() else: doc = doc_list.get_active_doc() doc.set_content_visible() appuifw.app.t.set_pos(doc.get_position()) options_menu_h.set_menu() #= #= About #= def about_pyEditS60(): import graphics, pyS60gfxutil, pyedits60version img = None def _redraw(area=None): cnvs.clear() if img != None: cnvs.blit(img) basicapp = pyS60uiutil.save_current_app_info() appuifw.app.screen = 'full' _lock = e32.Ao_lock() appuifw.app.exit_key_handler = lambda: _lock.signal() cnvs = appuifw.Canvas(_redraw) appuifw.app.body = cnvs appuifw.app.menu = [] mylines = [('C', 'pyEdit for S60'), ('C', '[v'+pyedits60version.v+' '+pyedits60version.d+']'), ('C', 'Copyright (c) 2005 - '+pyedits60version.d[:4]), ('C', 'by'), ('C', 'Lasse Huovinen')] try: img = graphics.Image.new(cnvs.size) #img.clear((128,128,128)) img.clear((255,255,255)) fnt = ('title', None, None) pyS60gfxutil.positionedTextImage(mylines, img, 0, 7, 'middle', fnt, (0,0,255)) fnt = ('legend', None, None) pyS60gfxutil.positionedTextImage([('R', 'Close'),], img, 5, 7, 'bottom', fnt, (0,0,0)) _redraw() except: pyS60uiutil.restore_app_info(basicapp) return _lock.wait() pyS60uiutil.restore_app_info(basicapp) #= #= Help #= def help_pyEditS60(): set_rlabel(u'Exit') fv = pyS60uiutil.fileViewer('Help pyEdit', joystick=True) fv.load(EDITOR_HELP_FILE) fv.view() set_rlabel(u'Words') #= #= Copy/Cut'n'Paste #= class region_param_t: """ Data type used for storing text region selection parameters. """ def __init__(self): self.orig_sp = None #= original cursor start position self.content = None #= Original text self.marker = None #= Marker character def no_function(): pass def paste_clipboard(): global clipboard_content if clipboard_content: appuifw.app.t.add(clipboard_content) def text_region_start(): global options_menu_h, selprm set_rlabel(u'') appuifw.app.exit_key_handler = no_function options_menu_h.set_menu_state('region') options_menu_h.set_menu() selprm = region_param_t() selprm.orig_sp = appuifw.app.t.get_pos() selprm.content = appuifw.app.t.get() #= Insert 'region start marker', we could possibly use 'unassigned' #= character in the case the editor shows that as a 'box'. Then there #= would not be possibility for conflict. #= Another possibility would be to check whether the mark is used in the #= doc and then select another marker or something like that. #u'\u00d7' #= multiplication sign #u'\u02d0' #= triangular colon - not shown #u'\u2022' #= black small circle #u'\u2023' #= triangular bullet - not shown #u'\u203b' #= reference mark - not shown #u'\u25a0' #= black square - not shown selprm.marker = u'\u00d7' #appuifw.app.t.style = appuifw.STYLE_BOLD | \ # appuifw.STYLE_UNDERLINE | \ # appuifw.HIGHLIGHT_ROUNDED appuifw.app.t.style = appuifw.STYLE_BOLD | \ appuifw.HIGHLIGHT_STANDARD appuifw.app.t.add(selprm.marker) appuifw.app.t.style = 0 appuifw.app.t.set_pos(selprm.orig_sp) def text_region_error(): global options_menu_h, selprm appuifw.note(u'Region marker lost. Region editing aborted.', 'error') #= Instead of cancellation the made changes could be left effective. text_region_cancel() def text_region_cancel(): #= Just return the original text global options_menu_h, selprm set_rlabel(u'Words') appuifw.app.exit_key_handler = sentences_handler options_menu_h.set_menu_state('normal') options_menu_h.set_menu() appuifw.app.t.clear() appuifw.app.t.set(selprm.content) appuifw.app.t.set_pos(selprm.orig_sp) del selprm def text_region_done(): global options_menu_h, selprm set_rlabel(u'Words') appuifw.app.exit_key_handler = sentences_handler options_menu_h.set_menu_state('normal') options_menu_h.set_menu() #= Remove region marker. If the marker is not found do not #= consider it as an eror. marker_pos = appuifw.app.t.get().find(selprm.marker) if marker_pos != -1: cur_pos = appuifw.app.t.get_pos() if cur_pos > 0: cur_pos -= 1 appuifw.app.t.delete(marker_pos, 1) appuifw.app.t.set_pos(cur_pos) del selprm def text_region_search(): #= more useful when used along with replacing function... global selprm sp = appuifw.app.t.get().find(selprm.marker) if sp == -1: text_region_error() return ep = appuifw.app.t.get_pos() if (sp == ep) or (sp + 1 == ep): #= No real region selected -> do nothing return if sp < ep: search((sp+1, ep)) else: search((ep, sp)) def text_region_replace(): global selprm sp = appuifw.app.t.get().find(selprm.marker) if sp == -1: text_region_error() return ep = appuifw.app.t.get_pos() if (sp == ep) or (sp + 1 == ep): #= No real region selected -> do nothing return if sp < ep: replace((sp+1, ep)) else: replace((ep, sp)) def text_region_end(operation): global options_menu_h, selprm, clipboard_content set_rlabel(u'Words') appuifw.app.exit_key_handler = sentences_handler options_menu_h.set_menu_state('normal') options_menu_h.set_menu() sp = appuifw.app.t.get().find(selprm.marker) if sp == -1: text_region_error() return ep = appuifw.app.t.get_pos() if (sp == ep) or (sp + 1 == ep): #= No real region selected -> same as cancel text_region_cancel() return if sp < ep: selected_text = appuifw.app.t.get(sp+1, (ep - sp - 1)) if operation == 'cut': appuifw.app.t.delete(sp+1, (ep - sp - 1)) cursor_position = sp else: cursor_position = ep clipboard_content = selected_text elif sp > ep: selected_text = appuifw.app.t.get(ep, (sp - ep)) if operation == 'cut': appuifw.app.t.delete(ep, (sp - ep)) cursor_position = ep clipboard_content = selected_text appuifw.app.t.set_pos(cursor_position) #= Show the content of the selected text region. if len(selected_text) > 50: selected_text = selected_text[:25] + ' ... ' + selected_text[-25:] appuifw.note(unicode(selected_text), 'info') text_region_done() #= #= Search and replace #= def init_search_and_replace(): global search_options, replace_options search_options = (u'', [(u'Case sensitive', False), (u'From beginning', True)]) replace_options = (u'', u'', [(u'Replace all', False), (u'Case sensitive', False), (u'From beginning', True)]) def stop_search_and_replace(): global options_menu_h, current_find_parameters options_menu_h.set_menu_state(current_find_parameters['prevmenu']) options_menu_h.set_menu() def restart_search(): global current_find_parameters current_find_parameters['start'] = current_find_parameters['origstart'] find_next() def find_next_match(start, end, searchstr, sensitive, overwrap): #= Note overwrapping not supported yet if sensitive: #= case sensitive matchpos = appuifw.app.t.get().find(searchstr, start, end) else: matchpos = appuifw.app.t.get().lower().find(searchstr.lower(), \ start, end) return matchpos def find_next(): global current_find_parameters if (current_find_parameters['region'] != None and \ current_find_parameters['origdoclen'] != appuifw.app.t.len()): appuifw.note(u'Unexpected change in region. Operation stopped!', 'error') stop_search_and_replace() return matchpos = find_next_match(current_find_parameters['start'], current_find_parameters['end'], current_find_parameters['searchstring'], current_find_parameters['sensitive'], current_find_parameters['overwrap']) if matchpos == -1: current_find_parameters['start'] = -1 appuifw.note(u'string not found', 'error') else: appuifw.app.t.set_pos(matchpos) if matchpos == current_find_parameters['end']: current_find_parameters['start'] = matchpos else: current_find_parameters['start'] = matchpos + 1 def search(region=None): global options_menu_h, search_options, current_find_parameters searchstr = appuifw.query(u'Search for', 'text', search_options[0]) if searchstr == None: return oldsrcsize = appuifw.app.screen oldtitle = appuifw.app.title appuifw.app.screen = 'normal' appuifw.app.title = u'Search options' mcl= pyS60uiutil.multiChoiceList() newlist = mcl.select(search_options[1]) set_rlabel(u'Words') appuifw.app.screen = oldsrcsize appuifw.app.title = oldtitle if newlist[0] == False: return else: selections = newlist[1] #= save options for the next search l = [] for ix in range(len(newlist[1])): l.append((search_options[1][ix][0], selections[ix])) search_options = (searchstr, l) #= handle selected options and start searching if region == None: if selections[1]: start = 0 #= start from beginning else: start = appuifw.app.t.get_pos() end = appuifw.app.t.len() else: start = region[0] end = region[1] current_find_parameters = {'origstart': start, 'start': start, 'end': end, 'searchstring': searchstr, 'sensitive': selections[0], 'overwrap': False, 'prevmenu': options_menu_h.get_menu_state(), 'region': region, 'origdoclen': appuifw.app.t.len()} options_menu_h.set_menu_state('search') options_menu_h.set_menu() find_next() def replace_text_in_position(pos, searchstr, replacestr): appuifw.app.t.delete(pos, len(searchstr)) appuifw.app.t.set_pos(pos) #= Needed? appuifw.app.t.add(replacestr) def replace_and_find(): global current_find_parameters if current_find_parameters['start'] != -1: replace_text_in_position(current_find_parameters['start'] - 1, current_find_parameters['searchstring'], current_find_parameters['replacestring']) off = current_find_parameters['endoffset'] current_find_parameters['origdoclen'] += off current_find_parameters['end'] += off find_next() def replace(region=None): global options_menu_h, replace_options, current_find_parameters searchstr = appuifw.query(u'Replace string', 'text', replace_options[0]) if searchstr == None: return replacestr = appuifw.query(u'with string', 'text', replace_options[1]) if replacestr == None: return oldsrcsize = appuifw.app.screen oldtitle = appuifw.app.title appuifw.app.screen = 'normal' appuifw.app.title = u'Replace options' mcl= pyS60uiutil.multiChoiceList() newlist = mcl.select(replace_options[2]) set_rlabel(u'Words') appuifw.app.screen = oldsrcsize appuifw.app.title = oldtitle if newlist[0] == False: return else: selections = newlist[1] #= save options for the next search l = [] for ix in range(len(newlist[1])): l.append((replace_options[2][ix][0], selections[ix])) replace_options = (searchstr, replacestr, l) #= handle selected options and start replacing if region == None: if selections[2]: start = 0 #= start from beginning else: start = appuifw.app.t.get_pos() end = appuifw.app.t.len() end_offset = 0 else: #= replacing within region start = region[0] end = region[1] end_offset = len(replacestr) - len(searchstr) if selections[0]: #= replace all count = 0 while 1: matchpos = find_next_match(start, end, searchstr, selections[1], False) if matchpos == -1: appuifw.note(unicode(str(count)) + u' occurrences replaced', \ 'info') break else: count += 1 replace_text_in_position(matchpos, searchstr, replacestr) start = matchpos + 1 if end_offset: end += end_offset else: #= find and replace occurrences one by one current_find_parameters = {'origstart': start, 'start': start, 'end': end, 'endoffset': end_offset, 'searchstring': searchstr, 'replacestring': replacestr, 'sensitive': selections[0], 'overwrap': False, 'prevmenu': options_menu_h.get_menu_state(), 'region': region, 'origdoclen': appuifw.app.t.len()} options_menu_h.set_menu_state('replace') options_menu_h.set_menu() find_next() return #= #= Redraw the current buffer with the current font settings. #= def redraw(): t = appuifw.app.t.get() p = appuifw.app.t.get_pos() appuifw.app.t.set(t) appuifw.app.t.set_pos(p) #= #= Options menu handling #= class options_menu: """ Handles the main options menu. The menu is 'dynamic', i.e., it changes according to the operations performed by the user. """ def __init__(self): self.which_menu = 'normal' def set_menu_state(self, state): """ 'normal' - normal operation 'region' - region, 'search' - search 'replace' - replace """ self.which_menu = state def get_menu_state(self): """ Returns the current menu state. May be used to create nested functionality, normal->region->search for instance. """ return self.which_menu def set_menu(self): """ Create and set main options menu. """ global doc_list if self.which_menu == 'region': appuifw.app.menu = [(u'Copy region', \ lambda: text_region_end('copy')), (u'Cut region', \ lambda: text_region_end('cut')), (u'Search in region', text_region_search), (u'Replace in region', text_region_replace), (u'Quit region', text_region_done), (u'Undo and Quit', text_region_cancel)] elif self.which_menu == 'search': appuifw.app.menu = [(u'Find next', find_next), (u'Restart', restart_search), (u'Quit', stop_search_and_replace)] elif self.which_menu == 'replace': appuifw.app.menu = [(u'Replace and Find', replace_and_find), (u'Leave and Find', find_next), (u'Restart', restart_search), (u'Quit', stop_search_and_replace)] else: #= 'normal' appuifw.app.menu = [] #= Sub-menu for editing functionality sub_menu_edit = ((u'Mark region', text_region_start), (u'Paste clipboard', paste_clipboard), (u'Search', search), (u'Replace', replace), (u'Redraw', redraw)) appuifw.app.menu.append((u'Edit', sub_menu_edit)) nr = doc_list.get_nr_of_docs() if nr > 1: item = unicode('Switch document (' + str(nr) + ')') appuifw.app.menu.append((item, select_doc_from_list)) #= Sub-menu for loading and inserting from a file sub_menu_load = ((u'Favorites', load_favorite), (u'Load', load_document), (u'Insert', insert_file), (u'Revert', revert_file)) appuifw.app.menu.append((u'Load / Insert', sub_menu_load)) #= Sub-menu for saving to a file sub_menu_save = ((u'Save', lambda: save_file(appuifw.app.t.get())), (u'Save as', lambda: save_file_as(appuifw.app.t.get()))) appuifw.app.menu.append((u'Save (as)', sub_menu_save)) #= Sub-menu for docs sub_menu_document = ((u'Info', show_doc_info), (u'Codec', set_doc_specific_codec), (u'New', create_document), (u'Close', close_document)) appuifw.app.menu.append((u'Document', sub_menu_document)) appuifw.app.menu.append((u'Exit', my_exit)) appuifw.app.menu.append((u'Settings', editor_settings)) #= Sub-menu for tools sub_menu_tools = ((u'Insert SMS', insert_sms), (u'Send over BlueTooth', send_over_bt), (u'Send as text message', send_as_sms)) if e32.s60_version_info[0] >= 3: sub_menu_tools += ((u'Send as MMS', send_as_mms),) appuifw.app.menu.append((u'Tools', sub_menu_tools)) #= These should be available always. sub_menu_help = ((u'Help', help_pyEditS60), (u'About', about_pyEditS60)) appuifw.app.menu.append((u'Help', sub_menu_help)) #= #= Document holder #= class document: """ Document info. """ def __init__(self, full_name=''): self.codec_ix = 0 self.full_name = full_name self.content = u'' self.cursor_position = 0 def get_codec(self): return self.codec_ix def set_codec(self, codec_ix): self.codec_ix = codec_ix def get_path(self): return os.path.split(self.full_name)[0] def get_name(self): return os.path.split(self.full_name)[1] def get_full_name(self): return self.full_name def set_full_name(self, full_name): self.full_name = full_name def set_position(self, pos): self.cursor_position = pos def get_position(self): return self.cursor_position def set_content_visible(self): """ Set the content of the document visit in Text UI control. The content in 'doc' will be removed in order to save memory. """ appuifw.app.t.clear() if self.content == None: return appuifw.app.t.set(self.content) self.content = None def set_content(self, content): """ Set new content for the document. """ self.content = content #= #= Document list handler #= class document_list: """ Keeps track on currently open documents. """ def __init__(self): self.doc_list = [] self.active_ix = None def add_doc(self, doc): self.doc_list.append(doc) def del_doc(self): #= Note, only currently active doc can be deleted. self.doc_list.remove(self.get_active_doc()) if len(self.doc_list) == 0: self.active_ix = None else: self.active_ix = 0 def get_nr_of_docs(self): return len(self.doc_list) def get_name_list(self, mark_active=False): list = [] if mark_active: active_doc = self.doc_list[self.active_ix] for i in self.doc_list: if i == active_doc: list.append(unicode('*'+i.get_name())) else: list.append(unicode(' '+i.get_name())) else: for i in self.doc_list: list.append(unicode(i.get_name())) return list def set_active_doc(self, doc): self.active_ix = self.doc_list.index(doc) def get_active_doc(self): if self.active_ix == None: return None return self.doc_list[self.active_ix] def get_doc_by_index(self, index): return self.doc_list[index] #= #= Exiting #= def my_exit(): global doc_list nr = doc_list.get_nr_of_docs() cnf = True if nr > 1: cnf = appuifw.query(unicode('Want to exit?\n' + str(nr) +\ ' open docs.'), 'query') elif appuifw.app.t.len(): cnf = appuifw.query(u'Want to exit?', 'query') if cnf: escript_lock.signal() #= #= The editor #= def pyEditorS60(): global options_menu_h, doc_list global clipboard_content, default_path #= Check if the currently installed pyS60uiutil module version is #= compatible with this version of pyEdit. if pyS60uiutil.version_compatibility((0,10)) == False: msg = 'pyS60uiutil\nversion %d.%d.%d\ntoo old' % pyS60uiutil.version appuifw.note(unicode(msg), 'error') return old_app_gui_data = pyS60uiutil.save_current_app_info() appuifw.app.title = u'pyEdit for S60' appuifw.app.t = appuifw.Text(u'') appuifw.app.body = appuifw.app.t set_rlabel(u'Words') clipboard_content = None default_path = None initialize_editor_settings() apply_editor_settings() set_default_sentences_file() stick = pyS60uiutil.jumpTextCursorWithJoystick(appuifw.app.t) init_search_and_replace() doc_list = document_list() FIX: jos initial doc configured, niin lisaa ja lataa se tassa!!! oma function ehka hyva!!!!!!! initial_doc = document() initial_doc.set_codec(CNF.getConfigParameter('txtcodecs', 'default_codec_ix')) doc_list.add_doc(initial_doc) doc_list.set_active_doc(initial_doc) appuifw.app.exit_key_handler = sentences_handler options_menu_h = options_menu() options_menu_h.set_menu() escript_lock.wait() #= Wait for exiting... set_rlabel(u'Exit') pyS60uiutil.restore_app_info(old_app_gui_data) if __name__ == '__main__': localpath = str(os.path.split(appuifw.app.full_name())[0]) localpath = os.path.join(localpath, 'my') EDITOR_CONFIG_FILE = os.path.join(localpath, EDITOR_CONFIG_FILE) pyEditorS60()